> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mobula.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Build Deposit Payload

> Build a signed canonical payload to deposit USDC collateral into a perpetual DEX account (Lighter) or bridge USDC into a Gains account.

<Note>**Lighter-only.** Gains has no deposit endpoint — collateral on Gains is sent directly with each `create-order`. Skip this for Gains.</Note>

Bridges USDC from `originChainId` to the Lighter L2 account via a multi-step bridge route.

Deposits are eventually consistent and tracked asynchronously — the `execute-v2` response carries a `processId` you poll via `/2/perp/check-process`.

<Info>
  **First-time deposits register the account on Lighter.** If the EOA has never deposited on Lighter, this call must transfer **≥ 5 USDC** (a Lighter-side requirement). Once the bridge settles, Lighter assigns an `accountIndex` to the L1 address — needed by [`/2/perp/payloads/create-account`](/rest-api-reference/endpoint/perp-payload-create-account) and every subsequent trade/withdraw call. See the create-account page for how to discover the `accountIndex` afterwards.
</Info>

### Lighter deposit payload shape

The response's `payloadStr` is a canonical envelope whose `payload` carries a bridge route with one or more transaction steps:

```jsonc theme={null}
{
  "action": "deposit",
  "dex": "lighter",
  "chainId": "lighter:304",
  "transport": "evm-tx",
  "payload": {
    "route": "...",
    "steps": [
      {
        "id": "...",
        "kind": "transaction",
        "items": [
          { "status": "incomplete", "data": { "to": "0x..", "data": "0x..", "value": "0", "chainId": 42161, "gas": "...", "maxFeePerGas": "...", "maxPriorityFeePerGas": "..." } }
        ]
      }
      // additional steps (approvals, bridge, …)
    ]
  }
}
```

**Client workflow (Lighter):**

1. Parse `payloadStr`.
2. For every step with `kind === "transaction"` and every `item` whose `status !== "complete"`, sign the tx with the user's key on `item.data.chainId`.
3. Push each signed hex string (in order) into a new array `payload.signedTxs`.
4. Re-stringify the envelope → `finalPayloadStr`.
5. Sign `` `api/2/perp/execute-v2-${timestamp}-${finalPayloadStr}` `` and call `/2/perp/execute-v2` with **that** `payloadStr` and **no** top-level `signedTx`.

### Request Body

<ParamField body="dex" type="string" required>Must be `lighter`.</ParamField>

<ParamField body="chainId" type="string" required>
  Destination Lighter chain. Must be `lighter:304` — only id the router accepts for deposit today.
</ParamField>

<ParamField body="originChainId" type="string" required>
  EVM chain holding the user's USDC. Confirmed working: `evm:42161` (Arbitrum), `evm:8453` (Base). Other EVM chains may resolve — open an issue if you need one that isn't listed.
</ParamField>

<ParamField body="amountUsdc" type="string" required>
  USDC amount as a decimal string (e.g., `"250"` or `"100.5"`). Must be positive.
</ParamField>

### Authentication

Every `/2/perp/payloads/<action>` endpoint verifies the caller by requiring two extra fields in the request body alongside the action parameters:

<ParamField body="timestamp" type="number" required>
  Unix timestamp in milliseconds. Must be within 30 seconds of server time. Older timestamps are rejected to prevent replay.
</ParamField>

<ParamField body="signature" type="string" required>
  Hex signature (EIP-191 `personal_sign`) of the message `` `${endpoint}-${timestamp}` ``, where `endpoint` is the path **of this endpoint** without the leading slash (e.g., for this page: `api/2/perp/payloads/<this-action>`). The recovered signer address becomes the `user` for the request. Single-use — replay returns `403 signature already used`.
</ParamField>

