Execution rules
Rules the integrator’s frontend and backend must follow around the four API calls. Working code for every rule is in Reference code.
Wallet requirements
- v1 requires a 65-byte EOA or EIP-7702 authorization signature. ERC-1271 and
ERC-4337 contract-wallet signatures are rejected
(
400 contract_wallet_unsupported). - Do not classify every non-empty
eth_getCoderesult as an unsupported contract wallet: EIP-7702 accounts expose delegation code and are supported. Sweep’s server-side validation is authoritative. - Keep the raw EIP-1193 provider available.
wallet_getCapabilities,wallet_sendCallsandwallet_getCallsStatusare wallet RPC methods; a library signer alone is not enough. - Discover injected providers with EIP-6963 rather than assuming a single
window.ethereum. - Subscribe to
accountsChanged(invalidate the current intent),chainChanged(update UI only) anddisconnect(stop execution, preserve already-broadcast evidence). - Read
wallet_getCapabilitiesimmediately beforeprepareand pass the result unchanged. On failure pass{}; never infer atomic support from the connector name.
Source amounts
- Token identity is the canonical pair
(chainId, address), neversymbol. Use the zero address for a native asset. - Convert display amounts to raw units without floating-point arithmetic.
- Never send the full native balance as a source: the source must not spend the gas needed to execute itself.
- Re-read selected balances immediately before creating the quote.
Executing chain actions
Each chainAction belongs to one source chain. Process actions in returned
order. Before every action:
- confirm the connected account still equals the prepared wallet;
- switch the wallet to
chainAction.chainId(wallet_switchEthereumChain; on error4902,wallet_addEthereumChainwith trusted metadata); - verify the resulting active chain;
- verify sufficient native gas;
- preserve the exact prepared call order.
wallet_sendCalls: submit calls[] through the wallet’s EIP-5792 method and
report the returned bundle id immediately — do not wait for receipt hashes.
sendTransaction: submit every entry in transactions[] in order. Approval
transactions are part of the immutable plan. Wait for an earlier transaction
when the next depends on it. Collect hashes in order and preserve
already-broadcast successes if a later transaction fails.
Never:
- change
to,dataorvalue; - skip an approval because local allowance state appears different;
- merge actions from different chains;
- fabricate call-to-hash mappings;
- silently retry a user rejection.
Persist before side effects
Persist state before:
- sending every mutating Sweep request (
operation,idempotencyKey, exact body); - starting wallet execution;
- reporting a bundle id or transaction hash;
- acknowledging a webhook.
A wallet broadcast can succeed while the browser loses connectivity or the backend response is lost. Design recovery around already-broadcast value, not around page state.
Minimal durable attempt record:
type SweepAttemptRecord = {
walletAddress: string;
recipient: string;
intentSessionId: string;
attemptId?: string;
quoteExpiresAt: string;
signableExpiresAt?: string;
evidenceDeadlineAt?: string;
authorizationSignature?: string;
chainResults: Array<{
chainId: number;
bundleId?: string;
txHashes: string[];
}>;
pendingRequests: Array<{
operation: 'quote' | 'prepare' | 'submitted';
idempotencyKey: string;
exactBody: string;
acknowledged: boolean;
}>;
};Recovery
| Situation | Required behavior |
|---|---|
| Request timed out before a response | Retry the same body with the same idempotency key |
| Quote expired before prepare | Create a new quote and key |
| Prepare signable window expired before broadcast | Start again from a new quote |
| Wallet broadcast succeeded but reporting failed | Preserve evidence and retry /submitted |
| New receipt hashes appeared | Send additive evidence with a new key |
| User rejected a wallet prompt | Stop that action; preserve earlier chain successes |
| One source chain failed after another succeeded | Never discard the successful chain evidence |
| Connected account changed | Stop; never use a new account to complete another recipient’s attempt |
On application startup:
- restore pending attempt records;
- retry unacknowledged backend requests with their stored bytes and keys;
- resume
wallet_getCallsStatusfor known bundle ids; - query
GET /statusand treat execution as already done unless the status proves otherwise; - continue polling non-terminal attempts.
Time windows
| Field | Meaning |
|---|---|
Quote expiresAt | Latest time to call prepare for that quote session |
Prepare expiresAt | Short attempt freshness window; start wallet signing before it passes |
walletAuthorization.message.expiresAt | Validity of the EIP-712 authorization, Unix seconds, much longer default |
evidenceDeadlineAt | Latest time for initial evidence when none has been accepted; additive evidence after acceptance is unbounded by it |
Observability
Log identifiers, not secrets or signed authorization payloads:
partnerId, intentSessionId, attemptId, idempotencyKey,
walletAddress, chainId, submitMethod, bundleId, txHash,
status transition, failedSources reasonCodeTrack latency separately for quote, prepare, wallet confirmation, evidence acceptance and settlement. A slow wallet prompt is not an API latency problem.