Skip to content

Account Endpoints

Get Balances

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 = false means the wallet RPC lookup failed; the vault-side numbers are still authoritative, and the wallet-side fields are returned as 0.
  • 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:

  1. POST /approve to build an ERC-20 allowance tx when you are not using permit.
  2. POST /deposit to build depositFund(...) or depositFundWithPermit(...).
  3. POST /tx/send to 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

POST /deposit

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

POST /approve

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

POST /tx/send

Authentication: API Key required

Request Body

{
  "raw_tx": "0x..."
}

Response

{
  "tx_hash": "0x..."
}

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

POST /withdraw

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

POST /withdraw/build

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

POST /withdraw/send

Authentication: Optional

Request Body

{
  "raw_tx": "0x..."
}

Response

{
  "tx_hash": "0x..."
}

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:

  1. Call emergencyWithdraw(token, amount) on the Sera contract to initiate the withdrawal request.
  2. Wait ~24 hours (~7,200 blocks).
  3. 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

POST /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

POST /transfer/send

Authentication: API Key required

Request Body

{
  "raw_tx": "0x..."
}

Response

{
  "tx_hash": "0x..."
}