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 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)¶
$ 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/scriptruns againstrpctestplus 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).