# DeFiLoops > The execution layer for multi-step DeFi. An agent signs in with a wallet, finds the operation it needs in the catalogue, and calls the /v1 gateway. This guide is generated from the product's catalogue and route table. The contract is the OpenAPI document, not this file. ## Start here - [OpenAPI](https://api.defiloops.com/v1/openapi.json): every /v1 route and the error shape - [Catalogue](https://api.defiloops.com/v1/catalog): every operation, its chains, and whether an agent can call it - [Plan schema](https://defiloops.com/schemas/plan-v1.schema.json): the JSON Schema of a plan document (in the repository: deploy/schemas/plan-v1.schema.json) - [A2A agent card](https://app.defiloops.com/.well-known/agent-card.json): the same operations as skills - Hire pages: each template agent has a public page at https://app.defiloops.com/hire/ (readable without JavaScript), the same facts as text at https://app.defiloops.com/hire//llms.txt, and a prompt you can give your own agent to validate them at https://app.defiloops.com/hire//validate.txt ## Sign in Sign-In-With-X for EVM wallets (CAIP-122, with the EIP-4361 message). 1. POST /v1/auth/challenge with {address, chainId}, and optionally an ERC-8004 agentId. The answer is a message that expires in five minutes and can be used once. 2. Sign it with the wallet. Smart-contract wallets work (ERC-1271, ERC-6492). 3. POST /v1/auth/verify with {message, signature}. The answer is an opaque agent token that lasts an hour. 4. Send it on every other call as `Authorization: Bearer `. The token is not a console credential and the console's credentials are not accepted on /v1. The wallet is the tenant. An agentId, if given, must be one the wallet owns or is the agent wallet of. ## Routes Base URL: https://api.defiloops.com. Every non-2xx answer has one shape: {"error": {"code", "message", "retryable"}}. - `GET /v1/openapi.json` (no token): every /v1 route, as OpenAPI 3: the contract - `GET /v1/catalog` (no token): every catalogue operation, its chains and whether an agent can call it; with q (job words) and/or chain it searches the callable ones, ranked by measured quality - `POST /v1/auth/challenge` (no token): sign-in step 1: send {address, chainId, agentId?}, get a single-use SIWE message to sign - `POST /v1/auth/verify` (no token): sign-in step 2: send {message, signature} (EOA, ERC-1271 or ERC-6492), get a token - `GET /v1/me` (token): who the token is: tenant, wallet, chain, agent and expiry - `GET /v1/credits` (token): your credits: spendable, held by runs, total, and bought-but-not-yet-minted; poll it after paying POST /v1/credits - `GET /v1/accounts` (token): your wallet's account on its sign-in chain, and whether it is deployed - `POST /v1/accounts` (token, x402): create that account (pays for the deployment; closed until priced) - `GET /v1/accounts/{account}/deposit` (token): where to send money for one of your accounts - `GET /v1/accounts/{account}/balances` (token): the account's balances - `GET /v1/accounts/{account}/positions` (token): the account's lending positions - `GET /v1/grants` (token): the hire grants that name you; an owner records them on the console - `POST /v1/session-certs` (token): register a session certificate: let a session key act for one purpose, on one chain, for at most 24 hours - `GET /v1/session-certs` (token): your session certificates and their status - `PUT /v1/webhook` (token): send {url}: the one https URL your runs' completion is posted to; the answer carries its signing secret, once - `GET /v1/webhook` (token): your webhook URL (never its secret) - `DELETE /v1/webhook` (token): remove your webhook ## Payments x402 is the only way to pay. A paid route answers 402 with the accepted payment in the PAYMENT-REQUIRED header; pay, and send the request again with the payment and a payment-identifier, which is the idempotency key: the work is never done twice for one identifier. A refused request is not charged. A paid route that has no price yet answers like an unknown path. ## Run webhooks When a run ends, its webhook (if you registered one) receives one POST: a JSON event {"type": "run.ended", "id", "runId", "quoteId", "status", "cursor", "events", "endedAt"}. Delivery is at least once and retried for a few hours; dedupe on the id, which is the same on every retry. The events cursor is the record: GET the run's events with after= to reconcile. To verify a delivery, before parsing it: 1. Read the raw body bytes and the headers `DefiLoops-Webhook-Timestamp` (unix seconds) and `DefiLoops-Webhook-Signature`. 2. Refuse a timestamp more than 300 seconds from your clock. 3. The key is your secret (`whsec_…`, from PUT /v1/webhook) without its prefix, base64url-decoded (no padding). 4. Compute HMAC-SHA256(key, timestamp + "." + raw body), hex-encode it, and compare it in constant time with the `v1=` value of the signature header. 5. `DefiLoops-Webhook-Id` is the event id. The URL must be https and resolve only to public addresses, and redirects are not followed. A new PUT replaces the URL and the secret. ## Operations The catalogue operations an agent can call (65). Each is a step in a plan document; GET /v1/catalog?q= finds one by what you want done. - `transfer.native` (ethereum, base, arbitrum): Move the chain's native token by transaction value. - `transfer.erc20` (ethereum, base, arbitrum): Move an ERC-20 token. - `deposit.pull` (ethereum, base, arbitrum): Draw tokens the OWNER has approved into the account. The source is the owner's own wallet and is read from the account, so there is nothing to point it at; the amount is bounded by the allowance they granted and by the account's pull cap, which is separate from its spend cap: a pull does not count against the spend cap. Use it as the FIRST step when the account is empty and the owner's wallet is not. - `bridge.cctp` (ethereum, base, arbitrum): Move USDC to another chain through CCTP Fast. Settles OUT OF BAND: minutes, and the destination step must not start before the source step is final. What lands is LESS than what was sent, by at most 100 bps. - `bridge.across` (ethereum, base, arbitrum): Move an asset to another chain through Across. Settles OUT OF BAND: minutes, and the destination step must not start before the source step is final. What lands is LESS than what was sent, by at most 100 bps. - `swap.router` (ethereum, base, arbitrum): Swap through the first-party aggregating router. The route is computed off-chain and is therefore UNTRUSTED: the protection is the minimum output, not inspection of the route. - `cow.presign` (ethereum, base, arbitrum): ONLY WHEN SOMEBODY NAMES CoW BY NAME. Everything else that rests at a price is place_order on a saved plan, which re-arms itself after a short fill and which a later step can be written to follow. This is an outside venue settling out of band: nothing downstream can spend what it delivers and it cannot re-arm, so choosing it unasked substitutes a venue nobody picked. - `cow.cancel` (ethereum, base, arbitrum): Withdraw a CoW Protocol order this account pre-signed and has not filled: clears the on-chain pre-signature and the standing allowance to CoW's vault relayer, so nothing more can be filled. Moves no token. Name the order exactly as it was placed — the same sell, buy, buy_min, valid_to, partial and amount — because the adapter rebuilds the order hash from them and refuses one it never pre-signed. Works after valid_to too, which is how a lapsed order's allowance is cleared. - `loan.shift` (ethereum, base, arbitrum): MOVE A LEVERAGED POSITION. Sells the collateral held and buys the destination market's, keeping the loan -- or sells NOTHING when both ends take the SAME collateral token, which is how a position changes protocol without changing its pair. BOTH ENDS ARE NAMED BY CURATED MARKET, so it reaches only pairs that are listed. Moves a leveraged position from one market to another in ONE transaction, paid for by a flash loan the account never holds. Set mode to what the position should BE — neutral means the same asset is lent and borrowed, short means a true reversal — and it is refused if the destination market disagrees. Set repay_all to clear the debt EXACTLY: a fixed amount leaves the interest accrued since planning, and dust debt blocks the withdraw. Amount is the COLLATERAL to move, because that is what leaves the account. - `loan.close` (ethereum, base, arbitrum): CLOSE IT AND TAKE THE MONEY OUT. Ends the position: clear the debt with borrowed money, sell the collateral, repay the loan, keep the difference. THIS IS HOW AN ORDER FILLS WHEN THE ASSET TO SELL IS COLLATERAL — max_slippage_bps is the floor on that sale, so a triggered close with a floor IS a limit order on locked collateral. Set flash_market when the debt asset cannot itself be flash-borrowed. Partial: repay and sell less than all of it. - `leverage.aave_v3` (ethereum, base, arbitrum): LEVER AN AAVE v3 POSITION UP in one flash-loan transaction: flash-borrow the loan asset, swap it into collateral, supply that (plus any principal you add), borrow against it and repay the flash from the borrow — the account never holds the flash. The market names both tokens. The owner's LTV ceiling is the guard on the result and is not a field here; shrinking a position is deleverage.aave_v3, taking it all to cash is loan.close. - `leverage.aave_v4` (ethereum): Lever an AAVE v4 position up, the same shape as leverage.aave_v3 but on a v4 spoke. ETHEREUM ONLY: Aave v4 is on no other chain this system reaches. `market` is the curated PAIR ("weth-usdc"), never a spoke name: the spoke is the one that lists both tokens (weth-usdc lives in main, xaut-usdt in gold). - `leverage.morpho` (ethereum, base, arbitrum): Lever a MORPHO Blue position up, the same shape as leverage.aave_v3. Here Morpho is both the flash lender and where the position lives. - `deleverage.aave_v3` (ethereum, base, arbitrum): LEVER AN AAVE v3 POSITION DOWN in one flash-loan transaction: flash-borrow the loan asset, repay part of the debt, withdraw the freed collateral, sell it back into the loan asset and repay the flash — the position stays OPEN, just smaller. Ordinary use is PARTIAL: pass collateral_amount and the debt to repay; repay_all clears it entirely. Taking the whole position to cash is loan.close, not this. - `deleverage.aave_v4` (ethereum): Lever an AAVE v4 position down, the same shape as deleverage.aave_v3 but on a v4 spoke, which maxes the repay out by construction. ETHEREUM ONLY. `market` is the same curated PAIR the lever-up named, never a spoke name. - `deleverage.morpho` (ethereum, base, arbitrum): Lever a MORPHO Blue position down, the same shape as deleverage.aave_v3. - `property.buy` (base): Buy 0xequity property tokens. The venue prices the trade from its own oracle, so the amount is a TOKEN COUNT and what a plan bounds is the currency side. - `property.sell` (base): Sell 0xequity property tokens. Requires the ACCOUNT to hold them, which it does only once enrolled — the router pulls from msg.sender. - `rent.claim_own` (base): Collect rent on property the ACCOUNT itself holds. Needs the account enrolled on 0xequity and holding property; otherwise it harvests nothing and succeeds. Mints a lock NFT rather than paying out. - `rent.redeem` (base): TURN A RENT LOCK INTO MONEY. Redeems a matured lock NFT and delivers USDC to this account -- NOT a rent token, and nothing needs selling afterwards. A later step may spend the output directly: `{"Symbolic": ".output"}` with asset USDC is how rent repays a loan. The amount is the lock NFT id, not a quantity -- rent.claim_own mints one rather than paying out, and this is what turns it into tokens. - `rent.harvest_delegated` (base): Drive 0xequity's rent automation for the OWNER's holding, when the owner delegated through RentDelegatee and created rent positions. Harvests, redeems and splits across those positions. DELIVERS THIS ACCOUNT NOTHING — the rent lands in the owner's positions and the un-delegated remainder in the owner's wallet — so nothing may spend its output. Pair it with rent.claim_position, which collects this account's share. Needs the owner's one global delegatee slot pointed at RentDelegatee, which the run checks before it commits. - `rent.claim_position` (base): Collect this account's share of a rent position's harvested rent, paid in USDC and spendable by a later step exactly as rent.redeem's output is. The amount is the POSITION token id, not a quantity — the same shape as rent.redeem's lock NFT id. The recipient is not chosen here: the forwarder pays whoever the position's owner nominated, and the adapter refuses a position that pays neither this account nor its owner. - `lend.supply` (ethereum, base, arbitrum): Supply an asset as collateral. - `lend.borrow` (ethereum, base, arbitrum): Borrow against supplied collateral. - `lend.repay` (ethereum, base, arbitrum): Repay borrowed principal to Aave. Repays an EXACT amount: interest accrues every block, so "all" is a number nobody signed. - `lend.withdraw` (ethereum, base, arbitrum): Take supplied collateral back out of Aave. Carries a loan-to-value cap because removing collateral raises the ratio on whatever debt remains. - `morpho.supply` (ethereum, base, arbitrum): Put up the COLLATERAL of a curated Morpho market. `asset` must be that market's collateral token — wsteth-usdc takes wstETH, not USDC — and a market is named -. It earns NO YIELD: collateral in Morpho backs a borrow and accrues nothing. Lending the loan token to earn the supply rate is a different call this system does not make, so a Morpho supply APY is not something a plan can take. - `morpho.borrow` (ethereum, base, arbitrum): Borrow the loan asset of a curated Morpho market. - `morpho.repay` (ethereum, base, arbitrum): Repay borrowed principal to a curated Morpho market. An amount at or above the debt clears ALL of it — repaid by shares, so no dust is left to block withdrawing the collateral — and what the repay did not need stays in the account. To close a position, size it a little over the debt (interest accrues until the step runs); below the debt it repays exactly the amount. - `aavev4.supply` (ethereum): Supply collateral into an Aave v4 spoke. THREE calls: approve, supply, and setUsingAsCollateral — v4 treats a supply as a deposit until it is explicitly marked as collateral. The Gold Spoke holds NATIVE XAUt, which no other venue here does. - `aavev4.borrow` (ethereum): Borrow from an Aave v4 spoke. The collateral backing it must have been enabled — aavev4.supply does that as part of its step. - `aavev4.repay` (ethereum): Repay borrowed principal to an Aave v4 spoke. - `aavev4.withdraw` (ethereum): Withdraw collateral from an Aave v4 spoke. - `morpho.withdraw` (ethereum, base, arbitrum): Withdraw collateral from a curated Morpho market. - `uni.mint` (ethereum, base, arbitrum): Mint a Uniswap V3 LP NFT. The recipient is the Kernel account, never a signed field. token0 and token1 are symbols; ticks are the position's range. - `uni.mint_single` (ethereum, base, arbitrum): Mint a Uni V3 LP from ONE token. Ticks are computed on-chain from the current tick; one of amount0/amount1 must be zero. - `uni.increase` (ethereum, base, arbitrum): Add liquidity to an existing Uni V3 NFT the account already owns. - `uni.decrease` (ethereum, base, arbitrum): Remove a fraction of a Uni V3 position's liquidity. Proceeds land on the account. - `uni.collect` (ethereum, base, arbitrum): Collect fees from a Uni V3 NFT onto the Kernel account. - `uni.withdraw` (ethereum, base, arbitrum): Burn a Uni V3 position fully and optionally swap the proceeds through the aggregation router at a signed minOut. - `uni.rebalance` (ethereum, base, arbitrum): Burn a Uni V3 NFT and mint a new one with signed ticks. Leftover tokens stay on the account. - `uni.split` (ethereum, base, arbitrum): Split one Uni V3 NFT into two new NFTs on the account, meeting at the tick you name. MAJOR BUMP FROM v1: `tick` is required and there is no default. v1 always cut at the geometric midpoint, so a range spanning an ODD number of spacings could not be split at all -- the midpoint landed between two ticks and the position manager refused it with EMPTY RETURNDATA and nothing to read. A position left by uni.reposition is one spacing wide and can never be split, because no tick lies strictly inside it. - `uni.reposition` (ethereum, base, arbitrum): Move a Uni V3 position to a one-spacing range around the current tick. - `uni.close_at_tick` (ethereum, base, arbitrum): Fully decrease a Uni V3 position if the current tick is inside a signed window. - `revert.create_borrow` (ethereum, base, arbitrum): Move a Uni V3 NFT into the Revert vault and borrow USDC onto the Kernel account. - `revert.mint_create_borrow` (ethereum, base, arbitrum): Mint a Uni V3 NFT, deposit it into Revert, and borrow USDC in one intent. - `revert.borrow` (ethereum, base, arbitrum): Borrow more USDC against an existing Revert position. Proceeds land on the account. - `revert.repay` (ethereum, base, arbitrum): Repay a Revert USDC borrow. Cap is charged on USDC; leftover is refunded like Aave repay. - `revert.remove` (ethereum, base, arbitrum): Return a Uni V3 NFT from the Revert vault to the Kernel account. - `revert.repay_withdraw` (ethereum, base, arbitrum): Repay Revert, remove the NFT, and burn the LP onto the Kernel account. - `revert.collect_fees` (ethereum, base, arbitrum): Collect Uni fees on a Revert-levered position through the vault's own door: no flash, the loan stays open. - `revert.add_liquidity` (ethereum, base, arbitrum): Increase liquidity on a Revert-levered Uni NFT inside a Morpho flash. - `revert.remove_liquidity` (ethereum, base, arbitrum): Decrease a Revert-levered Uni position through the vault's own door: no flash, the vault checks the loan stays healthy. - `revert.split` (ethereum, base, arbitrum): Split a Revert-levered Uni NFT into two new levered NFTs inside a flash, meeting at the tick you name. MAJOR BUMP FROM v1 for the same reason as uni.split: v1 always cut at the geometric midpoint, so an odd-span range could not be split at all. - `revert.rebalance` (ethereum, base, arbitrum): Rebalance a Revert-levered Uni position to signed ticks inside a Morpho flash. - `revert.close_levered` (ethereum, base, arbitrum): Close a Revert-levered Uni position: repay, burn LP, keep proceeds on the account. - `revert.add_leverage` (ethereum, base, arbitrum): Increase leverage on a Revert Uni position: add the flash-loaned asset's side as liquidity, funded by a larger Revert borrow, plus the other side from the account. - `revert.open_levered` (ethereum, base, arbitrum): Mint a Uni V3 NFT and open a Revert borrow against it, funded by a Morpho flash. - `revert.reposition` (ethereum, base, arbitrum): Reposition a Revert-levered Uni NFT to new ticks; same flash path as rebalance. - `revert.exit_to_aave` (ethereum, base, arbitrum): Move a Revert-levered Uni position onto Aave v3: repay vault, burn LP, supply both, borrow the flash. - `revert.move_from_aave` (ethereum, base, arbitrum): Move Aave collateral into a Revert-levered Uni NFT, funded by a Morpho flash. - `revert.add_from_aave` (ethereum, base, arbitrum): Pull Aave collateral into an existing Revert Uni NFT inside a Morpho flash. - `revert.exit_to_aave_long` (ethereum, base, arbitrum): Close a Revert Uni position into an Aave LONG: supply both tokens, borrow USDC, sell WETH if needed. - `revert.exit_to_aave_short` (ethereum, base, arbitrum): Close a Revert Uni position into an Aave SHORT: supply USDC, borrow WETH, sell WETH for USDC. - `revert.exit_to_aave_one` (ethereum, base, arbitrum): Close a Revert Uni position into Aave with a single collateral, swapping the other side first. ## Optional - [Catalogue as OpenAPI](https://api.defiloops.com/mcp/openapi.json): the tool catalogue - [Swap API](https://swap.defiloops.com/openapi.json): quotes and unsigned swap transactions