> ## 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 Update-Margin Payload

> Build a signed canonical payload to add or remove collateral from an open perpetual position (Lighter) or change its per-trade leverage (Gains).

<Warning>
  **Lighter requires the wallet to be a registered account before any trade/withdraw action.**

  If the EOA has never deposited on Lighter, this endpoint will fail. First-time setup is a two-step prerequisite:

  1. **Deposit ≥ 5 USDC** via [`/2/perp/payloads/deposit`](/rest-api-reference/endpoint/perp-payload-deposit) → Lighter creates an `accountIndex` on-chain once the bridge settles. (5 USDC is a Lighter requirement, not a Mobula limit.)
  2. **Provision API key + auth token** via [`/2/perp/payloads/create-account`](/rest-api-reference/endpoint/perp-payload-create-account) using that `accountIndex`.

  Read the [Build Create-Account Payload](/rest-api-reference/endpoint/perp-payload-create-account) page for the full setup flow including how to discover the `accountIndex` after a deposit.
</Warning>

Adjusts margin on an open position. The exact semantics depend on the DEX:

* **Lighter** — supply `usdcAmount` and `increase` to add or remove USDC collateral on the market's position.
* **Gains** — supply `newLeverage` to change the trade's leverage, which effectively adjusts its collateral.

### Request Body

<ParamField body="dex" type="string" required>`gains` or `lighter`.</ParamField>

<ParamField body="chainId" type="string" required>Chain of the position.</ParamField>

<ParamField body="marketId" type="string">Mobula market identifier.</ParamField>

<ParamField body="positionId" type="string">Gains trade index. Required for Gains.</ParamField>

<ParamField body="usdcAmount" type="number">
  **Lighter only.** USDC amount to add or remove (> 0).
</ParamField>

<ParamField body="increase" type="boolean">
  **Lighter only.** `true` to add margin, `false` to remove it.
</ParamField>

<ParamField body="newLeverage" type="number">
  **Gains only.** New per-trade leverage (> 0).
</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    | `update-margin payload action failed` — missing position, wrong leg supplied for the DEX, or DEX refusal |

### Full flow — update margin end-to-end

Single example covering both DEXes (Lighter offchain-api with `usdcAmount`+`increase`, Gains evm-tx with `newLeverage`). The flow branches on `data.transport`.

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

// 1. Auth-sign + fetch the update-margin payload
const endpoint = 'api/2/perp/payloads/update-margin';
const ts = Date.now();
const authSig = await wallet.signMessage(`${endpoint}-${ts}`);

// Lighter: add 50 USDC of margin to BTC-USD
const lighterBody = {
  timestamp: ts, signature: authSig,
  dex: 'lighter', chainId: 'lighter:301',
  marketId: 'lighter-btc-usd',
  usdcAmount: 50, increase: true,
};

// Gains alternative: lower leverage to 5x on Gains trade #12345
// const gainsBody = {
//   timestamp: ts, signature: authSig,
//   dex: 'gains', chainId: 'evm:42161',
//   marketId: 'gains-btc-usd',
//   positionId: '12345', newLeverage: 5,
// };

const { data } = await fetch(`https://api.mobula.io/${endpoint}`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(lighterBody),
}).then(r => r.json());

// 2. Branch on transport
let signedTx;
const finalPayloadStr = data.payloadStr;

if (data.transport === 'evm-tx') {
  // Gains
  const txData = JSON.parse(data.payloadStr).payload.data;
  const provider = new ethers.JsonRpcProvider(rpcUrlFor(txData.chainId));
  const feeData = await provider.getFeeData();

  const baseTx = {
    to: txData.to,
    data: txData.callData,
    value: txData.value ? BigInt(txData.value) : 0n,
    from: wallet.address,
    chainId: txData.chainId,
    nonce: txData.nonce ?? await provider.getTransactionCount(wallet.address),
  };
  const gasLimit = txData.gas ? BigInt(txData.gas) : await provider.estimateGas(baseTx);

  signedTx = await wallet.signTransaction({
    ...baseTx, gasLimit,
    maxFeePerGas: txData.maxFeePerGas ? BigInt(txData.maxFeePerGas) : feeData.maxFeePerGas,
    maxPriorityFeePerGas: txData.maxPriorityFeePerGas ? BigInt(txData.maxPriorityFeePerGas) : feeData.maxPriorityFeePerGas,
    type: 2,
  });
}

// 3. Sign + submit execute-v2
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,
    marketId: data.marketId,
    transport: data.transport,
    payloadStr: finalPayloadStr,
    timestamp: execTs,
    signature: execSig,
    ...(signedTx && { signedTx }),
  }),
}).then(r => r.json());
```


## OpenAPI

````yaml POST /2/perp/payloads/update-margin
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/update-margin:
    post:
      tags:
        - V2 - Perps
      summary: Build update-margin payload
      description: >-
        Build a signed canonical payload to adjust margin on an open position.
        Lighter uses usdcAmount+increase; Gains uses newLeverage to adjust
        collateral.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                timestamp:
                  type: number
                signature:
                  type: string
                dex:
                  type: string
                  enum:
                    - gains
                    - lighter
                chainId:
                  type: string
                marketId:
                  type: string
                positionId:
                  type: string
                  description: Gains trade index. Required for Gains.
                usdcAmount:
                  type: number
                  description: Lighter only. USDC amount to add or remove (> 0).
                increase:
                  type: boolean
                  description: Lighter only. true = add margin, false = remove.
                newLeverage:
                  type: number
                  description: Gains only. New per-trade leverage (> 0).
              required:
                - timestamp
                - signature
                - dex
                - chainId
      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

````