---
title: "Create a deposit"
method: POST
path: "/v1/deposits"
tags: ["Deposits"]
---

# Create a deposit

`POST /v1/deposits`

Initiates a deposit transaction to move funds into an optimal DeFi protocol.<br><br>
User 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>
Both **signature-based (gasless)** and **approval-based** approvals are supported.<br><br>
Actual deposit transaction is executed by an operator on behalf of the user after validating the request and JWT token.

## Required Fields (All Deposits)

These fields are **always required** regardless of deposit method:
- `token`: The symbol of the deposit token (e.g., "USDC", "USDT", "MUSD", "RLUSD", "USDG", "USDE", "PYUSD")
- `walletAddress`: The user address making the deposit (must match JWT token). Currently only EOAs are supported (no smart contract wallets).
- `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.

## Deposit Methods

### Signature-Based Deposits (EIP-3009)
Supported for **USDC**, **MUSD**, and **PYUSD** tokens. This method enables gasless approval where
the user signs an authorization permit off-chain, and MetaLend executes the transfer on-chain.

**Additional required fields:**
- `signature`: The user's signature authorizing the transfer
- `validAfter`: Unix timestamp indicating when the signature becomes valid
- `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).
- `nonce`: A unique 32-byte hex string to prevent replay attacks
- `tokenName`: The official name of the token (e.g., "USD Coin"), can be read on token contract (ERC20 standard).
- `tokenVersion`: The token contract version, can be read on token contract (ERC20 standard).

Sign `ReceiveWithAuthorization` EIP-712 typed data structure as defined by the token contract.

### Approval-Based Deposits
Supported for **all tokens** (USDT, USDC, MUSD, RLUSD, USDG, USDE, PYUSD). This method requires the user
to approve the rebalancer contract before initiating the deposit by sending a transaction.

**Additional requirements:**
- Standard ERC-20 approval via the token contract before calling this endpoint
- Only the base required fields (chain, token, walletAddress, amount) are allowed in the request
- Do NOT include signature, timestamp, or nonce fields for approval-based deposits

## Authentication & Security

- Requires JWT authentication via JWT token in the Authorization header
- The JWT must contain the same wallet address as specified in the request body

## Maintenance — deposits paused

MetaLend can temporarily pause **new** deposit creation while other API features (withdrawals, balances, config, pools, etc.) stay available.

When paused, this endpoint returns **`503 Service Unavailable`** with `internalCode` **`DEPOSITS_PAUSED`** and does not create a deposit or return a tracking ID.

## Processing Flow

1. Check whether deposits are paused
2. JWT verification to ensure the caller owns the specified wallet
3. Request validation (chain, token, amount, address, signature/approval requirements)
4. Deposit request is queued for processing with a unique tracking ID
5. Returns immediately tracking ID
6. Use `GET /v1/deposits/{trackingId}` to monitor the deposit progress

## Response

Returns a **tracking ID** that can be used to poll the deposit status. The tracking ID is:
- A randomly generated UUID

The tracking ID can be used with the status endpoint
to check if the deposit was successful, failed, is still processing or is bridging.

## Source of Funds — User's Own Wallet ERC20 Balance

Deposits move tokens **from the user's own wallet** (their personal ERC20 balance
on-chain) into a DeFi protocol via the MetaLend rebalancer contract.

**The `chain` field must be the chain where the user currently holds the ERC20 token
balance in their wallet.** For example, if the user has USDC on Ethereum,
`chain` must be `"ETHEREUM"`. If they have USDC on Base, `chain` must be `"BASE"`.

Before calling this endpoint:
1. Check the user's ERC20 balance: `token.balanceOf(userWalletAddress)` on the
  target chain — this is what they are depositing from.
2. For **approval-based** deposits: call `token.approve(rebalancerAddress, amount)`
  on the same chain before calling this endpoint.
3. For **signature-based** deposits (USDC/MUSD/PYUSD only): sign the `ReceiveWithAuthorization`
  permit off-chain — no on-chain approval transaction needed.

The rebalancer contract address to approve is returned by
`GET /v1/config/{walletAddress}` in the `rebalancerAddress` field.

## Notes
- Smart contract wallets that do not comply with standard signature length are currently not supported
- For signature-based deposits, ensure timestamps are within valid range

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

## Request body

- DepositRequest
  - `chain` 'ETHEREUM' | 'BASE' | 'POLYGON' | 'ARBITRUM' | 'OPTIMISM' | 'AVALANCHE' | 'LINEA', required — The blockchain network for this deposit. IMPORTANT: Do NOT derive this from wagmi's chain.name or chain.network — those use different naming conventions. Always use PoolBalance.chain from GET /v1/balances which is already in the correct API format. For chain switching before the on-chain approve() call, use PoolBalance.chainId (integer EVM chain ID) with switchChain({ chainId }). If you must map from wagmi/viem chainId to this field, use: - chainId 1 → "ETHEREUM" - chainId 8453 → "BASE" - chainId 137 → "POLYGON" - chainId 42161 → "ARBITRUM" - chainId 10 → "OPTIMISM" - chainId 43114 → "AVALANCHE" - chainId 59144 → "LINEA"
  - `token` 'USDC' | 'MUSD' | 'USDT' | 'RLUSD' | 'USDG' | 'USDE' | 'PYUSD', required — Token symbol to configure (must be uppercase)
  - `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)
  - `validAfter` string — Unix timestamp after which signature is valid (required for signature based approvals)
  - `validBefore` string — Unix timestamp before which signature is valid (required for signature based approvals, max 1 min in future, > validAfter)
  - `nonce` string — Unique nonce for signature (required for signature based approvals)
  - `signature` string — EIP-712 signature for authorization (required for signature based approvals)
  - `tokenName` string — Token name for EIP-712 domain (required for signature based approvals)
  - `tokenVersion` string — Token version for EIP-712 domain (required for signature based approvals)

## Response `201`

Deposit initiated successfully

- DepositResponse
  - `trackingId` string, required — Unique identifier to track deposit status

## Other responses

- `400` — Validation error
- `401` — No authorization header provided
- `403` — User is not authorized to perform this action
- `409` — Signature or nonce already used (replay)
- `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
- `503` — Service unavailable — deposits paused (maintenance)

---

[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/revisions/1b9900a9e91e/schema)
