Builds the payload that moves free USDC from the user’s perp trading account back to their wallet. Only free margin (not locked by open positions/orders) can be withdrawn.
Lighter withdraw payload shape
The response’s payloadStr is an envelope whose payload includes an L1 authorization challenge:
{
"action": "withdraw",
"dex": "lighter",
"chainId": "lighter:301",
"transport": "offchain-api",
"payload": {
"MessageToSign": "Lighter withdraw ... <server-generated challenge>",
// other Lighter-native fields
}
}
Client workflow (Lighter):
- Parse
payloadStr.
- Sign
payload.MessageToSign with the user’s wallet (standard EIP-191 personal_sign).
- Set the resulting hex on
payload.L1Sig and delete payload.MessageToSign.
- Re-stringify the envelope →
finalPayloadStr.
- Sign
`api/2/perp/execute-v2-${timestamp}-${finalPayloadStr}` and call /2/perp/execute-v2 with that payloadStr (no top-level signedTx).
Gains withdraw payload shape
Single EVM tx under payload.data with transport: "evm-tx". Sign it and submit as the top-level signedTx to /2/perp/execute-v2; payloadStr is forwarded unchanged.
Request Body
Chain of the perp account (e.g., lighter:301, evm:42161).
USDC amount as a decimal string (e.g., "100"). Must be positive.
Endpoint-specific errors
| Status | message |
|---|
| 400 | withdraw payload generation failed — insufficient free margin or DEX refusal |
Example — Lighter withdraw 100 USDC
const endpoint = 'api/2/perp/payloads/withdraw';
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:301',
amountUsdc: '100',
}),
}).then(r => r.json());
const { data } = payloadRes;
const envelope = JSON.parse(data.payloadStr);
// Lighter L1 sig dance
const l1Sig = await wallet.signMessage(envelope.payload.MessageToSign);
envelope.payload.L1Sig = l1Sig;
delete envelope.payload.MessageToSign;
const finalPayloadStr = JSON.stringify(envelope);
const execTs = Date.now();
const execSig = await wallet.signMessage(`api/2/perp/execute-v2-${execTs}-${finalPayloadStr}`);
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,
}),
});