Prepare
POST /api/external/intents/{intentSessionId}/prepareBuilds 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" } }
}
}
}| Field | Required | Meaning |
|---|---|---|
walletContext.connectorName | No | Human-readable wallet name |
walletContext.connectorId | No | Connector identifier |
walletContext.walletCapabilities | No | The 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,primaryTypeandmessageexactly. With ethers or viem, pass them to the typed-data signer unchanged. - With raw
eth_signTypedData_v4, add the standardEIP712Domaindeclaration mechanically from the fields the returneddomainactually carries — currentlynameandversiononly. 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 response | Meaning | Integrator behavior |
|---|---|---|
prepared | Nothing has been broadcast for this attempt | Execute chainActions |
wallet_submitted, provider_pending | Evidence already exists | Do not execute; resume status polling |
settled, failed, expired | Attempt is terminal | Do 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
| Field | Meaning |
|---|---|
expiresAt (top-level) | Short attempt freshness window (ISO 8601); start wallet signing before it passes |
walletAuthorization.message.expiresAt | Validity of the authorization itself, Unix seconds, much longer default; sign as returned, never rebuild locally |
evidenceDeadlineAt | Latest 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.