Global and Partitioned State
SCL contract functions are classified as touching either global contract state or a bounded partition of it. The classification exists to solve one problem: making sure that one user's missing data can never freeze everyone else's assets.
The problem: data availability
An envelope call commits a hash of the signed payload on Bitcoin; the payload itself travels off-chain through the node network. That design keeps Bitcoin usage minimal, but it creates a gap: the chain can prove that a call happened without every node holding the data to execute it.
If a payload is withheld, nodes cannot compute the contract's next state. Execution for the affected state has to stall until the data arrives or the call expires. The question that matters is the blast radius of that stall. If the stall unit is the whole contract, then one withheld payload freezes every holder of a token, and an attacker can grief an entire asset for the price of a single transaction fee. That is unacceptable for anything holding real value.
The solution: partition the state
For a partition-scoped function, the state a call can touch is derivable from its anchoring Bitcoin transaction alone: the token-bound input slots the transaction consumes, plus the output slots the transaction itself creates.
That property changes the stall unit from "the contract" to "the withholder's own transaction footprint":
- If Alice withholds the payload for her transfer, the only state that freezes is the slots her transaction consumed and created. Her tokens, her problem.
- Bob's transfer to Carol on the same token is untouched. It reads and writes different slots, so nodes execute it normally while Alice's call is unresolved.
This is why partition scoping is tied to UTXO-bound state: outpoints partition naturally, because Bitcoin already guarantees each one is consumed exactly once.
Address-keyed balance maps are refused partition classification, and the refusal is deliberate. A recipient's balance key can be touched by anyone who sends to them, so under address-keyed state, Alice's withheld transfer would freeze Carol's balance merely because Carol was the recipient. That is a griefing vector (freeze any account for one transaction fee), so the protocol rejects the classification outright rather than trusting contracts to avoid it.
Declaring the classification
Classification is per-function, in the contract source:
@interface(utxo_token_v1)
contract Token {
@partition_scoped
fn transfer(amount: Int, dest_vout: Int) { ... }
@global
fn set_issuance_policy(cap: Int) { ... }
}
@partition_scoped: the function touches only state derivable from its anchoring transaction. Stalls are confined to the caller's own footprint.@global(the default for undeclared functions): the function may touch anything, so a withheld global call falls back to whole-contract stall semantics.
The split usually falls out naturally: high-frequency user operations (transfers) are partition-scoped, while rare administrative operations (issuance policy, supply changes) are global. Users get isolation where it matters; admins accept broader semantics for calls that genuinely touch shared state.
Declarations are checked, not trusted
A classification is a performance promise, never a safety assumption. When a withheld payload finally arrives, the VM captures the call's actual read and write set and checks it against the partition that was frozen. If the call escaped its declared partition, execution degrades to the standard rollback-and-replay path, which converges byte-identically on every node regardless. A wrong (or malicious) declaration can cost performance; it cannot corrupt state or cause divergence.
Stalls are time-boxed
Partition stalls cannot last forever. Envelope transfers carry a void window (default 144 blocks, roughly a day, configurable per contract at deploy): the sender may void an unrevealed transfer within the window, and at expiry an unrevealed, unvoided transfer resolves automatically by the interface's deterministic fallback, carrying the tokens to a sender-designated output. Every node resolves it identically, because block distance is the only clock involved.
So for a partitioned token, the worst case from withheld data is: the withholder's own slots are stuck for at most the window, then resolve deterministically. Third parties never notice. Global-state stalls have no such fallback, which is exactly why the high-traffic paths of a token interface are required to be partition-scoped.
Summary
| Partition-scoped | Global | |
|---|---|---|
| State a call touches | Derivable from the anchoring tx alone | Potentially anything |
| Withheld-payload blast radius | The withholder's own footprint | The whole contract |
| Resolution | Void window expiry triggers deterministic carry-forward | Time-boxed rescue only, no fallback |
| Typical use | Transfers and user operations | Admin and issuance policy |
The net effect: a conforming token's holders are isolated from each other. No one can freeze your tokens by withholding their own data, and any stall that does occur resolves deterministically within a bounded number of Bitcoin blocks.