Ship TRON contracts
without the JavaScript.¶
Compile, test, deploy, and estimate — from a single Go binary.
SunSail replaces the JavaScript toolchain with a Foundry-inspired workflow: incremental compilation, Solidity-native tests on a real TVM interpreter, deployment scripts that build the actual signed transactions, and energy figures validated ±0 against live TRON.
Install with Go 1.26+: go install github.com/sailor-labs/sunsail/cmd/sun@latest
$ sun test VoyageTest [PASS] testAddWaypoints (energy: 2250) [PASS] testSail (energy: 35034) [PASS] testSailToSamePositionReverts (energy: 2796) [PASS] testSailWithContext (energy: 31261) 4 passed, 0 failed
Why SunSail¶
static binary
One binary, zero runtime deps¶
CGO_ENABLED=0, statically linked, cross-compiled for linux/darwin/windows. No Node, no npm — just sun.
no javascript
A real TVM, not an EVM guess¶
The built-in interpreter implements TRON's divergences: the two-layer energy model, SSTORE pricing, exact precompiles, 0x41 addresses, TIP-6780.
±0 verified
Exact metering, proven on-chain¶
Planned Energy / Bandwidth / TRX-burn equal the solidified receipts exactly — no percentage tolerances, validated against java-tron on Nile.
The walkthrough, command by command¶
Every block below is real output from the sun binary, driving a tiny Voyage
contract from an empty directory to a live contract on the Nile test network.
sun init — scaffold a Foundry-like project from embedded templates
$ sun init demo
Initialized SunSail project in demo
.gitignore
README.md
lib/
lib/.gitkeep
out/
sail.toml
script/
script/Deploy.s.sol
src/
src/Counter.sol
test/
test/Counter.t.sol
sun compile — incremental, deterministic; the pinned solc is downloaded and SHA-256-verified on first use
$ sun compile
downloading solc 0.8.23 (darwin/amd64)…
verified solc 0.8.23 (sha256)
Compiled 3 unit(s), 0 cached
script/Deploy.s.sol
src/Counter.sol
test/Counter.t.sol
$ sun compile # second run: nothing changed
Compiled 0 unit(s), 3 cached
sun test -vvv — Solidity-native tests on the local TVM, with cheatcodes, decoded events, and source-mapped traces
$ sun test -vvv
VoyageTest
[PASS] testSail (energy: 35034)
emit THbhyMMwMrp2zQe3csKqbj5yyRhnRPJizm topic0=0x74b1fee7…e07f data=0x…0005
[ROOT] VoyageTest (energy: 35034)
[STATICCALL] Voyage (energy: 392)
[CALL] Voyage (energy: 27857)
[PASS] testSailToSamePositionReverts (energy: 2796)
[ROOT] VoyageTest (energy: 2796)
[CALL] Voyage (energy: 811) ← revert: already there [src/Voyage.sol:25]
4 passed, 0 failed
The revert resolves to src/Voyage.sol:25 — the exact source line, not a bytecode offset.
sun estimate — exact Energy / Bandwidth / TRX-burn for a call to an already-deployed contract (positional syntax)
$ sun estimate TMts7FK5h5US99x16qCRkJUzd1VYtahd7o "sail(uint256)" 9 --network nile
Energy 19286 (estimate-energy)
Bandwidth 313
TRX burn 2.241600 TRX (2241600 sun; energy 100 sun/u, bandwidth 1000 sun/B)
Prices (energy 100 sun/u, bandwidth 1000 sun/B) are read from the chain, never hardcoded.
sun script — build the REAL transaction sequence; each address derives from its transaction id
Without --broadcast, sun script prints the plan and touches nothing:
$ sun script script/DeployVoyage.s.sol --network nile
owner TESXcRcFMU2LwroehawwC2B3HgMYe3XSZ2
forking nile at solidified block 69344261
plan: DeployVoyage on nile (fork block 69344261, ref block 69344280, 1 txs)
1 deploy → TTh5G9khEZAyybTF3YHwCARtNJCCDQNSUD energy 427793 bandwidth 3295 burn 46.074300 TRX
txid 9ab38eec4865097a90408ee38474625432800c1c5d706d2dd0b93ee7688d857c
totals: energy 427793, bandwidth 3295, burn 46.074300 TRX
Plan cost == on-chain receipt, exactly
Broadcasting the same script to Nile deployed TMts7FK5h5US99x16qCRkJUzd1VYtahd7o
(tx 960ce7c2…84db5d), and the solidified deploy receipt reported energy
427793 — the same number the plan predicted. A follow-up sail(5)
interaction (tx 0b41a3dc…c805e41) metered 27857, matching its test-trace
energy. Because addresses derive from the transaction id, the dry-run address
(TTh5G9kh…) differs from the broadcast one — each build uses a fresh reference
block, so a different txid yields a different address.
sun audit — Aderyn static analysis with a TVM-aware post-filter
$ sun audit
LOW · src/Voyage.sol:2 · unspecific-solidity-pragma · Unspecific Solidity Pragma
LOW · src/Voyage.sol:24 · unused-public-function · Public Function Not Used Internally
LOW · src/Voyage.sol:33 · unused-public-function · Public Function Not Used Internally
0 high, 3 low (1 suppressed by the TVM post-filter — --show-suppressed to list)
One EVM-only finding is suppressed (never silently — --show-suppressed lists it); CI decides the gate with --fail-on high.
sun sonar — offline utilities: address conversion, selectors, CREATE2
$ sun sonar address TMts7FK5h5US99x16qCRkJUzd1VYtahd7o
base58 TMts7FK5h5US99x16qCRkJUzd1VYtahd7o
hex 4182cf3d4db8ede38e0ae4d674cba9965b3011b653
evm 0x82cf3d4dB8eDe38e0Ae4D674cBa9965B3011B653
$ sun sonar selector "sail(uint256)"
0x4a225440
The evm line is the EIP-55 checksummed form — the one solc accepts in a Solidity address literal.
The full cycle, end to end¶
sun init dapp && cd dapp
sun compile
sun test -vvv
sun script script/Deploy.s.sol --network nile --broadcast # deploy first
sun estimate <deployed-address> "sail(uint256)" 9 --network nile
sun audit --fail-on high
From an empty directory to a live, metered contract on TRON — with one binary and no JavaScript.