Single unified execution endpoint for every perp action. You forward the exact envelope returned by the matchingDocumentation Index
Fetch the complete documentation index at: https://docs.mobula.io/llms.txt
Use this file to discover all available pages before exploring further.
/2/perp/payloads/<action> call plus a signature that proves the caller approved this specific payload at this specific timestamp.
POST /2/perp/payloads/<action> (see left-hand nav) → receive { action, dex, chainId, marketId?, transport, payloadStr }.transport: "evm-tx" only: parse payloadStr, extract payload.data, sign the EVM transaction locally → obtain signedTx.POST /2/perp/execute-v2 with the envelope fields, signature, timestamp, and (when required) signedTx.withdraw, create-account, deposit, create-order, close-position, cancel-order, update-margin, edit-order. Must match the action inside payloadStr.gains or lighter. Must match payloadStr.payloadStr.payloadStr.marketId.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./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.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.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.| Status | message | Cause |
|---|---|---|
| 400 | payloadStr is not valid JSON | payloadStr is not parseable |
| 400 | invalid payloadStr | envelope fails schema validation (see errors) |
| 400 | payloadStr metadata does not match request metadata | action/dex/transport/chainId mismatch between envelope and request |
| 400 | payloadStr marketId does not match request marketId | marketId mismatch |
| 400 | signedTx execution requires an EVM chainId, received "<chainId>" | transport: "evm-tx" with a non-EVM chain |
| 403 | timestamp expired | timestamp more than 30s from server clock |
| 403 | signature already used | Replay of an earlier request |
| 403 | signature signer does not match payload.from | Signer address ≠ payload.data.from for actions that carry a from |
| 500 | Failed to broadcast signed transaction on <chainId> | RPC rejected signedTx (see errors list for details) |
| 500 | DEX-specific reason | Adapter-level failure returned by the DEX |
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.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.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:
| DEX | Action | Transport | Client signing |
|---|---|---|---|
| Lighter | create-order, close-position, cancel-order, edit-order, update-margin | offchain-api | execute-v2 signature only — forward payloadStr byte-for-byte |
| Lighter | create-account | offchain-api | If the response carries payload.MessageToSign: sign it, set payload.L1Sig, delete payload.MessageToSign, re-stringify. Otherwise no mutation. |
| Lighter | deposit | evm-tx | Sign 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. |
| Lighter | withdraw | offchain-api | Sign payload.MessageToSign, set payload.L1Sig, delete payload.MessageToSign, re-stringify, then sign execute-v2 over the new string. |
| Gains | create-order, close-position, cancel-order, edit-order, update-margin | evm-tx | Read 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. |
withdraw, create-account, deposit, create-order, close-position, cancel-order, update-margin, edit-order gains, lighter offchain-api, evm-tx JSON-stringified canonical envelope. Envelope metadata must match the request fields.
Hex signature of api/2/perp/execute-v2-{timestamp}-{payloadStr}.
Hex-encoded single signed EVM transaction (Gains single-tx actions with transport evm-tx).