---
title: "Create a withdrawal"
method: POST
path: "/v1/withdrawals"
tags: ["Withdrawals"]
---

# Create a withdrawal

`POST /v1/withdrawals`

## Source of Funds — Rebalancer Contract Pool Balance

Withdrawals move tokens **from a pool held by the user's rebalancer contract**
back to the user's wallet. The funds are NOT in the user's wallet — they are
already inside the DeFi protocol (Aave, Morpho, or Euler) deposited by the
rebalancer.

**The `chain`, `token`, `poolContract`, and available `amount` must all be
derived from `GET /v1/balances/{walletAddress}`** — do not guess or hardcode
these values.

## How to build a withdrawal request from the balances response

1. Call `GET /v1/balances/{walletAddress}` to get the rebalancer's holdings.
2. Navigate to: `perToken[n].perChain[m].perProtocol[p].perPool[q]`
3. From the pool object:
   - `chain` field → use as `chain` in the withdrawal request
   - `poolAddress` field → use as `poolContract` in the withdrawal request
   - `balanceRaw` field → maximum withdrawable amount (raw units)
   - `withdrawRequest` field → pre-filled EIP-712 domain, types, and value for signing
4. The `withdrawRequest.value` contains `token` (token contract address) and
   `poolContract` (pool address) — use these as the signing value.
5. Supply `amount` (≤ `balanceRaw`, ≥ minimum) and `deadline` (unix timestamp,
   max 1 minute in future).
6. Sign the EIP-712 typed data using the domain/types/value from `withdrawRequest`.
7. Submit this endpoint with all fields.

## Field-by-field mapping from balances response to WithdrawalRequest

| WithdrawalRequest field | Source                                                                 |
|-------------------------|------------------------------------------------------------------------|
| `chain`                 | `PoolBalance.chain` (string, e.g. "BASE")                            |
| `token`                 | Parent token key/symbol (e.g. "USDC") — NOT `withdrawRequest.value.token` which is a contract address |
| `walletAddress`         | The user's wallet address (same as used in JWT)                        |
| `poolContract`          | `PoolBalance.poolAddress`                                              |
| `amount`                | User-chosen raw amount, ≤ `PoolBalance.balanceRaw`, ≥ 100000 for USDC |
| `deadline`              | User-chosen unix timestamp, max 60 seconds from now                    |
| `signature`             | EIP-712 sign of `withdrawRequest.domain` + `withdrawRequest.types` + `{token: withdrawRequest.value.token, poolContract: withdrawRequest.value.poolContract, amount: <your_amount>, deadline: <your_deadline>}` |

Initiates a withdrawal transaction from DeFi protocols.

Initiates a withdrawal transaction from DeFi protocols.

Withdrawal must be signed as per **EIP-712 Typed structured data hashing and signing**. The signature must be created by wallet.

Signature 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.

`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.

`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.

`Signature` must be a `0x`-prefixed hex string signed by the wallet address associated with the **JWT token**. For a standard EOA wallet this is 132 characters (0x + 130 hex characters). **Smart contract wallets are also supported** (e.g. multisig, passkey wallets) via EIP-1271 (`isValidSignature`) verification, so the signature may be longer than 132 characters in that case — up to a maximum of 4096 hex characters.

**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).

## Request body

- WithdrawalRequest
  - `chain` 'ETHEREUM' | 'BASE' | 'POLYGON' | 'ARBITRUM' | 'OPTIMISM' | 'AVALANCHE' | 'LINEA', required — The blockchain network where the user's funds are located. Must match the chain returned in GET /v1/balances/{walletAddress}. The chain where the rebalancer holds funds in the specified pool. Must match the `chain` field of the pool from GET /v1/balances/{walletAddress} perToken.perChain.perProtocol.perPool. Do NOT use the user's wallet chain — this is the chain the rebalancer deposited into, which may be different. This value must be taken directly from PoolBalance.chain — do NOT convert from wagmi chain names. Use withdrawRequest.domain.chainId (integer) to switch the wallet chain before signing.
  - `token` 'USDC' | 'MUSD' | 'USDT' | 'RLUSD' | 'USDG' | 'USDE' | 'PYUSD', required — Token symbol (uppercase). Use the symbol (e.g. "USDC"), NOT the contract address. Do NOT use `withdrawRequest.value.token` here — that is the ERC-20 contract address used only for EIP-712 signing. This field must be the human-readable symbol from the parent token level of GET /v1/balances/{walletAddress}.
  - `walletAddress` string, required — User's Ethereum wallet address (must be checksummed per EIP-55)
  - `amount` string, required — Amount in token's smallest unit (minimum 0.1 to cover gas fees)
  - `deadline` string, required — Unix timestamp deadline for withdrawal (max 1 minute in future)
  - `signature` string, required — EIP-712 signature for withdrawal authorization. 130 hex characters (0x + 130) for a standard EOA wallet; smart contract wallets (EIP-1271, e.g. multisig, passkey wallets) may produce longer signatures, up to 4096 hex characters.
  - `poolContract` string, required — Pool contract address to withdraw from (must be checksummed per EIP-55)

## Response `201`

Withdrawal initiated successfully

- WithdrawalResponse
  - `trackingId` string, uuid, required — Tracking ID (UUID) returned from withdrawal creation

## Other responses

- `400` — Validation error
- `401` — No authorization header provided
- `403` — User is not authorized to perform this action
- `409` — Withdrawal not allowed
- `429` — Too many requests for this endpoint. Limits are enforced per client IP for the public API routes backed by `RebalancerResource` (and related resources using the same filter). Response headers (when throttled): - `Retry-After`: seconds to wait before retrying (matches the rate-limit window duration for that endpoint). - `X-RateLimit-Limit`: maximum requests allowed in the window (e.g. `1`). - `X-RateLimit-Remaining`: remaining requests in the window (`0` when throttled). - `X-RateLimit-Reset`: Unix timestamp (seconds) when the limit window resets. `internalCode` in the JSON body is `THROTTLE_PER_IP` for IP-scoped limits (other values may apply for different scopes in the backend).
- `500` — Internal server error

---

[API](https://skmtc.net/metalend/apis/metalend-rebalancing-api.md) · [All operations](https://skmtc.net/metalend/apis/metalend-rebalancing-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/metalend/metalend-rebalancing-api/versions/1b9900a9e91e/schema)
