Account Endpoints¶
Get Balances¶
Authentication: API Key required
Query Parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
owner_address | address | Yes | Must match the authenticated API-key owner |
include_zero | boolean | No | Defaults to false. When false, tokens whose total is zero are omitted to keep the response bounded; set true to include every whitelisted token. |
Response¶
{
"owner_address": "0x...",
"balances": [
{
"token": "0xd3BdB2CE9cD98566EFc2e2977448c40578371779",
"symbol": "EURC",
"decimals": 6,
"wallet_balance": "1250000000",
"vault_available": "400000000",
"vault_frozen": "100000000",
"vault_total": "500000000",
"total": "1750000000"
}
],
"updated_at": "2026-04-15T09:12:34.567890+00:00",
"wallet_balance_available": true
}
| Field | Type | Description |
|---|---|---|
token | address | ERC-20 contract address |
symbol | string | Token ticker |
decimals | integer | Token precision; use to convert the raw strings to human-readable values |
wallet_balance | string | User's wallet balance (raw uint256 decimal string) |
vault_available | string | Vault balance available for trading (raw uint256 decimal string) |
vault_frozen | string | Vault balance locked in open orders (raw uint256 decimal string) |
vault_total | string | vault_available + vault_frozen |
total | string | wallet_balance + vault_total |
wallet_balance_available | boolean | false if the wallet RPC lookup failed |
All balance fields are raw uint256 decimal strings. Use each row's decimals field to convert them to human-readable values. Vault balances are authoritative; wallet balances are best-effort.
Response Notes¶
wallet_balance_available = falsemeans the wallet RPC lookup failed; the vault-side numbers are still authoritative, and the wallet-side fields are returned as0.vault_available + vault_frozen = vault_total.wallet_balance + vault_total = total.
Example¶
const response = await fetch(
'https://api.sera.cx/api/v1/balances?owner_address=0x...',
{ headers: { 'Authorization': 'Bearer sera_key:secret' } }
);
const { balances } = await response.json();
for (const bal of balances) {
console.log(`${bal.symbol}: available=${bal.vault_available}, frozen=${bal.vault_frozen}`);
}
Deposit Helpers¶
Fetch the live vault_address and sor_address from GET /config. Deposits now use three helpers:
POST /approveto build an ERC-20 allowance tx when you are not using permit.POST /depositto builddepositFund(...)ordepositFundWithPermit(...).POST /tx/sendto broadcast the signed approve or deposit tx.
If the token supports EIP-2612, you can skip a separate approve by passing permit_signature and permit_deadline to POST /deposit. For client-side permit discovery, use GET /permit/metadata.
Build Deposit Transaction¶
Authentication: API Key required
Request Body¶
{
"token": "0xd3BdB2CE9cD98566EFc2e2977448c40578371779",
"owner": "0x...",
"amount": "1000000",
"permit_signature": "0x...",
"permit_deadline": 1713254400,
"permit_amount": "1000000"
}
| Field | Type | Required | Description |
|---|---|---|---|
token | address | Yes | ERC-20 token address to deposit |
owner | address | Yes | Depositor address; must match the authenticated API-key owner |
amount | string | Yes | Raw uint256 token amount |
permit_signature | hex string | No | EIP-2612 permit signature for inline allowance grant; both standard 65-byte and compact 64-byte hex encodings are accepted |
permit_deadline | integer | No | Deadline signed into the permit |
permit_amount | string | No | Raw uint256 permit allowance; defaults to amount when omitted |
When permit fields are omitted, the builder targets depositFund(token, owner, amount). When permit fields are present, it targets depositFundWithPermit(...) instead.
Response¶
{
"tx": {
"to": "0xd0fc92d8eF9bE26D7288fCa1D6458f675e72B83a",
"data": "0x...",
"value": "0x0",
"chainId": "0xaa36a7",
"nonce": "0x2b",
"gas": "0x13880",
"type": "0x2",
"maxFeePerGas": "0x...",
"maxPriorityFeePerGas": "0x..."
}
}
Build Approve Transaction¶
Authentication: API Key required
Request Body¶
{
"token": "0xd3BdB2CE9cD98566EFc2e2977448c40578371779",
"owner": "0x...",
"spender": "0x5d6ffA9b9710C7Ab69105496a0fD8C48DfF40110",
"amount": "1000000"
}
spender must be the live Vault or SOR address from GET /config; other targets are rejected.
Response¶
{
"tx": {
"to": "0xd3BdB2CE9cD98566EFc2e2977448c40578371779",
"data": "0x...",
"value": "0x0",
"chainId": "0xaa36a7",
"nonce": "0x2a",
"gas": "0xc350",
"type": "0x2",
"maxFeePerGas": "0x...",
"maxPriorityFeePerGas": "0x..."
}
}
Broadcast Signed Approve Or Deposit¶
Authentication: API Key required
Request Body¶
Response¶
POST /tx/send only accepts signed approve / deposit calls that match an allowed selector-to-target pairing.
For approve, the encoded spender must also be the live Vault or SOR address from GET /config. If you submit an EIP-7702 type-4 transaction, every authorization delegate must be on the deployment's allowlist or the request is rejected.
Withdraw Co-Signature¶
Instant withdrawal is a dual-signature flow: the user signs WithdrawIntent, the executor co-signs it, and the user then signs the final transaction.
1. Request Executor Co-Signature¶
Authentication: Optional. If an API key is supplied, intent.user must match the authenticated owner.
Request Body¶
{
"intent": {
"user": "0x...",
"tokens": ["0xd3BdB2CE9cD98566EFc2e2977448c40578371779"],
"amounts": ["1000000"],
"recipient": "0x...",
"deadline": "1713254400",
"uuid": "123456789"
},
"user_signature": "0x..."
}
| Field | Type | Description |
|---|---|---|
intent.user | address | Withdrawing user's wallet |
intent.tokens | address[] | Token addresses to withdraw (1-20) |
intent.amounts | string[] | Per-token amounts (raw uint256 strings) |
intent.recipient | address | Destination wallet for the withdrawn tokens |
intent.deadline | string | Unix timestamp deadline (uint256 as string). Must be in the future and at most 365 days minus the 300-second clock-skew guard. |
intent.uuid | string | Unique replay-protection identifier (uint256 as string) |
user_signature | string | EIP-712 WithdrawIntent signature |
tokens and amounts must have the same length, with 1 to 20 entries. Each amount must be a positive uint256 string.
Response¶
{
"success": true,
"executor_address": "0xDa6e605DB8c3221f4B3706c1da9C4E28195045f5",
"executor_signature": "0x...",
"error": null
}
2. Build Withdrawal Transaction¶
Authentication: Optional. If an API key is supplied, intent.user must match the authenticated owner.
Request Body¶
{
"intent": {
"user": "0x...",
"tokens": ["0xd3BdB2CE9cD98566EFc2e2977448c40578371779"],
"amounts": ["1000000"],
"recipient": "0x...",
"deadline": "1713254400",
"uuid": "123456789"
},
"user_signature": "0x...",
"executor": "0xDa6e605DB8c3221f4B3706c1da9C4E28195045f5",
"executor_signature": "0x..."
}
| Field | Type | Description |
|---|---|---|
intent | object | Same WithdrawIntent as step 1 |
user_signature | string | Your EIP-712 signature |
executor | address | Co-signing executor address from step 1's response |
executor_signature | string | Executor co-signature returned by step 1 |
Response¶
{
"tx": {
"to": "0xd0fc92d8eF9bE26D7288fCa1D6458f675e72B83a",
"data": "0x...",
"value": "0x0",
"chainId": "0xaa36a7",
"nonce": "0x2c",
"gas": "0x249f0",
"type": "0x2",
"maxFeePerGas": "0x...",
"maxPriorityFeePerGas": "0x..."
}
}
3. Broadcast Signed Withdrawal¶
Authentication: Optional
Request Body¶
Response¶
POST /withdraw/send only accepts signed executeInstantWithdrawDualSig(...) calls targeting the live Sera contract from GET /config. If you submit an EIP-7702 type-4 transaction, every authorization delegate must be on the deployment's allowlist or the request is rejected.
Emergency Withdrawal¶
If the API is unavailable, users can still withdraw directly on-chain through Sera's two-step flow:
- Call
emergencyWithdraw(token, amount)on the Sera contract to initiate the withdrawal request. - Wait ~24 hours (~7,200 blocks).
- Call
emergencyWithdraw(token, amount)again with the same parameters to execute.
See the Sera.sol contract reference for details.
ERC-20 Transfer¶
The API can also build and broadcast direct ERC-20 transfers for whitelisted tokens.
Build Transfer¶
Authentication: API Key required
Request Body¶
{
"token": "0xd3BdB2CE9cD98566EFc2e2977448c40578371779",
"to": "0x...",
"amount": "1000000",
"from_address": "0x..."
}
| Field | Type | Required | Description |
|---|---|---|---|
token | address | Yes | ERC-20 token contract address (must be in the token registry) |
to | address | Yes | Recipient address |
amount | string | Yes | Amount in raw token units |
from_address | address | Yes | Sender wallet address |
Response¶
{
"tx": {
"to": "0xd3BdB2CE9cD98566EFc2e2977448c40578371779",
"data": "0x...",
"value": "0x0",
"chainId": "0xaa36a7",
"nonce": "0x2d",
"gas": "0xea60",
"type": "0x2",
"maxFeePerGas": "0x...",
"maxPriorityFeePerGas": "0x..."
}
}
Broadcast Transfer¶
Authentication: API Key required