Skip to main content

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.

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.

Shape

interface CanonicalIntentMessage {
  chainId: number;
  transfers: CanonicalTransferEntry[];
  action:
    | { type: "deposit"; recipient: string }
    | { type: "execute"; signedTxHash: string };
  nonce: string;
  expiresAt: number; // unix milliseconds
}
FieldWhat it is
chainIdExecution-chain id, fetched from getNetworkInfo(). Pinning this in the signature prevents cross-chain replay.
transfersThe Spark transfer ids and amounts to credit before the action runs. Empty for execute-only intents.
action.depositMint deposits to recipient (an EVM address) and stop.
action.executeRun a pre-signed EVM transaction. The signed-tx hash is part of the preimage so swapping the tx body invalidates the signature.
nonceRandom hex string for uniqueness. The SDK generates one.
expiresAtPast 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 with expiresAt:
await execClient.deposit({
  deposits: [...],
  expiresAt: Date.now() + 60 * 60 * 1000, // 1 hour
});
The cap is 24 hours. The gateway rejects anything beyond. Choose based on how long you can tolerate the intent sitting between admission and finality:
  • 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:
1. transfers:  Spark sats + Spark token  (multi-asset deposit)
2. action:     execute  (a Conductor swap that consumes both)
3. result:     SparkWithdrawal events  (settlement dispatches to Spark)
Atomicity is the point. Either the deposits land and the action runs, or neither does. The output dispatches back to Spark in the same finality round. The SDK exposes this composition through TradingClient.swap (deposit + swap + withdraw) and the lower-level execClient.execute(...) (any deposits + any pre-signed tx).

Status lifecycle

accepted -> oracle_pending? -> included_pending_finality -> finalized
                                                         \-> rejected
                                                         \-> expired
Status values are wire-format: lowercase, snake_case.
StatusMeaning
acceptedGateway took the intent, validated the signature and expiry, queued for sequencer admission.
oracle_pendingDeposit-shaped intent waiting for the Spark operator-DB to confirm every referenced transfer. Skipped when the intent has no deposits.
included_pending_finalityThe sequencer included the intent in a block. Validators are signing the finality certificate.
finalizedValidators reached quorum. The action ran. Settlement dispatched.
rejectedGateway admission failed, the deposit oracle couldn’t verify a transfer, or the EVM transaction reverted. statusMessage carries the reason.
expiredThe intent’s expiresAt passed before any terminal state was reached.
For most flows, wait for finalized before reading downstream balances. For optimistic UIs, included_pending_finality is enough.

Replay protection

Two layers:
  1. Spark transfer ids. Each Spark transfer can only fund one intent. A second intent referencing the same id is rejected at admission.
  2. 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.
You don’t manage either of these directly. The SDK derives the EVM nonce, and Spark transfer ids are unique per transfer.

Why intents instead of transactions

Three reasons the SDK doesn’t expose raw EVM transactions as the unit of work:
  1. Cross-chain atomicity. Spark deposits and the EVM action commit together. Plain transactions can’t reference Spark transfer state.
  2. Operator-paid execution. The sequencer pays the base fee for every transaction. The intent boundary is where the operator authorizes that.
  3. Settlement dispatch. Withdrawals need to translate SparkWithdrawal events back into Spark transfers. The intent boundary is what the settlement layer reads.
If you need raw EVM access (read-only, or for tooling that already speaks JSON-RPC), the gateway exposes a standard RPC endpoint at rpcUrl. Writes go through intents.