Execute API
Quickstart
Create an executable address, check it, and watch it execute. The Execute API lives on api.execute.cash.
Create an API key
Get an API key from the dashboard. It’s secret: keep it on your server, and send it as a bearer token.
Creating addresses from a web page instead? Use a client ID.
Create an executable address
Call
POST /v1/address/createwith your intent. Every field is required, and nothing else is accepted.Field Type What it is tokenaddress The ERC-20 your user sends. One of the chain’s tokens. amountstring Exactly what the calls spend, in base units: "25000000" is 25 USDC. callsarray What the address does once it holds the amount: contracts, functions and arguments, run in order. expirationTimestampinteger Unix seconds, 1 to 5 minutes from now. After it, everything goes to recovery. recoveryaddress Receives anything that can’t be spent: excess, late payments, everything after expiry. Usually your user. saltstring 32 random bytes, as lowercase hex. Makes the address unique. chainIdinteger The chain the address lives on, e.g. 8453 for Base. This intent pays a merchant 25 USDC on Base, and refunds your user anything that can’t be spent:
Request MERCHANT=0x… # who gets paid CUSTOMER=0x… # who gets back anything that can't be spent curl https://api.execute.cash/v1/address/create \ -H "Authorization: Bearer $EXECUTE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "amount": "25000000", "calls": [ { "target": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "function": "transfer(address to, uint256 amount)", "args": ["'$MERCHANT'", "25000000"] } ], "expirationTimestamp": '$(( $(date +%s) + 300 ))', "recovery": "'$CUSTOMER'", "salt": "0x'$(openssl rand -hex 32)'", "chainId": 8453 }'Response { "address": "0x7dFDdbf4b14DB25b020fBF8679a2e688613a2f35", "factory": "0x6D85B9706D8f076cB8A9fEA37d70Fcb8D22C2952", "intent": { "token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "amount": "25000000", "calls": [ { "target": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "function": "transfer(address to, uint256 amount)", "args": [ "0x9F0E8F6D3B1a2c4E5d7F8091a2b3C4D5E6F70819", "25000000" ] } ], "expirationTimestamp": 1790671800, "recovery": "0xc80d2a01247aD9e11D1Ab45B5AcE404B416aF086", "salt": "0x2f1c5b0a9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a291807f6e5d4c3b2a19080", "chainId": 8453 }, "terms": "0x000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913…" }Every field, type and error is in Create an address.
Verify an executable address
POST /v1/address/createreturns the executable address for your intent. There are three ways to check that the address belongs to your intent and nothing else:- Open its page at
execute.cash/address/<address>. The page fetches the address’s intent and recomputes the address from it, in your browser. - Look it up with
GET /v1/address/<address>, which returns the intent Execute holds for the address, and compare it with yours. - Recompute it yourself. The address is a function of the intent, and
PaymentFactorycomputes it onchain. This asks the factory:
verify.ts import { createPublicClient, encodeFunctionData, http, isAddressEqual, parseAbi, parseAbiItem, type AbiFunction, type AbiParameter, type Address, type Hex } from "viem"; import { arbitrum, base, monad } from "viem/chains"; type Intent = { token: Address; amount: string; calls: { target: Address; function: string; args: unknown[] }[]; expirationTimestamp: number; recovery: Address; salt: Hex; chainId: number; }; const FACTORY = "0x6D85B9706D8f076cB8A9fEA37d70Fcb8D22C2952"; const CHAINS = { [base.id]: base, [arbitrum.id]: arbitrum, [monad.id]: monad }; const factoryAbi = parseAbi([ "function paymentAddress(address token, uint256 amount, (address target, bytes data)[] calls, uint64 expirationTimestamp, address recovery, bytes32 salt, uint256 chainId) view returns (address)", ]); /** An argument as viem takes it: the API sends integers as base-10 strings, viem wants bigints. */ function toArg(param: AbiParameter, value: unknown): unknown { const array = /^(.*)\[\d*\]$/.exec(param.type); if (array) return (value as unknown[]).map((v) => toArg({ ...param, type: array[1] }, v)); if (param.type === "tuple") { const { components } = param as { components: readonly AbiParameter[] }; return (value as unknown[]).map((v, i) => toArg(components[i], v)); } return /^u?int\d*$/.test(param.type) ? BigInt(value as string) : value; } /** Whether `address` is the executable address of `intent`, as PaymentFactory itself computes it. */ export async function verify(address: Address, intent: Intent): Promise<boolean> { const chain = CHAINS[intent.chainId as keyof typeof CHAINS]; const client = createPublicClient({ chain, transport: http() }); const calls = intent.calls.map((call) => { const fn = parseAbiItem(`function ${call.function}`) as AbiFunction; const args = fn.inputs.map((param, i) => toArg(param, call.args[i])); return { target: call.target, data: encodeFunctionData({ abi: [fn], args }) }; }); const derived = await client.readContract({ address: FACTORY, abi: factoryAbi, functionName: "paymentAddress", args: [intent.token, BigInt(intent.amount), calls, BigInt(intent.expirationTimestamp), intent.recovery, intent.salt, BigInt(intent.chainId)], }); return isAddressEqual(derived, address); }Or have your coding agent do it, and explain what the address will do:
- Open its page at
Pay it, and track its execution
The address’s page,
execute.cash/address/<address>, is a live monitor. Send it the tokens and watch the payment get detected and the intent executed and confirmed onchain.From your server, poll
GET /v1/address/<address>/status, or receive webhooks as it changes. Itsstateis one of:State What it means awaiting_fundsWaiting for the amount. balance is what has arrived so far. fundedIt holds the amount, and Execute is executing it. failingFunded, but a call reverts (a vault at capacity, say). Execute retries until expiry; message and revert say why. settledEvery call ran. transactionHash and blockNumber point to it. refundingPast expiry with a balance, which is on its way to recovery. refundedThe balance went to recovery. amount says how much. expiredExpired with nothing to return. Try it: Execute in an app
This is what integrating Execute gives your users. Enter your address, pay the executable address from any wallet, and watch it execute.
Live demo · Base
Pay yourself 0.000001 USDC through an executable address.
It’s a real address on Base whose one call sends the USDC back to the address you give here.