EVM steps - Aave supply
A worked, self-contained example of bridging a token in from any origin chain and supplying it to an Aave V3 pool on the destination EVM chain to earn yield.
Supplying is two steps in one execution:
approve— let the Aave Pool pull the bridged token from the intermediary.supply— move the token into the pool and mint back aTokens (the pool's interest-bearing receipt) to the intermediary.
The service then appends its own fee step that debits the intermediary in the destination token, so the amount you supply and the fee together must not exceed what the bridge actually delivers. This doc covers the amount recalculation flow — three rounds, where you compute that amount yourself and can show the user an exact figure before they sign.
Three rounds vs one
There are two ways to size the supply amount. They produce the same on-chain result; they differ in how many round trips you make and in who computes the number.
single-round ({MIN_AMOUNT_OUT})
three-round (this doc)
Create requests
1 (dry: false)
3 (dry: true, dry: true, dry: false)
Supply amount in your steps
the literal {MIN_AMOUNT_OUT} placeholder
a concrete uint256
Who carves the fee
the service, inside the same call
the service too, one round earlier — you copy the carved figure forward
Exact figure known before signing
no
yes
{MIN_AMOUNT_OUT} exists precisely to collapse this three-round protocol into one call, and it is the better default. Reach for the three-round flow when the UI must display the exact supplied amount and fee before the user signs, or when you want a fee preview you can abort on.
Why three rounds and not two
The number you need — the post-fee bridged amount — depends on the gas fee, and the gas fee depends on the steps, which depend on the number. The rounds break that cycle:
Round
dry
steps
What the service does
What you learn
1
true
[] (empty)
fetches a 1Click quote, skips gas estimation entirely
the gross quote.minAmountOut — enough to build realistic steps
2
true
built from round 1
estimates gas against your real steps, then carves the fee out of the quote
details.networkFee and the post-fee quote.minAmountOut
3
false
rebuilt from round 2
same as round 2, plus it creates the execution and prepares the signing payload
id, quote.depositAddress, details.payload
Round 1 has to be step-less: gas estimation only runs when steps is non-empty, so an empty array is what gets you a quote with no fee deducted. Round 1 must therefore be dry: true — a dry: false create without steps is rejected with steps are required for non-dry executions.
Addresses (Aave V3 on Base, USDC)
The example uses USDC on Base. A different chain or reserve changes the pool and token addresses but not the shape of the steps.
Aave V3 Pool (Base)
0xA238Dd80C259a72e81d7e4664a9801593F98d1c5
USDC (Base)
0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
aBasUSDC (supply receipt)
0x4e65fE4DbA92790696d040ac24Aa414708F5c0AB
Fee collector
per-deployment config — read it back from the appended fee step rather than hard-coding it
Only base, eth and arb are enabled as EVM destinations. Anything else is rejected with 400 blockchain <chain> is not supported as a destination before the rest of this flow applies. The pools on the other two:
eth
0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2
arb
0x794a61358D6845594F94dc1DB02A252b5b4814aD
The one address you supply yourself is the intermediary — the deterministic EVM account where the bridged tokens land, where the steps execute, and which receives the aTokens:
Fetch it once per wallet, before you build any steps. (For a TON origin, pass ?publicKey=ed25519:<base58> — a TON address does not expose its public key.)
The steps
Both amounts are USDC base units (6 decimals). onBehalfOf is the intermediary, so the aTokens are minted to the account that will later withdraw them. The referralCode is 0 unless Aave issued you one.
Round 1 — gross quote (dry: true, no steps)
Take quote.minAmountOut (94275) — the worst-case delivered amount, before the fee. Nothing is created and nothing is reserved by this call.
Round 2 — measured fee (dry: true, real steps)
Same envelope, dry still true, now with the steps built at 94275:
Two things to read out of this response:
details.networkFee— the gas fee, denominated in the destination token, that the appended step will debit.quote.minAmountOut— already94275 − 7150 = 87125. The response amounts are always post-fee once a fee was estimated, so you do not subtract again. This is the amount to supply.
If details.networkFee is absent, stop. It means gas estimation failed, so nothing was carved and quote.minAmountOut is still the gross figure. Copying it into round 3 bakes an amount the batch cannot cover once the fee is added. Retry the round instead.
The third step in the echo is the service's own fee transfer. You never send it yourself — it is appended to every response that carries a networkFee, dry or not, and it is part of the batch the user signs.
The arithmetic that has to hold at execution time:
Anything the bridge delivers above minAmountOut simply stays in the intermediary.
Round 3 — create (dry: false, steps rebuilt at the post-fee amount)
Identical body with the steps rebuilt at 87125 and dry: false:
Before signing, compare result.quote.minAmountOut with the amount you baked into the steps. They should be equal. If the returned value is lower, the quote moved between rounds 2 and 3 and the batch will not have enough token to cover supply + fee
A
dry: falsecreate takes an in-flight lock per (intermediary, chain). A second create before the first reaches a terminal state returns409withan execution for this wallet is already in progress. Wait for it to complete or fail before creating a new one. So you get one shot per attempt: to retry with different numbers, cancel the execution first withDELETE /api/v1/executions/{wallet}/{executionId}. That call is not a bare DELETE — it needs a body carrying the wallet's signature over the literal stringdelete_execution:{executionId}.
Sign and submit
result.details.payload is signed with the origin wallet, using result.details.signingStandard — your origin chain's standard, not the destination's. You never handle EVM calldata.
The body is { signature, executionId } for an EVM or Tron origin. An ed25519 origin (Solana, NEAR, Stellar) adds publicKey. A TON origin adds both publicKey and a tonConnect envelope. No x-api-key is needed here.
Signing before the deposit is deliberate: the service holds the pre-signed batch and fires it the moment the bridge settles.
Deposit and settle
Transfer to
result.quote.depositAddress:EXACT_INPUT— exactlyquote.amount(1270000lamports here).EXACT_OUTPUT—result.quote.amountIn, the origin commitment the service computed. Do not recompute it.A Stellar origin must also attach
quote.depositMemoor the bridge will not settle.
Record it so 1Click is notified without waiting for a watcher:
MEMO-mode origins (Stellar) must include
"memo"— the address alone does not identify the execution and the call answers404without it.Poll
GET /api/v1/executions/{wallet}?id={executionId}until terminal.resultis an array, so readresult[0]even when filtering by a single id:DEPOSIT_FAILEDandOPERATION_FAILEDare terminal.EXPIREDlooks terminal but is not always: a deposit that settles late can revive the execution straight toOPERATION_PROCESSING.
After SUCCESS the intermediary holds aBasUSDC representing the supplied USDC plus accrued interest, redeemable later through an out-operation execution.
EXACT_INPUT vs EXACT_OUTPUT
The steps are identical across swap types. What changes is what quote.amount means, how the fee is handled, and how much you deposit.
EXACT_INPUT
EXACT_OUTPUT
quote.amount denominated in
the origin token (what you send)
the destination token (what you want supplied)
What's fixed
the input you bridge
the amount that ends up supplied (≈ Y)
Fee handling
the service carves the fee out of the quote's output, so you supply delivered − fee
the service grosses the 1Click quote up by the fee first, so after the carve you still net ≈ Y
How much you deposit
quote.amount, verbatim
result.quote.amountIn from the create response
Round-2 quote.minAmountOut
gross output minus the fee
≈ Y
EXACT_INPUT — "I'm sending 0.00127 SOL. Supply whatever survives fees."
EXACT_OUTPUT — "I want 87125 USDC supplied. Charge me whatever that costs." Read
result.quote.amountInfrom round 3 and deposit that.
Note that on EXACT_OUTPUT the gross-up happens only once a fee exists, so round 1 (no steps, no fee) reports a lower amountIn than round 3 does. Use the round-3 value for the deposit.
Rules
x-api-keyis required onPOST /api/v1/executions/{wallet}— every round, dry or not. It fetches a 1Click quote and prices per-key fees server-side.POST …/submitandPOST …/deposit/submitdo not take it.dry: falserequires steps. An emptystepsarray is only valid on a dry round.Do not add the fee step yourself. The service appends it; a duplicate would double-charge the intermediary.
Step objects accept only
to,functionSignature,parameters,valueandmetadata. An unknown key, a differently cased spelling, or a duplicate is a400. Anything else you need to carry goes inmetadata, which is passed through untouched.quote.recipientis out-operation only. On a bridge-in the recipient is always the intermediary; sending it is a400.{MIN_AMOUNT_OUT}and the three-round flow are mutually exclusive in practice — the placeholder is what you use instead of computing the amount. Mixing them (a placeholder in one step, a literal in another) means the two steps operate on different amounts.
Errors you will actually hit
400 steps are required for non-dry executions
dry: false with an empty steps array.
400 estimated gas fee exceeds minimum output amount
the fee is larger than the quote's minAmountOut — the bridged amount is too small to pay for the batch. Send more.
400 estimated gas fee exceeds expected output amount
same, against amountOut.
400 invalid steps JSON: …
a malformed parameter, an out-of-range integer, a tuple arity mismatch, or a bad step key. The detail is appended, and per-step problems are prefixed step <i>:.
409 an execution for this wallet is already in progress. Wait for it to complete or fail before creating a new one
a previous dry: false create is still live. Wait for it, or delete it.
401 missing or invalid api token
absent or rejected x-api-key.
Last updated
