Skip to main content
POST
/
v1
/
withdraw
Submit withdrawal
curl --request POST \
  --url https://api.testnet.arcus.xyz/v1/withdraw \
  --header 'Content-Type: application/json' \
  --data '
{
  "ethereumAddress": "<string>",
  "amount": "<string>",
  "nonce": "<string>",
  "signature": {
    "r": "<string>",
    "s": "<string>",
    "v": "<string>"
  },
  "accountIndex": 4
}
'
{
  "withdrawalId": "<string>",
  "ethereumAddress": "<string>",
  "accountIndex": 4,
  "amount": "<string>",
  "status": "PENDING",
  "submittedAt": 123
}
Submit a withdrawal of collateral to the chain. The request is self-authenticated by a secp256k1 EIP-712 typed-data signature in the body — it does not require the X-API-Key / X-Signature headers. In v0 only withdraw-to-self is supported: the recipient is always the wallet that signed the request (ethereumAddress), so there is no to field.

Response behavior

Asynchronous. A 202 Accepted means the withdrawal passed validation and was queued to the matching engine with status: PENDING. The definitive outcome (applied, or rejected for insufficient free collateral) is delivered asynchronously — poll GET /v1/accountTransferUpdates or subscribe to the account transfer update WebSocket channel and correlate on withdrawalId.

Signing the request

Withdrawals use EIP-712 typed data (eth_signTypedData_v4), so wallets render each field and institutional / MPC signers can whitelist on the domain. Sign this typed data with the wallet that owns ethereumAddress, then split the 65-byte result into {r, s, v} for the signature field. The gateway recovers the signer and rejects mismatches with HTTP 401. Domain
{
  "name": "Arcus Withdraw",
  "version": "1",
  "chainId": <rootchain chain id, e.g. 421614>,
  "verifyingContract": "<BridgeVault address for that chain>"
}
Types
Withdraw(address ethereumAddress,uint8 accountIndex,uint256 amount,string nonce)
Message
{
  "ethereumAddress": "<ethereumAddress>",
  "accountIndex": <accountIndex>,
  "amount": "<amount>",
  "nonce": "<nonce>"
}
amount is the integer collateral quote-quantum amount (same value as the amount request field). Per-environment domain parameters
EnvironmentchainIdverifyingContract (BridgeVault)
staging4216140xe91f43c1ad084463db129034fb7b93545dfe1d4e
testnet466300xe0166c85fcb29ea6d21915dd0dc54387e8d17915
productionTBATBA
Complete eth_signTypedData_v4 call (ethers v6 / viem)
// ethers v6
const domain = {
  name: "Arcus Withdraw",
  version: "1",
  chainId: 421614,                                       // staging
  verifyingContract: "0xe91f43c1ad084463db129034fb7b93545dfe1d4e"
};
const types = {
  Withdraw: [
    { name: "ethereumAddress",  type: "address" },
    { name: "accountIndex",     type: "uint8"   },
    { name: "amount",           type: "uint256" },
    { name: "nonce",            type: "string"  }
  ]
};
const message = {
  ethereumAddress: "0xYourAddress",
  accountIndex:    0,
  amount:          BigInt("5000000000000"),   // quote quantums
  nonce:           "b1c2d3e4-5f60-7182-93a4-b5c6d7e8f901"
};
const sig = await signer.signTypedData(domain, types, message);
// Split into r / s / v for the request body:
const r = sig.slice(0, 66);
const s = "0x" + sig.slice(66, 130);
const v = "0x" + sig.slice(130, 132);
// viem
const sig = await walletClient.signTypedData({ domain, types, primaryType: "Withdraw", message });
The v component must be 0x1b (27) or 0x1c (28); wallets that return 0 / 1 should have 27 added before sending.

Body

application/json
ethereumAddress
string
required

Master EVM address initiating the withdrawal. In v0 this is also the on-chain recipient (withdraw-to-self) — there is no separate destination field.

Pattern: ^(0x|0X)?[0-9a-fA-F]{40}$
Example:

"0x742d35cc6634c0532925a3b844bc9e7595f2bd18"

amount
string
required

Amount to withdraw, as a base-10 integer string in quote quantums (1e9 = $1; for example, 1000000000 represents $1). Requests with an amount below the minimum withdrawal size for the collateral currency are rejected with HTTP 400, for example validation error on field 'amount': must be at least 1000000000 quote quantums. Amounts must be positive, fit in a signed 64-bit integer, and be exactly representable in collateral base units; float-style decimals are rejected.

Pattern: ^[0-9]+$
Example:

"5000000000000"

nonce
string
required

Client-supplied nonce for replay protection.

Minimum string length: 1
Example:

"b1c2d3e4-5f60-7182-93a4-b5c6d7e8f901"

signature
object
required

secp256k1 EIP-712 typed-data signature (r, s, v) over the Withdraw payload (eth_signTypedData_v4). See the POST /v1/withdraw endpoint description for the exact domain, types, and message. Produced by the wallet that owns ethereumAddress; requests whose recovered signer does not match are rejected with HTTP 401.

accountIndex
integer

Trading account to debit. Defaults to 0 when omitted.

Required range: 0 <= x <= 9

Response

Withdrawal accepted and queued to the matching engine. The body carries status: PENDING and a withdrawalId; the terminal outcome is delivered on the account transfer update stream.

withdrawalId
string
required

Server-generated identifier (UUID) correlating this withdrawal with the matching AccountTransferUpdate on the account transfer update stream.

Example:

"b3f1c2d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d"

ethereumAddress
string
required

20-byte EVM address as hex: optional 0x or 0X prefix and exactly 40 hexadecimal digits. API responses normalize to lowercase af after 0x.

Pattern: ^(0x|0X)?[0-9a-fA-F]{40}$
accountIndex
integer
required

Account index (account index, 0–9). Identifies the account for orders, positions, fills, and API keys.

Required range: 0 <= x <= 9
amount
string
required

Withdrawal amount echoed back as a human-readable decimal in quote currency (quote resolution 1e9), matching how the amount is reported on the account transfer update feed.

Examples:

"5000"

"1.5"

status
enum<string>
required

Synchronous status of a submitted withdrawal. Always PENDING on the 202 response — the withdrawal has been queued to the matching engine but not yet applied. The terminal outcome (APPLIED, REJECTED_INSUFFICIENT_COLLATERAL, ...) is delivered asynchronously on the account transfer update stream (see AccountTransferUpdate).

Available options:
PENDING
submittedAt
integer<int64>
required

Server receive timestamp (epoch microseconds).