Skip to content

SCRIPTING.md — sun script: Full Sequential Plan, Policy Broadcast

Living reference for internal/script and the sun script command. Tier-1 authority is docs/ARCHITECTURE.md §5.7; the S7 differential battery (test/differential/script_test.go) is the empirical proof that planned figures equal on-chain receipts.


1. The model

sun script script/Deploy.s.sol --network nile [--broadcast] [--policy ...]

sun script compiles the project, executes the script contract's run() on a pinned fork of the target network, and builds the real transaction sequence before anything is broadcast. On TRON a deployed contract's address derives from its transaction id (0x41 ‖ keccak256(txid ‖ owner21)[12:]), so the plan cannot be simulated abstractly: transaction N is fully built and signed, its txid computed, and the resulting addresses injected into the still-running script before transaction N+1 exists (CLAUDE.md §3.7).

Without --broadcast the full plan is printed — one row per transaction with Energy (E_basic + DEM penalty), Bandwidth (serialized signed size + the 64-byte protocol result margin) and worst-case TRX burn at chain-read prices — and nothing touches the network's state. --json emits the same as a stable document.

Plan addresses are indicative, not final. Because a deploy address is f(txid) and each broadcast anchors to a fresh reference block, the txid — and therefore the derived address — differs between a dry-run plan and the eventual --broadcast. The address shown in the plan tells you the shape of the deployment; the definitive address is the one derived from the real txid at broadcast time (reported in the broadcast output). This is correct behavior — a direct consequence of CREATE = f(txid) — not a discrepancy.

2. What becomes a transaction (implicit top-frame recording)

The script contract itself lives only in the simulation (deployed at a synthetic address, never broadcast). Every transaction-forming action of run()'s root frame is intercepted (tvm.Config.Script, a closure injected by internal/script — the interpreter stays network-blind) and executed out-of-band as its own ROOT execution with the owner EOA as sender, exactly as it will run on chain:

Root-frame action Transaction Notes
new C(...) (CREATE) CreateSmartContract Address = f(txid), injected back into the script.
external call to a contract TriggerSmartContract Return data injected back.
value-only call to a codeless account (transfer/send/call{value:}) TransferContract Bandwidth only, zero energy.
STATICCALL (view/pure reads) — Free in-simulation read, never a transaction.
zero-value, zero-data call to a codeless account — Nothing to put on chain; succeeds without a transaction.
call with data to a codeless account plan error A trigger to a non-contract fails on chain — surfaced at plan time.
root-frame CREATE2, DELEGATECALL, CALLCODE, TRC-10 token call plan error No root-transaction form on TRON; deploy a factory / wrap in a contract.
call to the sail.sol cheatcode address plan error Cheatcodes have no meaning on a real chain.

Nested activity is untouched: a planned TriggerSmartContract whose target internally CREATEs, CALLs or CREATE2s runs those inside its own root execution, as on chain.

Costs are root-exact by construction: each action executes on the shared fork overlay (prior planned transactions already applied) with the DEM factors the fork captured, so PlannedTx.Energy is the penalty-inclusive figure the receipt will show. The S7 differential asserts receipt equality ±0 for both energy and bandwidth.

3. The signing key

The key comes from the PRIVATE_KEY environment variable; when unset, sun script reads the project's .env file (plain KEY=VALUE lines, only that key is extracted). It is required even for plan-only runs — §5.7 plans carry signed, broadcast-ready transactions, and the owner address drives the simulation. The key is never accepted as a flag, never printed, and never appears in any report. .env belongs in .gitignore (the scaffold ships it there).

4. Broadcast and the confirmation policy

--broadcast sends the plan sequentially: broadcast ALWAYS via the FullNode Wallet service; confirmation per --policy (§5.7 — TRON transaction state is binary, so there are no confirmation counts and no nonces):

Policy Advance when Notes
none immediately fire-and-forget
included GetTransactionInfoById (FullNode) sees it fast; revertible until solidified
solidified (default) the WalletSolidity service sees it irreversible downstream

Per-transaction timeout: --tx-timeout (default 90 s ≈ solidification with margin). Expiration window: --expiration (default 60 s). A failed on-chain receipt aborts with the node's message.

Lost/expired → suffix re-derivation. If a transaction disappears under included or expires without inclusion, the already-confirmed prefix is pinned (its txids — and therefore its addresses — are on chain) and run() is re-executed: the prefix actions reuse their confirmed txids verbatim while the suffix is rebuilt against a fresh reference block — new expirations → new txids → new dependent addresses. Broadcasting resumes from the failed index; re-derivations are bounded (3) and reported, never silent.

5. Worked example (the scaffold)

contract Deploy {
    function run() external returns (Counter counter) {
        counter = new Counter();
    }
}
$ sun script script/Deploy.s.sol --network nile
owner TESX…XSZ2
forking nile at solidified block 69329098
plan: Deploy on nile (fork block 69329098, ref block 69329117, 1 txs)
   1  deploy →   TAuv7tKr6af16XE5umaenLxiSqdb4gqqoM  energy 101188  bandwidth 846  burn 10.964800 TRX
      txid 0f395a4b…
totals: energy 101188, bandwidth 846, burn 10.964800 TRX

6. Testing

  • Hermetic (make check): internal/script runs against rpctest plus the loopback JSON-RPC storage stub — plan ordering, txid-derived addresses with zero broadcasts, suffix re-derivation reusing the confirmed prefix, all three policies, lost-tx retry.
  • E2E (make e2e): the real binary plans the scaffold script against the in-process node (plan-before-broadcast) and re-derives a lost transaction under --policy included.
  • Differential (make differential, key-gated): the real pipeline on Nile — deploy + interaction, receipts vs plan ±0, node contract address vs the txid-derived one (Tier-3).