```javascript theme={null}
// Replace `<action>` with the action of THIS page (e.g. create-account, deposit, …)
const endpoint = 'api/2/perp/payloads/<action>';
const timestamp = Date.now();
const signature = await wallet.signMessage(`${endpoint}-${timestamp}`);

await fetch(`https://api.mobula.io/${endpoint}`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    timestamp,
    signature,
    // ...action-specific fields below
  }),
});
```

### Authentication errors

| Status | `message`                                                       |
| ------ | --------------------------------------------------------------- |
| 403    | `timestamp expired` — timestamp older than 30s                  |
| 403    | `signature already used` — replay attempt                       |
| 400    | `zod validation failed` — `timestamp`/`signature` shape invalid |

### Response envelope

Every `/2/perp/payloads/<action>` endpoint returns the same envelope shape. You pass these fields verbatim into `POST /2/perp/execute-v2` to execute the action.

<Note>
  **Top-level shape.** Successful (2xx) responses return `{ data: { ... } }`. A `success: true` flag is only present inside the body of `execute-v2`'s response, not on the payload-build endpoints. Parse defensively: read `body.data`, then check for the action-specific fields you need (e.g. `data.payloadStr`).
</Note>

<ResponseField name="data" type="object">
  <Expandable title="data">
    <ResponseField name="action" type="string">
      Canonical action name — one of `withdraw`, `create-account`, `deposit`, `create-order`, `close-position`, `cancel-order`, `update-margin`, `edit-order`.
    </ResponseField>

    <ResponseField name="dex" type="string">`gains` or `lighter`.</ResponseField>
    <ResponseField name="chainId" type="string">Chain where the action lands (e.g., `evm:42161`, `lighter:301`).</ResponseField>
    <ResponseField name="marketId" type="string">Mobula market identifier. Present when the action targets a specific market.</ResponseField>

    <ResponseField name="transport" type="string">
      `offchain-api` — server submits to the DEX off-chain API on the user's behalf (Lighter trades, Lighter `withdraw`, Lighter `create-account`).<br />
      `evm-tx` — server broadcasts a user-signed EVM transaction (Lighter `deposit` bridge route, Gains trade actions). The Gains case requires a top-level `signedTx` on execute-v2; the Lighter `deposit` case injects signed txs **inside** `payloadStr`.
    </ResponseField>

    <ResponseField name="payloadStr" type="string">
      JSON-stringified canonical envelope. For most actions you forward this byte-for-byte into `/2/perp/execute-v2`. Mutations are required for: Lighter `deposit` (inject `payload.signedTxs`), Lighter `withdraw` (sign `payload.MessageToSign` → `payload.L1Sig`, delete `MessageToSign`), Lighter `create-account` (same as `withdraw` only if `payload.MessageToSign` is present). After mutation, re-stringify and sign execute-v2 over the **new** string. Never alter the envelope metadata (`action`, `dex`, `chainId`, `transport`, `marketId`) — execute-v2 cross-checks it.
    </ResponseField>
  </Expandable>
</ResponseField>

### Endpoint-specific errors

| Status | `message`                                                                                   |
| ------ | ------------------------------------------------------------------------------------------- |
| 400    | `deposit payload generation failed` — bridge unavailable, insufficient USDC, or DEX refusal |

### Example — Lighter bridge from Arbitrum

```javascript theme={null}
import { ethers } from 'ethers';

const endpoint = 'api/2/perp/payloads/deposit';
const timestamp = Date.now();
const signature = await wallet.signMessage(`${endpoint}-${timestamp}`);

const payloadRes = await fetch(`https://api.mobula.io/${endpoint}`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    timestamp,
    signature,
    dex: 'lighter',
    chainId: 'lighter:304',
    originChainId: 'evm:42161',
    amountUsdc: '250',
  }),
}).then(r => r.json());

const { data } = payloadRes;
const parsed = JSON.parse(data.payloadStr);

// 1. sign each bridge tx in order
const provider = new ethers.JsonRpcProvider(arbitrumRpcUrl);
let nonce = await provider.getTransactionCount(wallet.address);
const feeData = await provider.getFeeData();
const signedTxs = [];

for (const step of parsed.payload.steps) {
  if (step.kind !== 'transaction') continue;
  for (const item of step.items) {
    if (item.status === 'complete') continue;
    const tx = item.data;
    const signed = await wallet.signTransaction({
      to: tx.to,
      data: tx.data,
      value: tx.value ? BigInt(tx.value) : 0n,
      chainId: tx.chainId,
      nonce: nonce++,
      gasLimit: tx.gas ? BigInt(tx.gas) : 1_500_000n,
      maxFeePerGas: tx.maxFeePerGas ? BigInt(tx.maxFeePerGas) : feeData.maxFeePerGas,
      maxPriorityFeePerGas: tx.maxPriorityFeePerGas ? BigInt(tx.maxPriorityFeePerGas) : feeData.maxPriorityFeePerGas,
      type: 2,
    });
    signedTxs.push(signed);
  }
}

// 2. inject + re-stringify
parsed.payload.signedTxs = signedTxs;
const finalPayloadStr = JSON.stringify(parsed);

// 3. sign execute-v2 over the UPDATED string
const execTs = Date.now();
const execSig = await wallet.signMessage(`api/2/perp/execute-v2-${execTs}-${finalPayloadStr}`);

