Shape
| Field | What it is |
|---|---|
chainId | Execution-chain id, fetched from getNetworkInfo(). Pinning this in the signature prevents cross-chain replay. |
transfers | The Spark transfer ids and amounts to credit before the action runs. Empty for execute-only intents. |
action.deposit | Mint deposits to recipient (an EVM address) and stop. |
action.execute | Run a pre-signed EVM transaction. The signed-tx hash is part of the preimage so swapping the tx body invalidates the signature. |
nonce | Random hex string for uniqueness. The SDK generates one. |
expiresAt | Past this unix-ms timestamp the gateway refuses admission, inclusion, and settlement. |
How signing works
The SDK serializes the message in canonical camelCase JSON, takes a SHA-256 hash, and signs it with the identity key (DER-encoded secp256k1 ECDSA). The validator’s signature check uses the same preimage, so any deviation between client and gateway invalidates the intent. You don’t construct this message by hand.ExecutionClient.deposit / withdraw / withdrawToken / execute all build and sign it for you.
Expiry
The default TTL is 15 minutes. Override per-call withexpiresAt:
- Short TTL (1-5 min) for interactive UIs where the user should retry if something stalls.
- Default (15 min) for batch flows that need to survive transient gateway hiccups.
- Long (1-24h) for offline-signed intents you plan to submit later.
Composition
A single intent can carry many deposits and one execute action. The pattern most flows follow:TradingClient.swap (deposit + swap + withdraw) and the lower-level execClient.execute(...) (any deposits + any pre-signed tx).
Status lifecycle
SCREAMING_SNAKE_CASE.
| Status | Meaning |
|---|---|
ACCEPTED | Gateway took the intent: signature, expiry, and replay checks passed, and it is queued for sequencer admission. A deposit-shaped intent stays here while the deposit oracle confirms every referenced transfer against the Spark operator DB. |
INCLUDED_PENDING_FINALITY | The sequencer included the intent in a block. The EVM transaction ran successfully. Validators are signing the finality certificate. |
FINALIZED | Validators reached quorum. The action ran and settlement dispatched. |
EXPIRED | Terminal failure. The expiresAt deadline passed, gateway admission failed, the deposit oracle could not verify a transfer, or the EVM transaction reverted. statusMessage carries the reason. |
FINALIZED before reading downstream balances. For optimistic UIs, INCLUDED_PENDING_FINALITY is enough.
Replay protection
Two layers:- Spark transfer ids. Each Spark transfer can only fund one intent. A second intent referencing the same id is rejected at admission.
- EVM nonce. The signed transaction inside an execute intent has the standard EVM nonce, so the runtime rejects a replayed transaction even if the intent itself reaches the chain twice.
Why intents instead of transactions
Three reasons the SDK doesn’t expose raw EVM transactions as the unit of work:- Cross-chain atomicity. Spark deposits and the EVM action commit together. Plain transactions can’t reference Spark transfer state.
- Settlement dispatch. Withdrawals need to translate
SparkWithdrawalevents back into Spark transfers. The intent boundary is what the settlement layer reads. - Replay protection across Spark and EVM. The intent’s nonce binds the deposit ids, the EVM transaction, and the expiry into one preimage so a partial replay isn’t possible.
rpcUrl. Writes go through intents.