MetaLend Rebalancing API

API for managing cross-protocol, cross-chain DeFi rebalancing operations: SIWE/JWT auth, project settings, per-token user rebalancer configuration, deposits (gasless EIP-3009 or approval-based), signed EIP-712 withdrawals, wallet balances, rewards, pool listings with APY breakdowns, and per-chain transaction costs. All endpoints require X-API-Key + Origin.

OpenAPI Specification

metalend-rebalancing-openapi.json Raw ↑
{"openapi":"3.0.3","info":{"title":"MetaLend Rebalancing API","version":"0.1.0","description":"API for managing cross-protocol cross-chain DeFi rebalancing operations exposing: <ul>\n  <li><b>Auth</b> – challenge/verify flow for JWT issuance.</li>\n  <li><b>Project settings</b> – fetch current project settings.</li>\n  <li><b>User configuration</b> – register or update token-level user-rebalancer settings and fetch current wallet configs.</li>\n  <li><b>Deposits</b> – create deposit intents, receive tracking IDs, and poll status through PROCESSING -> BRIDGING (cross-chain only) -> SUCCESS/FAILED/EMERGENCY_WITHDRAW. New deposits may return <b>503</b> with <b>DEPOSITS_PAUSED</b> during maintenance.</li>\n  <li><b>Withdrawals</b> – submit signed withdrawal requests, receive tracking IDs, and monitor status PROCESSING -> SUCCESS/FAILED.</li>\n  <li><b>Balances</b> – query wallet-level balances and withdraw details, as well as balances in bridge progress.</li>\n  <li><b>Rewards</b> – aggregated rewards and claim details per wallet.</li>\n  <li><b>Pools</b> – list supported protocols (Aave, Morpho, Euler, etc.), their pools, APY breakdowns, and TVL snapshots.</li>\n  <li><b>Transaction Costs</b> – current deposit/withdraw on-behalf transaction fees and minimum amounts by chain.</li>\n  <li><b>AI Agent Guide</b> – Markdown flow guide at <code>/SKILL.md</code> with signing formats, wizard requirements, and known integration gotchas.</li>\n</ul> All endpoints require an <b>X-API-Key</b>. <b>JWT authentication</b> is required on <b>deposit, withdrawal, and config update</b> operations.\n<b>Obtain API key by <a href=\"https://developer.metalend.tech/\" target=\"_blank\" rel=\"noopener noreferrer\">creating a new project</a>.</b>\nAll requests must include the Origin header. Use Origin: https://app.metalend.tech unless you have been assigned a different allowed origin.","contact":{"name":"MetaLend API Support","email":"support@metalend.tech"},"termsOfService":"https://metalend-inc.gitbook.io/litepaper/terms-of-service","x-guidance":"AI agents MUST read https://api.metalend.tech/SKILL.md before implementing any MetaLend operation. It contains critical signing formats, required headers, wizard flows for config setup, and known failure modes.","x-default-api-key":"a7f2b8c4d9e1f3a6b5c8d2e7f1a4b9c3d6e8f2a5b7c1d4e9f3a6b8c2d5e7f1a4","x-api-key-guidance":"Use x-default-api-key as X-API-Key for agent testing/integration. If you are the project owner, create or use your own project API key from https://developer.metalend.tech instead."},"externalDocs":{"description":"Litepaper","url":"https://metalend-inc.gitbook.io/litepaper"},"servers":[{"url":"https://api.metalend.tech","description":"Production server"}],"security":[{"ApiKeyAuth":[],"OriginHeader":[]}],"paths":{"/SKILL.md":{"get":{"summary":"Get the AI agent flow guide","operationId":"getSkillMd","description":"Returns the MetaLend AI Agent Flow Guide — a comprehensive reference for AI agents covering authentication flows, signing formats, config wizard, deposit/withdrawal patterns, and known failure modes. **AI agents must fetch this document before implementing any MetaLend operation.\n\nThe guide is returned as `text/markdown` and is rate limited to 20 requests per 60 seconds per IP.\n","tags":["AI Agent Guide"],"security":[],"responses":{"200":{"description":"Markdown guide for AI agents integrating with the MetaLend Rebalancing API.","content":{"text/markdown":{"schema":{"type":"string","description":"Markdown contents of the MetaLend AI Agent Flow Guide."},"examples":{"guide":{"summary":"MetaLend AI Agent Flow Guide","value":"# MetaLend API — AI Agent Flow Guide\n\n> **Config Wizard Notice:** When a user wants to configure their rebalancer (set pools/tokens), you MUST run an interactive wizard before signing.\n\nThis document captures the exact flows, signing formats, and gotchas for an AI agent operating the MetaLend Rebalancing API (`http://api.metalend.tech`).\n"}}}}},"429":{"$ref":"#/components/responses/RateLimitExceeded"},"500":{"$ref":"#/components/responses/InternalError"}},"x-agentcash-auth":{"mode":"free"}}},"/v1/deposits":{"post":{"summary":"Create a deposit","description":"Initiates a deposit transaction to move funds into an optimal DeFi protocol.<br><br>\nUser owned **rebalancer contract** must be pre-approved to spend tokens. The address of this contract is returned by the `GET /v1/config/{walletAddress}` endpoint. This contract gets deployed during first deposit and its address is pre-computed using CREATE2.<br><br>\nBoth **signature-based (gasless)** and **approval-based** approvals are supported.<br><br>\nActual deposit transaction is executed by an operator on behalf of the user after validating the request and JWT token.\n\n## Required Fields (All Deposits)\n\nThese fields are **always required** regardless of deposit method:\n- `token`: The symbol of the deposit token (e.g., \"USDC\", \"USDT\", \"MUSD\")\n- `walletAddress`: The user address making the deposit (must match JWT token). Currently only EOAs are supported (no smart contract wallets).\n- `amount`: The amount to deposit in token's raw unit (e.g., for USDC with 6 decimals, \"1000000\" = 1 USDC). Token details can be found in the `GET /v1/pools` endpoint. Minimum deposit amount is 0.1 of the token to cover gas fees for on behalf transaction.\n\n## Deposit Methods\n\n### Signature-Based Deposits (EIP-3009)\nSupported for **USDC** and **MUSD** tokens. This method enables gasless approval where \nthe user signs an authorization permit off-chain, and MetaLend executes the transfer on-chain.\n\n**Additional required fields:**\n- `signature`: The user's signature authorizing the transfer\n- `validAfter`: Unix timestamp indicating when the signature becomes valid\n- `validBefore`: Unix timestamp indicating when the signature expires. This must be greater than `validAfter` and within a short time window (max 1 minute in future).\n- `nonce`: A unique 32-byte hex string to prevent replay attacks\n- `tokenName`: The official name of the token (e.g., \"USD Coin\"), can be read on token contract (ERC20 standard).\n- `tokenVersion`: The token contract version, can be read on token contract (ERC20 standard).\n\nSign `ReceiveWithAuthorization` EIP-712 typed data structure as defined by the token contract.\n\n### Approval-Based Deposits\nSupported for **USDT, USDC, MUSD**. This method requires the user \nto approve the rebalancer contract before initiating the deposit by sending a transaction.\n\n**Additional requirements:**\n- Standard ERC-20 approval via the token contract before calling this endpoint\n- Only the base required fields (chain, token, walletAddress, amount) are allowed in the request\n- Do NOT include signature, timestamp, or nonce fields for approval-based deposits\n\n## Authentication & Security\n\n- Requires JWT authentication via JWT token in the Authorization header\n- The JWT must contain the same wallet address as specified in the request body\n\n## Maintenance — deposits paused\n\nMetaLend can temporarily pause **new** deposit creation while other API features (withdrawals, balances, config, pools, etc.) stay available.\n\nWhen paused, this endpoint returns **`503 Service Unavailable`** with `internalCode` **`DEPOSITS_PAUSED`** and does not create a deposit or return a tracking ID.\n\n## Processing Flow\n\n1. Check whether deposits are paused\n2. JWT verification to ensure the caller owns the specified wallet\n3. Request validation (chain, token, amount, address, signature/approval requirements)\n4. Deposit request is queued for processing with a unique tracking ID\n5. Returns immediately tracking ID\n6. Use `GET /v1/deposits/{trackingId}` to monitor the deposit progress\n\n## Response\n\nReturns a **tracking ID** that can be used to poll the deposit status. The tracking ID is:\n- A randomly generated UUID\n\nThe tracking ID can be used with the status endpoint \nto check if the deposit was successful, failed, is still processing or is bridging.\n\n## Source of Funds — User's Own Wallet ERC20 Balance\n\nDeposits move tokens **from the user's own wallet** (their personal ERC20 balance \non-chain) into a DeFi protocol via the MetaLend rebalancer contract.\n\n**The `chain` field must be the chain where the user currently holds the ERC20 token \nbalance in their wallet.** For example, if the user has USDC on Ethereum, \n`chain` must be `\"ETHEREUM\"`. If they have USDC on Base, `chain` must be `\"BASE\"`.\n\nBefore calling this endpoint:\n1. Check the user's ERC20 balance: `token.balanceOf(userWalletAddress)` on the \n  target chain — this is what they are depositing from.\n2. For **approval-based** deposits: call `token.approve(rebalancerAddress, amount)` \n  on the same chain before calling this endpoint.\n3. For **signature-based** deposits (USDC/MUSD only): sign the `ReceiveWithAuthorization` \n  permit off-chain — no on-chain approval transaction needed.\n\nThe rebalancer contract address to approve is returned by \n`GET /v1/config/{walletAddress}` in the `rebalancerAddress` field.\n\n## Notes\n- Smart contract wallets that do not comply with standard signature length are currently not supported\n- For signature-based deposits, ensure timestamps are within valid range\n\n**Rate limiting**: 1 request per 6 seconds per client IP. Excess requests receive HTTP `429` with `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers (see `RateLimitExceeded` response).\n","operationId":"createDeposit","tags":["Deposits"],"security":[{"ApiKeyAuth":[],"JwtAuth":[],"OriginHeader":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DepositRequest"},"examples":{"signatureBasedUsdcLinea":{"summary":"Signature-based deposit — USDC on Linea","value":{"chain":"LINEA","token":"USDC","walletAddress":"0x1234567890123456789012345678901234567890","amount":"1000000","validAfter":"1700000000","validBefore":"1700000100","nonce":"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef","signature":"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef01","tokenName":"USD Coin","tokenVersion":"2"}},"signatureBasedUsdcEth":{"summary":"Signature-based deposit — USDC on Ethereum","value":{"chain":"ETHEREUM","token":"USDC","walletAddress":"0x1234567890123456789012345678901234567890","amount":"5000000","validAfter":"1700000000","validBefore":"1700000100","nonce":"0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890","signature":"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef01","tokenName":"USD Coin","tokenVersion":"2"}},"approvalBasedUsdtEthereum":{"summary":"Approval-based deposit — USDT on Ethereum","value":{"chain":"ETHEREUM","token":"USDT","walletAddress":"0x1234567890123456789012345678901234567890","amount":"1000000"}},"approvalBasedUsdcBase":{"summary":"Approval-based deposit — USDC on Base","value":{"chain":"BASE","token":"USDC","walletAddress":"0x1234567890123456789012345678901234567890","amount":"2000000"}},"approvalBasedUsdcArbitrum":{"summary":"Approval-based deposit — USDC on Arbitrum","value":{"chain":"ARBITRUM","token":"USDC","walletAddress":"0x1234567890123456789012345678901234567890","amount":"3000000"}}}}}},"responses":{"201":{"description":"Deposit initiated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DepositResponse"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"walletAddressBlank":{"$ref":"#/components/examples/WalletAddressBlank"},"invalidAddressFormat":{"$ref":"#/components/examples/InvalidAddressFormat"},"invalidAddressCheckSum":{"$ref":"#/components/examples/InvalidAddressCheckSum"},"signatureNotAllowed":{"$ref":"#/components/examples/SignatureNotAllowed"},"invalidTokenNameOrVersion":{"$ref":"#/components/examples/InvalidTokenNameOrVersion"},"partialSignatureFields":{"$ref":"#/components/examples/PartialSignatureFields"},"tokenNotSupported":{"$ref":"#/components/examples/TokenNotSupported"},"chainBlank":{"$ref":"#/components/examples/ChainBlank"},"chainNotSupported":{"$ref":"#/components/examples/ChainNotSupported"},"amountTooLow":{"$ref":"#/components/examples/AmountTooLow"},"invalidTimestampBeforeGreaterThanAfter":{"$ref":"#/components/examples/InvalidTimestampBeforeGreaterThanAfter"},"invalidTimestampAfterMustBeInPastOrNow":{"$ref":"#/components/examples/InvalidTimestampAfterMustBeInPastOrNow"},"invalidTimestampBeforeMustBeInFuture":{"$ref":"#/components/examples/InvalidTimestampBeforeMustBeInFuture"},"invalidTimestampBeforeMustBeAtMostOneMinuteInFuture":{"$ref":"#/components/examples/InvalidTimestampBeforeMustBeAtMostOneMinuteInFuture"},"insufficientApproval":{"$ref":"#/components/examples/InsufficientApproval"},"tokenNotSupportedOnChain":{"$ref":"#/components/examples/TokenNotSupportedOnChain"},"insufficientBalance":{"$ref":"#/components/examples/InsufficientBalance"},"depositInvalidSignature":{"$ref":"#/components/examples/DepositInvalidSignature"}}}}},"401":{"$ref":"#/components/responses/UnauthorizedNoHeader"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimitExceeded"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"Service unavailable — deposits paused (maintenance)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"depositsPaused":{"$ref":"#/components/examples/DepositsPaused"}}}}}},"x-agentcash-auth":{"mode":"free"}}},"/v1/deposits/{trackingId}":{"get":{"summary":"Get deposit status","description":"Retrieves the current status and details of a deposit transaction using its unique tracking ID.\n\nThis endpoint allows you to monitor the progress of a deposit from initiation to completion. \nThe tracking ID is returned when you create a deposit in UUID format.\n\nPlease be aware that if you see failed state, it can be also due to high gas fees, max allowed is 1 USD in gas fees, otherwise it will fail.\n\n**Status Values** (returned in `status`):\n- `PROCESSING`: Deposit is queued or being handled on the source chain.\n- `BRIDGING` (cross-chain only): Source-side funds movement succeeded and is bridging to the destination chain. Same-chain deposits go straight to final `SUCCESS` or `FAILED` status.\n- `SUCCESS`: Deposit has been successfully completed and funds are in the protocol.\n- `FAILED`: Deposit transaction has failed (e.g., insufficient gas, transaction reverted).\n- `EMERGENCY_WITHDRAW`: Destination-side deposit could not complete; funds were recovered via emergency withdrawal.\n\nThe API only exposes the five values above. Any other internal persistence state is mapped to `PROCESSING` for this response until the flow resolves.\n\n**Response Details:**\nWhen successful, the response includes:\n- Current deposit status (\"SUCCESS\")\n- Target protocol name (e.g., \"Aave\", \"Morpho\", \"Euler\") where funds were deposited\n- Pool/vault contract address receiving the funds\n- Blockchain network where the deposit occurred\n\n**Use Cases:**\n- Poll this endpoint to track deposit progress after creation\n- Verify successful completion before proceeding with other operations\n- Display real-time status updates to users in your application\n\n**Note:** If you receive a error code, the tracking ID is probably invalid or no deposit exists for that ID.\n\n**Rate limiting**: 1 request per 3 seconds per client IP. Excess requests receive HTTP `429` with `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers (see `RateLimitExceeded` response).\n","operationId":"getDepositStatus","tags":["Deposits"],"security":[{"ApiKeyAuth":[],"OriginHeader":[]}],"parameters":[{"name":"trackingId","in":"path","required":true,"description":"Tracking ID (UUID) returned from deposit creation","schema":{"type":"string"},"example":"550e8400-e29b-41d4-a716-446655440000"}],"responses":{"200":{"description":"Deposit status retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DepositStatusResponse"},"examples":{"processing":{"summary":"Deposit picked up, waiting for on-chain execution","value":{"status":"PROCESSING","protocol":null,"pool":null,"chain":"base"}},"bridging":{"summary":"Cross-chain deposit — source tx done, bridge in progress","description":"Details of the target pool, protocol and chain.","value":{"status":"BRIDGING","protocol":"Aave","pool":"0xc47b8C00b0f69a36fa203Ffeac0334874574a8Ac","chain":"base"}},"success":{"summary":"Deposit completed successfully","value":{"status":"SUCCESS","protocol":"Aave","pool":"0xc47b8C00b0f69a36fa203Ffeac0334874574a8Ac","chain":"base"}},"failed":{"summary":"Deposit failed","value":{"status":"FAILED","protocol":null,"pool":null,"chain":"base"}},"emergencyWithdraw":{"summary":"Destination deposit failed — funds emergency-withdrawn","value":{"status":"EMERGENCY_WITHDRAW","protocol":"Aave","pool":"0xc47b8C00b0f69a36fa203Ffeac0334874574a8Ac","chain":"base"}}}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"missingTrackingId":{"$ref":"#/components/examples/MissingTrackingId"},"invalidTrackingId":{"$ref":"#/components/examples/InvalidTrackingId"}}}}},"401":{"$ref":"#/components/responses/UnauthorizedNoHeader"},"429":{"$ref":"#/components/responses/RateLimitExceeded"},"404":{"description":"Tracking ID not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"trackingIdNotFound":{"$ref":"#/components/examples/TrackingIdNotFound"}}}}},"500":{"$ref":"#/components/responses/InternalError"}},"x-agentcash-auth":{"mode":"free"}}},"/v1/withdrawals":{"post":{"summary":"Create a withdrawal","description":"\n## Source of Funds — Rebalancer Contract Pool Balance\n\nWithdrawals move tokens **from a pool held by the user's rebalancer contract**\nback to the user's wallet. The funds are NOT in the user's wallet — they are\nalready inside the DeFi protocol (Aave, Morpho, or Euler) deposited by the\nrebalancer.\n\n**The `chain`, `token`, `poolContract`, and available `amount` must all be\nderived from `GET /v1/balances/{walletAddress}`** — do not guess or hardcode\nthese values.\n\n## How to build a withdrawal request from the balances response\n\n1. Call `GET /v1/balances/{walletAddress}` to get the rebalancer's holdings.\n2. Navigate to: `perToken[n].perChain[m].perProtocol[p].perPool[q]`\n3. From the pool object:\n   - `chain` field → use as `chain` in the withdrawal request\n   - `poolAddress` field → use as `poolContract` in the withdrawal request\n   - `balanceRaw` field → maximum withdrawable amount (raw units)\n   - `withdrawRequest` field → pre-filled EIP-712 domain, types, and value for signing\n4. The `withdrawRequest.value` contains `token` (token contract address) and\n   `poolContract` (pool address) — use these as the signing value.\n5. Supply `amount` (≤ `balanceRaw`, ≥ minimum) and `deadline` (unix timestamp,\n   max 1 minute in future).\n6. Sign the EIP-712 typed data using the domain/types/value from `withdrawRequest`.\n7. Submit this endpoint with all fields.\n\n## Field-by-field mapping from balances response to WithdrawalRequest\n\n| WithdrawalRequest field | Source                                                                 |\n|-------------------------|------------------------------------------------------------------------|\n| `chain`                 | `PoolBalance.chain` (string, e.g. \"BASE\")                            |\n| `token`                 | Parent token key/symbol (e.g. \"USDC\") — NOT `withdrawRequest.value.token` which is a contract address |\n| `walletAddress`         | The user's wallet address (same as used in JWT)                        |\n| `poolContract`          | `PoolBalance.poolAddress`                                              |\n| `amount`                | User-chosen raw amount, ≤ `PoolBalance.balanceRaw`, ≥ 100000 for USDC |\n| `deadline`              | User-chosen unix timestamp, max 60 seconds from now                    |\n| `signature`             | EIP-712 sign of `withdrawRequest.domain` + `withdrawRequest.types` + `{token: withdrawRequest.value.token, poolContract: withdrawRequest.value.poolContract, amount: <your_amount>, deadline: <your_deadline>}` |\n\nInitiates a withdrawal transaction from DeFi protocols.\n\nInitiates a withdrawal transaction from DeFi protocols.\n\nWithdrawal must be signed as per **EIP-712 Typed structured data hashing and signing**. The signature must be created by wallet.\n\nSignature relevant information is retrieved by `/v1/balances/{walletAddress}` endpoint in the `withdrawRequest` field. EIP 712 domain parameters and types are provided for convenience. The `value` field contains the `token` (address) and `poolContract` (address) that must match the withdrawal request, while `amount` and `deadline` must be supplied by the user.\n\n`Deadline` must be a unix timestamp no more than 1 minute in the future which is the maximum amount of time the server waits for transaction confirmation.\n\n`Amount` must be at least 0.1 (stablecoin) of the token to cover on-behalf transaction gas fees and no more than the available balance in the specified pool. This value is specified in raw units (e.g., 6 decimals for USDC). Token decimals are included in the `token` object of `/v1/balances/{walletAddress}` endpoint for reference.\n\n`Signature` must be of length 132 (0x + 130 characters) and signed by the wallet address associated with the **JWT token**.\n\n**Rate limiting**: 1 request per 3 seconds per client IP. Excess requests receive HTTP `429` with `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers (see `RateLimitExceeded` response).\n","operationId":"createWithdrawal","tags":["Withdrawals"],"security":[{"ApiKeyAuth":[],"JwtAuth":[],"OriginHeader":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WithdrawalRequest"},"examples":{"withdrawUsdcLinea":{"summary":"Withdraw USDC from Aave on Linea","value":{"chain":"LINEA","token":"USDC","walletAddress":"0x1234567890123456789012345678901234567890","amount":"500000","deadline":"1700000060","signature":"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef01","poolContract":"0xc47b8C00b0f69a36fa203Ffeac0334874574a8Ac"}},"withdrawUsdcEthereum":{"summary":"Withdraw USDC from Morpho on Ethereum","value":{"chain":"ETHEREUM","token":"USDC","walletAddress":"0x1234567890123456789012345678901234567890","amount":"10000000","deadline":"1700000060","signature":"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef01","poolContract":"0xdd0f28e19c1780eb6396170735d45153d261490d"}},"withdrawUsdcBase":{"summary":"Withdraw USDC from Morpho on Base","value":{"chain":"BASE","token":"USDC","walletAddress":"0x1234567890123456789012345678901234567890","amount":"5000000","deadline":"1700000060","signature":"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef01","poolContract":"0xc1256ae5ff1cf2719d4937adb3bbccab2e00a2ca"}},"withdrawUsdtEthereum":{"summary":"Withdraw USDT from Morpho on Ethereum","value":{"chain":"ETHEREUM","token":"USDT","walletAddress":"0x1234567890123456789012345678901234567890","amount":"8000000","deadline":"1700000060","signature":"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef01","poolContract":"0xbEef047a543E45807105E51A8BBEFCc5950fcfBa"}}}}}},"responses":{"201":{"description":"Withdrawal initiated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WithdrawalResponse"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"walletAddressBlank":{"$ref":"#/components/examples/WalletAddressBlank"},"invalidAddressFormat":{"$ref":"#/components/examples/InvalidAddressFormat"},"invalidAddressCheckSum":{"$ref":"#/components/examples/InvalidAddressCheckSum"},"tokenNotSupported":{"$ref":"#/components/examples/TokenNotSupported"},"chainBlank":{"$ref":"#/components/examples/ChainBlank"},"chainNotSupported":{"$ref":"#/components/examples/ChainNotSupported"},"amountTooLow":{"$ref":"#/components/examples/AmountTooLow"},"deadlineMustBeInFuture":{"$ref":"#/components/examples/DeadlineMustBeInFuture"},"deadlineTooFarInFuture":{"$ref":"#/components/examples/DeadlineMaxOneMinuteInFuture"},"missingSignature":{"$ref":"#/components/examples/SignatureRequired"},"protocolNotFound":{"$ref":"#/components/examples/ProtocolNotFound"},"failedWithdrawSignatureValidation":{"$ref":"#/components/examples/FailedWithdrawSignatureValidation"},"insufficientBalance":{"$ref":"#/components/examples/InsufficientBalance"}}}}},"401":{"$ref":"#/components/responses/UnauthorizedNoHeader"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"Withdrawal not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"withdrawalNotAllowed":{"$ref":"#/components/examples/WithdrawalNotAllowed"}}}}},"429":{"$ref":"#/components/responses/RateLimitExceeded"},"500":{"$ref":"#/components/responses/InternalError"}},"x-agentcash-auth":{"mode":"free"}}},"/v1/withdrawals/{trackingId}":{"get":{"summary":"Get withdrawal status","description":"Retrieves the current status of a withdrawal transaction using its unique tracking ID.\n\n**Status Values:**\n- `PROCESSING`\n- `SUCCESS`\n- `FAILED`.\n\n**Rate limiting**: 1 request per 3 seconds per client IP. Excess requests receive HTTP `429` with `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers (see `RateLimitExceeded` response).\n","tags":["Withdrawals"],"security":[{"ApiKeyAuth":[],"OriginHeader":[]}],"parameters":[{"name":"trackingId","in":"path","required":true,"description":"Tracking ID (UUID) returned from withdrawal creation","schema":{"type":"string","format":"uuid"},"example":"550e8400-e29b-41d4-a716-446655440000"}],"responses":{"200":{"description":"Withdrawal status retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WithdrawalStatusResponse"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"missingWithdrawalTrackingId":{"$ref":"#/components/examples/MissingWithdrawalTrackingId"},"invalidWithdrawalTrackingId":{"$ref":"#/components/examples/InvalidWithdrawalTrackingId"}}}}},"401":{"$ref":"#/components/responses/UnauthorizedNoHeader"},"429":{"$ref":"#/components/responses/RateLimitExceeded"},"404":{"description":"Tracking ID not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"withdrawalTrackingIdNotFound":{"$ref":"#/components/examples/WithdrawalTrackingIdNotFound"}}}}},"500":{"$ref":"#/components/responses/InternalError"}},"x-agentcash-auth":{"mode":"free"}}},"/v1/withdrawals/version":{"get":{"summary":"Get withdrawal contract version","description":"Returns the current version of the rebalancer contracts used for withdrawal signing.\nThis value should be used as the `version` field in the EIP-712 domain when constructing withdrawal signatures.\n\n**Rate limiting**: 1 request per 6 seconds per client IP. Excess requests receive HTTP `429` with `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers (see `RateLimitExceeded` response).\n","operationId":"getWithdrawalVersion","tags":["Withdrawals"],"security":[{"ApiKeyAuth":[],"OriginHeader":[]}],"responses":{"200":{"description":"Withdrawal contract version retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetWithdrawalVersionResponse"},"example":{"version":"1"}}}},"401":{"$ref":"#/components/responses/UnauthorizedNoHeader"},"429":{"$ref":"#/components/responses/RateLimitExceeded"},"500":{"$ref":"#/components/responses/InternalError"}},"x-agentcash-auth":{"mode":"free"}}},"/v1/config":{"put":{"summary":"Update rebalancer configuration","description":"Creates or updates the rebalancing configuration for a specific wallet address and token. This endpoint allows users to set custom protocols and pools for automated rebalancing of their funds.\n\n**Authentication**: Requires a valid JWT token. The wallet address in the JWT must match the `walletAddress` field in the request body.\n\n**Configuration per Token**: Each request configures one specific token (USDC, MUSD, or USDT, RLUSD). To configure multiple tokens, make separate PUT requests for each token. After successfully setting a configuration for a token, the `hasSignedConfig` flag for that token will become `true` when retrieved via GET `/v1/config/{walletAddress}`.\n\n**Configuration Structure**: The configuration specifies arrays of domain IDs, protocol IDs, and pool contract addresses. All three arrays must have the same length, with each index representing one pool configuration:\n- `domainIds[0]`, `protocolIds[0]`, `poolAddresses[0]` = first pool\n- `domainIds[1]`, `protocolIds[1]`, `poolAddresses[1]` = second pool\n- And so on...\n\n**Domain IDs**: \n- `11` = LINEA\n- `6` = BASE\n- `7` = POLYGON\n- `3` = ARBITRUM\n- `1` = AVALANCHE\n- `2` = OPTIMISM\n- `0` = ETHEREUM\n\n**Protocol IDs**: Maps to supported protocols:\n- `0` = Aave\n- `1` = Morpho\n- `2` = Euler\n\n**Collateral Exposure**: Collateral exposure symbols for the pools in the configuration, must be taken from the pool catalog from GET /v1/pools, unique and not empty. If you want to disable the filter, pass null.\nexample: [\"WETH\", \"cbBTC\", \"wstETH\"]\n\nTvl and liquidity multiplier are optional and can be used to filter the pools in the configuration.\nExample:\n- requiredTvl: 5000000\n- requiredLiquidityMultiplier: 10\nThis will filter the pools in the configuration to only include pools that have a TVL of at least $5M and a liquidity multiplier of at least 10.\nExample:\n- requiredTvl: 0\n- requiredLiquidityMultiplier: 0\nThis will disable the filter and include all pools in the configuration.\nDuring deposits, pools that do not comply with filters (exposure, liquidity, tvl) are ignored. Active balances are also monitored periodically and if a pool with a deposit no longer complies with the filters, rebalance immediately happens outside to first available pool with highest APY %\n\n**Pool Contracts**: Each pool contract address must be a valid checksummed Ethereum address (EIP-55) corresponding to a pool on the specified protocol. Pool addresses can be obtained from the `/v1/pools` endpoint.\n\n**Signature Requirement**: The `signature` field must be created using the following steps:\n\n1. ABI-encode the following parameters in order:\n  - `address` — `rebalancingManagerAddress` (from `GET /v1/config/{walletAddress}`)\n  - `uint8[]` — `protocolIds` array\n  - `address[]` — `poolAddresses` array\n  - `uint32[]` — `domainIds` array\n  - `uint256` — `spendingCapRaw` (use `\"0\"` if not set, can be set only for USDC, when setting this value, AAVE protocol on LINEA must be included in the configuration with a non-zero spending cap, otherwise the request will be rejected)\n\n2. Compute `keccak256` of the ABI-encoded bytes.\n\n3. Sign the resulting hash using `personal_sign` (i.e. `eth_sign` with the standard Ethereum prefix `\\x19Ethereum Signed Message:\\n32`). In viem this is `walletClient.signMessage({ account, message: { raw: hash } })`.\n\nThe signature must be 132 characters long (0x + 130 hex characters) and must be signed by the wallet address in the `walletAddress` field.\n\n**Smart Contract Wallets / ERC-6492**: For contract wallet signatures, the backend validates the configuration signature on every chain contained within the `domainIds`. If the signature is rejected on any of those chains, the entire configuration is rejected. The error response will indicate which domain IDs were rejected: `\"Contract wallets configs can only contain chains verified by supported signature. Found rejected domain IDs: <rejectedIds>\"`.\n\n**Include Rewards APY**: The optional `includeRewardsApy` boolean flag (default: `true`) determines whether reward 

# --- truncated at 32 KB (120 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/metalend/refs/heads/main/openapi/metalend-rebalancing-openapi.json