Execute API

Create an address

Create an executable address for an intent.

POSThttps://api.execute.cash/v1/address/createAPI key or client ID

The 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 to recovery before 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 recovery and runs nothing.

Example1790671800

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.

Example8453

Values

Every value has exactly one JSON form, the same for top-level fields and call arguments:

ABI typeJSONAcceptedRefused
addressstring"0xc80d2a01247aD9e11D1Ab45B5AcE404B416aF086", or all lowercasea 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
boolbooleantrue"true", 1
bytes<N>, byteslowercase hex string"0x095ea7b3", "0x"uppercase, odd length, the wrong size
stringstring"hello"
T[], T[k], tuplesarray[["0xc80d…F086", "5"]]objects
  • Integers are strings because JSON numbers lose precision past 253. Base 10 is what bigint.toString() writes.
  • chainId and expirationTimestamp are 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.

Canonical signatures
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 PaymentFactory that deploys it, and whose formula derives it: always 0x6D85B9706D8f076cB8A9fEA37d70Fcb8D22C2952 in 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 · hex
abi.encode(token, amount, calls, expirationTimestamp, recovery, salt, chainId), with each call’s calldata encoded: exactly the arguments of PaymentFactory.paymentAddress and execute.

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:

Request body
{
  "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
}
200 OK
{
  "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:

400 Bad Request
{
  "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"
      }
    ]
  }
}
PathRefused when
/<field>A field is missing ("required"), or isn’t one of the seven ("unknown field", with the one you probably meant).
/tokenNot an address, or not one of the chain’s tokens. The message lists the tokens the chain supports.
/amountNot a base-10 integer string (hex is converted for you in the message), or zero.
/callsNot an array, empty, or too large to deploy: the init code is over EIP-3860’s 49,152 bytes.
/calls/<i>/targetNot an address, or the zero address.
/calls/<i>/functionNot in canonical form. The message gives the canonical string to send.
/calls/<i>/argsNot an array, or the wrong number of values for the function.
/calls/<i>/args/<k>A value not in its type’s JSON form.
/expirationTimestampNot a JSON integer, in the past, under 1 minute or over 5 minutes away. Milliseconds are spotted, and converted in the message.
/recoveryNot an address, the zero address, the token itself, or one of Execute’s own contracts.
/saltNot exactly 32 bytes of lowercase hex.
/chainIdNot 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, 403 and 429: see Authentication.