Execute API
Create an address
Create an executable address for an intent.
https://api.execute.cash/v1 /address/createAPI key or client IDThe address is a pure function of the body: the same intent always gives the same address, so retrying is always safe and needs no idempotency key. Every call with the same body answers 200 with the same response.
Request body
Every field is required, and no other is accepted.
tokenaddress- The ERC-20 your user sends. It must be one of the chain’s tokens in List chains.
Example
"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" amountstring · base-10 integer- Exactly what the calls spend, in the token’s base units:
"1000000000"is 1,000 USDC. Anything sent above it goes torecoverybefore the calls run. Greater than zero.Example
"1000000000" callsarray of calls · at least one- What the address does once it holds the amount. The calls run in order, as the executable address, in one transaction; if one fails, none happens.
targetaddress- The contract called. It must be a contract by the time the address executes: a call to an address without code fails.
Example
"0xbeef0e0834849aCC03f0089F01f4F1Eeb06873C9" functionstring- The function’s signature, with its parameters’ names, in the canonical form. The API encodes the calldata from it.
Example
"deposit(uint256 assets, address receiver)" argsarray- The function’s arguments, in order, each in its type’s JSON form.
Example
["1000000000", "0xc80d2a01247aD9e11D1Ab45B5AcE404B416aF086"]
expirationTimestampinteger · unix seconds- 1 to 5 minutes from now. If the calls haven’t run by then, the address sends its whole balance to
recoveryand runs nothing.Example
1790671800 recoveryaddress- Receives anything that can’t be spent: an excess, the whole balance after expiry, and anything sent late or on another chain. Usually your user.
Example
"0xc80d2a01247aD9e11D1Ab45B5AcE404B416aF086" saltstring · 32 bytes, lowercase hex- Makes the address unique. The same intent with the same salt gives the same address, so pick it once per payment: random, or derived from your own order id.
Example
"0x4b7ef6dd50ef7570851a0abfbf121fa35edfcdd5f83d07e6bc275547447ec004" chainIdinteger- The chain the address settles on. One of List chains.
Example
8453
Values
Every value has exactly one JSON form, the same for top-level fields and call arguments:
| ABI type | JSON | Accepted | Refused |
|---|---|---|---|
address | string | "0xc80d2a01247aD9e11D1Ab45B5AcE404B416aF086", or all lowercase | a bad checksum, ENS names, no 0x |
uint<N>, int<N> | base-10 string | "1000000000", "0", "-5" | JSON numbers, hex, "1e9", "1000.0", "+1", leading zeros |
bool | boolean | true | "true", 1 |
bytes<N>, bytes | lowercase hex string | "0x095ea7b3", "0x" | uppercase, odd length, the wrong size |
string | string | "hello" | |
T[], T[k], tuples | array | [["0xc80d…F086", "5"]] | objects |
- Integers are strings because JSON numbers lose precision past 253. Base 10 is what
bigint.toString()writes. chainIdandexpirationTimestampare JSON integers: small, and held as numbers by every library.- Addresses may be all lowercase or EIP-55 checksummed; any other casing must be a valid checksum. Responses are checksummed.
Functions
A call names its function by its signature, with every parameter named: name(type name, …), one space after each comma. Tuples list their named components in parentheses.
transfer(address to, uint256 amount)
deposit(uint256 assets, address receiver)
supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode)
fill((address maker, uint256 amount)[] orders, bytes32 id)There’s no function keyword, visibility, mutability or returns, and types are canonical (uint256, never uint). It’s the form viem, ethers and alloy print a function in. Anything else is refused, and the error gives the canonical string. Raw calldata isn’t accepted, so a wallet can always show every call in words.
Response
addressaddress- The executable address, EIP-55 checksummed.
factoryaddress- The
PaymentFactorythat deploys it, and whose formula derives it: always0x6D85B9706D8f076cB8A9fEA37d70Fcb8D22C2952in v1. intentobject- Your intent in canonical form. If you sent canonical values, it is your request as you sent it, except that addresses come back checksummed.
termsstring · hexabi.encode(token, amount, calls, expirationTimestamp, recovery, salt, chainId), with each call’s calldata encoded: exactly the arguments ofPaymentFactory.paymentAddressandexecute.
Example
A deposit of 1,000 USDC into Morpho’s Steakhouse Prime USDC vault on Base, with the vault’s shares going to the user:
{
"token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amount": "1000000000",
"calls": [
{
"target": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"function": "approve(address spender, uint256 amount)",
"args": [
"0xbeef0e0834849aCC03f0089F01f4F1Eeb06873C9",
"1000000000"
]
},
{
"target": "0xbeef0e0834849aCC03f0089F01f4F1Eeb06873C9",
"function": "deposit(uint256 assets, address receiver)",
"args": [
"1000000000",
"0xc80d2a01247aD9e11D1Ab45B5AcE404B416aF086"
]
}
],
"expirationTimestamp": 1790671800,
"recovery": "0xc80d2a01247aD9e11D1Ab45B5AcE404B416aF086",
"salt": "0x4b7ef6dd50ef7570851a0abfbf121fa35edfcdd5f83d07e6bc275547447ec004",
"chainId": 8453
}{
"address": "0x3a9012C8bbDA720CE9C94458f8753965dFB6455a",
"factory": "0x6D85B9706D8f076cB8A9fEA37d70Fcb8D22C2952",
"intent": {
"token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amount": "1000000000",
"calls": [
{
"target": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"function": "approve(address spender, uint256 amount)",
"args": [
"0xbeef0e0834849aCC03f0089F01f4F1Eeb06873C9",
"1000000000"
]
},
{
"target": "0xbeef0e0834849aCC03f0089F01f4F1Eeb06873C9",
"function": "deposit(uint256 assets, address receiver)",
"args": [
"1000000000",
"0xc80d2a01247aD9e11D1Ab45B5AcE404B416aF086"
]
}
],
"expirationTimestamp": 1790671800,
"recovery": "0xc80d2a01247aD9e11D1Ab45B5AcE404B416aF086",
"salt": "0x4b7ef6dd50ef7570851a0abfbf121fa35edfcdd5f83d07e6bc275547447ec004",
"chainId": 8453
},
"terms": "0x000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913…"
}Errors
An invalid intent is refused with 400 invalid_intent, and every problem is reported at once, each at a JSON Pointer into your body:
{
"error": {
"code": "invalid_intent",
"message": "3 fields are invalid",
"issues": [
{
"path": "/amount",
"message": "expected a base-10 integer string with no sign, decimal point or leading zeros; you sent hex, which is \"1000000000\" in base 10"
},
{
"path": "/calls/1/function",
"message": "write it exactly as \"deposit(uint256 assets, address receiver)\" (no \"function\" keyword, modifiers or returns; one space after each comma)"
},
{
"path": "/expirationTimestamp",
"message": "looks like milliseconds; in seconds that is 1790671800"
}
]
}
}| Path | Refused when |
|---|---|
/<field> | A field is missing ("required"), or isn’t one of the seven ("unknown field", with the one you probably meant). |
/token | Not an address, or not one of the chain’s tokens. The message lists the tokens the chain supports. |
/amount | Not a base-10 integer string (hex is converted for you in the message), or zero. |
/calls | Not an array, empty, or too large to deploy: the init code is over EIP-3860’s 49,152 bytes. |
/calls/<i>/target | Not an address, or the zero address. |
/calls/<i>/function | Not in canonical form. The message gives the canonical string to send. |
/calls/<i>/args | Not an array, or the wrong number of values for the function. |
/calls/<i>/args/<k> | A value not in its type’s JSON form. |
/expirationTimestamp | Not a JSON integer, in the past, under 1 minute or over 5 minutes away. Milliseconds are spotted, and converted in the message. |
/recovery | Not an address, the zero address, the token itself, or one of Execute’s own contracts. |
/salt | Not exactly 32 bytes of lowercase hex. |
/chainId | Not a JSON integer, or a chain Execute isn’t live on. The message lists the live chains. |
The request can also be refused for its credentials, or with:
409 address_conflict: the same calldata was already registered with different function signatures. The message names the registered ones.401,403and429: see Authentication.