const execRes = await fetch('https://api.mobula.io/api/2/perp/execute-v2', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    action: data.action,
    dex: data.dex,
    chainId: data.chainId,
    transport: data.transport,
    payloadStr: finalPayloadStr,
    timestamp: execTs,
    signature: execSig,
  }),
}).then(r => r.json());

// poll execRes.data.processId via /2/perp/check-process
```


## OpenAPI

````yaml POST /2/perp/payloads/deposit
openapi: 3.0.0
info:
  version: 1.0.0
  title: Mobula API
  description: >-
    Documentation of the Mobula API


    **Demo API**: The default server (demo-api.mobula.io) is a demo API with
    rate limits.

    For production use, please use api.mobula.io with an API key from
    https://admin.mobula.io
servers:
  - url: https://demo-api.mobula.io/api/
    description: Demo API (rate limited, for testing only)
  - url: https://api.mobula.io/api/
    description: Production API (requires API key)
security: []
tags:
  - name: V2 - Token
    description: Token details, price, security, ATH, and holder data
  - name: V2 - Market Data
    description: Market details, OHLCV history, and lighthouse metrics
  - name: V2 - Trades
    description: Token trades, enriched trades, and trade filters
  - name: V2 - Wallet
    description: Wallet positions, activity, trades, analysis, and labels
  - name: V2 - Assets
    description: Cross-chain asset details and price history
  - name: V2 - Swap
    description: Swap quoting and execution
  - name: V2 - Perps
    description: Perpetual futures quoting, execution, and positions
  - name: V2 - Bridge
    description: Cross-chain bridge quoting and intent status (Alpha Preview)
  - name: V2 - DeFi
    description: Bonding pools and pulse data
  - name: V2 - Search
    description: Universal fast search
  - name: V2 - Usage
    description: Per-key API and WebSocket usage history
  - name: V2 - Blockchains
    description: System metadata and chain listings
  - name: V2 - Prediction Markets
    description: >-
      Polymarket markets/events, wallet positions, and the full execution stack
      (auth, order build/submit/cancel, approvals, pUSD wrap/unwrap, deploy,
      deposit/withdraw, redeem). Alpha — see /api/2/pm/*.
  - name: V1 - Market Data
    description: Market prices, history, sparklines, pairs, and multi-data
  - name: V1 - Wallet
    description: Wallet portfolio, transactions, history, and NFTs
  - name: V1 - Token
    description: First buyers
  - name: V1 - Trades
    description: Market trades by pair
  - name: V1 - Metadata
    description: Token metadata, categories, and news
  - name: V1 - Assets
    description: List all assets
  - name: V1 - Search
    description: Search for assets, tokens, and pairs
  - name: V1 - DeFi
    description: Bonding pool pulse data
  - name: V1 - Blockchains
    description: Blockchain listings, pairs, and stats
  - name: V1 - Webhooks
    description: Webhook management
  - name: V1 - Feed
    description: Custom feed creation
paths:
  /2/perp/payloads/deposit:
    post:
      tags:
        - V2 - Perps
      summary: Build deposit payload
      description: >-
        Lighter-only. Build a signed canonical payload to bridge USDC from the
        user's wallet into their Lighter L2 account via a multi-step Relay
        route. Deposits are eventually consistent — the execute-v2 response
        carries a processId to poll via /2/perp/check-process. Gains has no
        deposit endpoint (collateral is sent with each create-order).
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                timestamp:
                  type: number
                signature:
                  type: string
                dex:
                  type: string
                  enum:
                    - lighter
                chainId:
                  type: string
                  description: Destination Lighter chain (lighter:301 or lighter:304).
                originChainId:
                  type: string
                  description: Source chain holding the user's USDC.
                amountUsdc:
                  type: string
                  description: >-
                    USDC amount as a decimal string (e.g., "250"). Must be
                    positive.
              required:
                - timestamp
                - signature
                - dex
                - chainId
                - originChainId
                - amountUsdc
      responses:
        '200':
          description: Canonical payload envelope
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      action:
                        type: string
                      dex:
                        type: string
                      chainId:
                        type: string
                      marketId:
                        type: string
                      transport:
                        type: string
                        enum:
                          - offchain-api
                          - evm-tx
                      payloadStr:
                        type: string
                    required:
                      - action
                      - dex
                      - chainId
                      - transport
                      - payloadStr
                required:
                  - success
                  - data

````