Skip to Content
APIPrepare

Prepare

POST /api/external/intents/{intentSessionId}/prepare

Builds the exact executable transactions for a ready quote and creates an attempt. Call it only after the user confirms the quote and before the quote’s expiresAt.

Request

{ "walletContext": { "connectorName": "MetaMask", "connectorId": "injected", "walletCapabilities": { "0x2105": { "atomic": { "status": "supported" } } } } }
FieldRequiredMeaning
walletContext.connectorNameNoHuman-readable wallet name
walletContext.connectorIdNoConnector identifier
walletContext.walletCapabilitiesNoThe unmodified result of wallet_getCapabilities, read immediately before this call. {} is valid and produces sequential sendTransaction actions

Never claim atomic batch support the wallet did not report.

Response

{ "apiVersion": "1", "attemptId": "attempt_00000000000000000001", "intentSessionId": "intent_00000000000000000001", "status": "prepared", "expiresAt": "2030-01-01T00:01:00.000Z", "evidenceDeadlineAt": "2030-01-01T00:15:30.000Z", "chainActions": [ { "chainId": 8453, "provider": "relay", "submitMethod": "wallet_sendCalls", "atomicRequired": true, "calls": [ { "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "data": "<prepared approval calldata>", "value": "0", "callId": "chain:8453:call:0", "callIndex": 0, "quoteIndex": 0 }, { "to": "0x2222222222222222222222222222222222222222", "data": "<prepared execution calldata>", "value": "0", "callId": "chain:8453:call:1", "callIndex": 1, "quoteIndex": 0 } ], "transactions": null, "transactionCount": 2, "quoteCount": 1 } ], "walletAuthorization": { "domain": { "name": "Sweep External Intent", "version": "1" }, "types": { "SweepExecutionAuthorization": [ { "name": "partnerId", "type": "string" }, { "name": "intentSessionId", "type": "string" }, { "name": "attemptId", "type": "string" }, { "name": "wallet", "type": "address" }, { "name": "recipient", "type": "string" }, { "name": "executionCommitment", "type": "bytes32" }, { "name": "nonce", "type": "bytes32" }, { "name": "expiresAt", "type": "uint256" } ] }, "primaryType": "SweepExecutionAuthorization", "message": { "partnerId": "partner-example", "intentSessionId": "intent_00000000000000000001", "attemptId": "attempt_00000000000000000001", "wallet": "0x1111111111111111111111111111111111111111", "recipient": "0x1111111111111111111111111111111111111111", "executionCommitment": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "nonce": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "expiresAt": "1893542400" } } }

data values are opaque calldata. Submit the returned bytes exactly; never construct them from documentation.

Chain actions

type ChainAction = { chainId: number; submitMethod: 'sendTransaction' | 'wallet_sendCalls'; atomicRequired: boolean; calls: ChainCall[] | null; // wallet_sendCalls transactions: ChainTransaction[] | null; // sendTransaction, ordered transactionCount: number; quoteCount: number; };

Execution rules — order, network switching, immutability — are in Execution rules.

Wallet authorization

walletAuthorization is an EIP-712 typed-data object bound to the partner, session, attempt, wallet, recipient, execution commitment, nonce and expiry.

  • Sign the returned domain, types, primaryType and message exactly. With ethers or viem, pass them to the typed-data signer unchanged.
  • With raw eth_signTypedData_v4, add the standard EIP712Domain declaration mechanically from the fields the returned domain actually carries — currently name and version only. Extra fields (chainId, verifyingContract) produce a signature Sweep rejects.
  • Submit the resulting 65-byte ECDSA signature with execution evidence. ERC-1271 contract-wallet signatures are not accepted in v1.
  • Without the authorization signature, evidence may be accepted but the attempt cannot progress to settled.

Gate execution on status

Retrying an ambiguous prepare failure with the same idempotency key does not create a second attempt: Sweep replays the stored attempt with its current status and the same chainActions.

status on the responseMeaningIntegrator behavior
preparedNothing has been broadcast for this attemptExecute chainActions
wallet_submitted, provider_pendingEvidence already existsDo not execute; resume status polling
settled, failed, expiredAttempt is terminalDo not execute; read the outcome from status

Re-running chainActions on a replayed attempt spends the user’s funds a second time. Sweep cannot undo a second broadcast.

Deadlines

FieldMeaning
expiresAt (top-level)Short attempt freshness window (ISO 8601); start wallet signing before it passes
walletAuthorization.message.expiresAtValidity of the authorization itself, Unix seconds, much longer default; sign as returned, never rebuild locally
evidenceDeadlineAtLatest time for initial evidence to be accepted

Once a wallet has broadcast an action, preserve and report the evidence even if the freshness window closes while the transaction is pending.

Code

import crypto from 'node:crypto'; // Browser: read capabilities immediately before prepare. let walletCapabilities = {}; try { walletCapabilities = await provider.request({ method: 'wallet_getCapabilities', params: [address], }); } catch { // Unsupported: Sweep returns sequential sendTransaction actions. } // Backend: prepare with its own idempotency key. const idempotencyKey = `prepare-${crypto.randomUUID()}`; const result = await client.prepare( intentSessionId, { walletContext: { connectorName, connectorId, walletCapabilities } }, idempotencyKey ); const attempt = result.response; if (attempt.status !== 'prepared') { // Replayed attempt: do NOT execute; reconcile via GET /status. }

Signing and execution helpers: Reference code. Failure codes: Errors.

Last updated on