Skip to main content
POST
Submit internal transfer
The response body shown here is a static example, not live data. After you click Send, your live result appears in a separate panel headed 200 OK. The panel under the status-code tabs is a fixed sample from the spec — its field values (fees, prices, sizes, IDs, timestamps) are placeholders. Use Send, or call the endpoint, for current values.
Move collateral between two account indexes of the same wallet. 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 transfers are same-wallet only: both fromAccountIndex and toAccountIndex belong to ethereumAddress, so there is no cross-wallet recipient field. Funds never leave custody, so nothing settles on-chain.

Response behavior

Asynchronous. A 202 Accepted means the transfer 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 transferId.

Signing the request

Transfers use EIP-712 typed data (eth_signTypedData_v4). 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
Types
Message
amount is the integer collateral quote-quantum amount (same value as the amount request field). Per-environment domain parameters. chainId and verifyingContract are identical to the withdraw domain — see the per-environment table under POST /v1/withdraw. Only the domain name differs ("Arcus Transfer" vs "Arcus Withdraw"). Complete eth_signTypedData_v4 call (ethers v6 / viem)
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 that owns both the source and destination accounts. In v0 an internal transfer is same-wallet only: the funds move between two account indexes of this one address — there is no cross-wallet recipient field.

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

"0x742d35cc6634c0532925a3b844bc9e7595f2bd18"

fromAccountIndex
integer
required

Account index to debit.

Required range: 0 <= x <= 9
toAccountIndex
integer
required

Account index to credit. Must differ from fromAccountIndex; a transfer to the same index is rejected with HTTP 400.

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

Amount to transfer, as a base-10 integer string in quote quantums (1e9 = $1; for example, 1000000000 represents $1). Amounts must be positive and fit in a signed 64-bit integer. Unlike withdrawals, an internal transfer never settles on-chain, so the amount is NOT constrained to collateral-base-unit multiples. Float-style decimals are rejected. The source account must have sufficient free collateral or the transfer is rejected asynchronously with REJECTED_INSUFFICIENT_COLLATERAL.

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

"100000000000"

nonce
string
required

Replay-protection nonce, single-use per (ethereumAddress, fromAccountIndex, toAccountIndex) — a repeated nonce is rejected with 409. Send the current time as nanoseconds since the Unix epoch (a plain base-10 integer, e.g. 1730000000000000000); the server accepts a timestamp up to 48 hours in the past by default and up to 24 hours in the future by default — both bounds are configurable per environment. A legacy opaque nonce (e.g. a UUID) is still accepted but deprecated. At most 64 characters.

Required string length: 1 - 64
Example:

"1730000000000000000"

signature
object
required

secp256k1 EIP-712 typed-data signature (r, s, v) over the Transfer payload (eth_signTypedData_v4). See the POST /v1/transfer 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.

Response

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

transferId
string
required

Server-generated identifier (UUID) correlating this transfer 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}$
fromAccountIndex
integer
required

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

Required range: 0 <= x <= 9
toAccountIndex
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

Transfer 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:

"100"

"1.5"

status
enum<string>
required

Synchronous status of a submitted transfer. Always PENDING on the 202 response — the transfer 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).