# 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 page for people at https://app.defiloops.com/hire/, an agent view with every fact at https://app.defiloops.com/hire//agent (readable without JavaScript), the same facts as markdown 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. Each agent view is listed under Template agents below. ## 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 ## MCP An MCP server at https://mcp.defiloops.com/mcp (Streamable HTTP, JSON-RPC over POST). Its default tools are the /v1 reads above and none of them can move funds. Without a token it answers the public reads: the catalogue and its search, the plan schema, the templates and their search, and agent listings and credentials. A tool about your own accounts or quotes answers 401 until you sign in: OAuth 2.1 with PKCE, where your wallet signs a SIWX message; the 401's WWW-Authenticate header names the metadata to start from. A second scope, mcp:chat, is offered only when the operator has switched it on, and only to a wallet that holds its own key. A token with it must be bound to a DPoP key, and it adds the chat_* tools, which use that wallet's console chat as you. One of them, chat_confirm, is the console's Start press and can start a run that moves funds. It runs only the action named by the actionId and actionHash that chat_status showed, and it refuses with action_changed if the conversation or plan changed since. A token without mcp:chat never sees these tools. Calls are rate limited per address, and a 429 carries Retry-After. ## 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 (69). 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. - `weth.wrap` (ethereum, base, arbitrum): Wrap native ETH into WETH, one for one, at WETH's own contract. The amount is native ETH and counts against the ETH cap; nothing moves out. The WETH it delivers can feed later steps. This and transfer.native are the only steps that spend native ETH: in every asset parameter "ETH" still means WETH. - `weth.unwrap` (ethereum, base, arbitrum): Unwrap WETH back into native ETH, one for one, at WETH's own contract. The amount is WETH and counts against the WETH cap, and can be what an earlier step delivered in WETH; nothing moves out. The ETH it delivers cannot feed a later step: native value is not passed between steps. - `lighter.deposit` (ethereum): Move USDC from this account on Ethereum into its own Lighter account (perps or spot). The amount is USDC in its raw 6-decimal units, at least 1 USDC, and counts against the owner's USDC cap. Credited on Lighter after Lighter includes the deposit. - `lighter.claim` (ethereum): Make sure this account's pending Lighter USDC is paid home to the account on Ethereum. The amount is the most to claim, in raw 6-decimal units; the step pays what is still pending up to it, and does nothing (without failing) when someone already paid it. It feeds no later step: a step after it spends a literal amount. ## Template agents Each one's agent view: what it does and may touch, every contract involved, every fee and how a run's price is computed, its ERC-8004 registration and x402 service, how the money is protected, and its public record. People hire it from the page the link names. - [repay-and-withdraw](https://app.defiloops.com/hire/repay-and-withdraw/agent): agent view; the page for people is https://app.defiloops.com/hire/repay-and-withdraw - [supply-usdc-aave](https://app.defiloops.com/hire/supply-usdc-aave/agent): agent view; the page for people is https://app.defiloops.com/hire/supply-usdc-aave - [swap-then-supply](https://app.defiloops.com/hire/swap-then-supply/agent): agent view; the page for people is https://app.defiloops.com/hire/swap-then-supply - [swap-usdc](https://app.defiloops.com/hire/swap-usdc/agent): agent view; the page for people is https://app.defiloops.com/hire/swap-usdc - [withdraw-aave](https://app.defiloops.com/hire/withdraw-aave/agent): agent view; the page for people is https://app.defiloops.com/hire/withdraw-aave ## 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