An intent is the canonical message you sign to do anything on Flashnet Execution. It carries the Spark transfers funding the action, the action itself, and an expiry. The sequencer admits an intent, validators sign the finality certificate, and settlement dispatches results back to Spark — all bound to one signature.Documentation Index
Fetch the complete documentation index at: https://flashnet-build.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
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
| Status | Meaning |
|---|---|
accepted | Gateway took the intent, validated the signature and expiry, queued for sequencer admission. |
oracle_pending | Deposit-shaped intent waiting for the Spark operator-DB to confirm every referenced transfer. Skipped when the intent has no deposits. |
included_pending_finality | The sequencer included the intent in a block. Validators are signing the finality certificate. |
finalized | Validators reached quorum. The action ran. Settlement dispatched. |
rejected | Gateway admission failed, the deposit oracle couldn’t verify a transfer, or the EVM transaction reverted. statusMessage carries the reason. |
expired | The intent’s expiresAt passed before any terminal state was reached. |
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.
- Operator-paid execution. The sequencer pays the base fee for every transaction. The intent boundary is where the operator authorizes that.
- Settlement dispatch. Withdrawals need to translate
SparkWithdrawalevents back into Spark transfers. The intent boundary is what the settlement layer reads.
rpcUrl. Writes go through intents.