Skip to main content
POST
/
2
/
perp
/
execute-v2
Execute perp action
curl --request POST \
  --url https://demo-api.mobula.io/api/2/perp/execute-v2 \
  --header 'Content-Type: application/json' \
  --data '
{
  "action": "withdraw",
  "dex": "gains",
  "chainId": "<string>",
  "transport": "offchain-api",
  "payloadStr": "<string>",
  "timestamp": 123,
  "signature": "<string>",
  "marketId": "<string>",
  "signedTx": "<string>"
}
'
{
  "success": true,
  "data": {
    "success": true,
    "executionDetails": [
      {
        "txHash": "<string>",
        "type": "<string>",
        "status": "<string>",
        "orderStatuses": [
          {
            "orderId": "<string>",
            "status": "<string>",
            "type": "<string>"
          }
        ]
      }
    ],
    "processId": "<string>"
  }
}

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.

Single unified execution endpoint for every perp action. You forward the exact envelope returned by the matching /2/perp/payloads/<action> call plus a signature that proves the caller approved this specific payload at this specific timestamp.
The /2/perp/execute (v1) endpoint is deprecated. All new integrations must use /2/perp/execute-v2.

Flow

  1. Call POST /2/perp/payloads/<action> (see left-hand nav) → receive { action, dex, chainId, marketId?, transport, payloadStr }.
  2. For Gains transport: "evm-tx" only: parse payloadStr, extract payload.data, sign the EVM transaction locally → obtain signedTx.
  3. Sign the execute-v2 authentication message (see Authentication below).
  4. POST /2/perp/execute-v2 with the envelope fields, signature, timestamp, and (when required) signedTx.

Request Body

action
string
required
Canonical action name. One of withdraw, create-account, deposit, create-order, close-position, cancel-order, update-margin, edit-order. Must match the action inside payloadStr.
dex
string
required
gains or lighter. Must match payloadStr.
chainId
string
required
Chain of the action. Must match payloadStr.
marketId
string
Mobula market identifier. If provided, must match payloadStr.marketId.
transport
string
required
offchain-api — server submits to the DEX off-chain API using the user’s signature.
evm-tx — server broadcasts the user-signed EVM transaction supplied in signedTx. Must match payloadStr.
payloadStr
string
required
JSON-stringified canonical envelope returned by /2/perp/payloads/<action>.Envelope metadata (action, dex, chainId, transport, marketId) must be forwarded unchanged — it is cross-checked against the request fields.payload sub-fields are action-specific and some flows require mutating them before signing execute-v2:
  • deposit (Lighter route with EVM bridge steps) — sign every tx in payload.steps[].items[].data and write the hex results into payload.signedTxs as an array of strings.
  • withdraw on Lighter — sign payload.MessageToSign, set the hex result on payload.L1Sig, then delete payload.MessageToSign.
In those cases you re-stringify the envelope and use the new string everywhere: as this field, and in the execute-v2 signed message below.
timestamp
number
required
Unix timestamp in milliseconds, within 30 seconds of server time.
signature
string
required
Hex signature of the message api/2/perp/execute-v2-{timestamp}-{payloadStr}. The recovered signer address must match the payload.data.from address for actions that carry a from (e.g., create-order, close-position). Single-use: a second call with the same signature is rejected.
signedTx
string
Hex-encoded single signed EVM transaction. Used for Gains single-tx actions (create-order, close-position, cancel-order, edit-order, update-margin) whose response has transport: "evm-tx". The server broadcasts it via eth_sendRawTransaction on chainId.Do not use this field for multi-tx flows (Lighter deposit bridge route) — those inject signed txs into payloadStr under payload.signedTxs instead.

Response

data
object

Errors

StatusmessageCause
400payloadStr is not valid JSONpayloadStr is not parseable
400invalid payloadStrenvelope fails schema validation (see errors)
400payloadStr metadata does not match request metadataaction/dex/transport/chainId mismatch between envelope and request
400payloadStr marketId does not match request marketIdmarketId mismatch
400signedTx execution requires an EVM chainId, received "<chainId>"transport: "evm-tx" with a non-EVM chain
403timestamp expiredtimestamp more than 30s from server clock
403signature already usedReplay of an earlier request
403signature signer does not match payload.fromSigner address ≠ payload.data.from for actions that carry a from
500Failed to broadcast signed transaction on <chainId>RPC rejected signedTx (see errors list for details)
500DEX-specific reasonAdapter-level failure returned by the DEX

Authentication

The execute-v2 signature binds the exact payloadStr to the exact timestamp:
const timestamp = Date.now();
const message = `api/2/perp/execute-v2-${timestamp}-${payloadStr}`;
const signature = await wallet.signMessage(message);
Rules:
  • payloadStr in the signed message is the same string you put in the request body. If the flow requires injecting fields (deposit signedTxs, Lighter withdraw L1Sig), do the injection + re-stringify before signing — sign the final string, not the original.
  • For actions whose envelope carries payload.data.from (Gains EVM txs, Lighter orders), the signer must equal from. Deposit/withdraw envelopes that don’t expose a from skip this check.
  • Signatures are single-use for 30 seconds after the timestamp.

Transport & signing matrix

The transport returned by /2/perp/payloads/<action> is authoritative — forward it as-is. What the client must sign depends on both the DEX and the action:
DEXActionTransportClient signing
Lightercreate-order, close-position, cancel-order, edit-order, update-marginoffchain-apiexecute-v2 signature only — forward payloadStr byte-for-byte
Lightercreate-accountoffchain-apiIf the response carries payload.MessageToSign: sign it, set payload.L1Sig, delete payload.MessageToSign, re-stringify. Otherwise no mutation.
Lighterdepositevm-txSign each tx in payload.steps[].items[].data (each item’s data is a tx object: to, data, value, chainId, gas?, fee fields). Inject results into payload.signedTxs: string[], re-stringify, then sign execute-v2 over the new string. Do not send top-level signedTx.
Lighterwithdrawoffchain-apiSign payload.MessageToSign, set payload.L1Sig, delete payload.MessageToSign, re-stringify, then sign execute-v2 over the new string.
Gainscreate-order, close-position, cancel-order, edit-order, update-marginevm-txRead the tx object at payload.data (fields: to, callData (not data), value, chainId, nonce?, gas?, fee fields). Sign as a type-2 EVM tx → submit as top-level signedTx. payloadStr is forwarded unchanged.

Body

application/json
action
enum<string>
required
Available options:
withdraw,
create-account,
deposit,
create-order,
close-position,
cancel-order,
update-margin,
edit-order
dex
enum<string>
required
Available options:
gains,
lighter
chainId
string
required
transport
enum<string>
required
Available options:
offchain-api,
evm-tx
payloadStr
string
required

JSON-stringified canonical envelope. Envelope metadata must match the request fields.

timestamp
number
required
signature
string
required

Hex signature of api/2/perp/execute-v2-{timestamp}-{payloadStr}.

marketId
string
signedTx
string

Hex-encoded single signed EVM transaction (Gains single-tx actions with transport evm-tx).

Response

200 - application/json

Perp execute response

success
boolean
required
data
object
required