Skip to main content

Spot Wallet Overview

ZTDX Spot is a multi-token wallet running alongside (but isolated from) the perpetual exchange. The MVP supports the DF governance token on BSC, plus an internal USDT bridge between perp margin and the spot wallet.

Key Concepts

ConceptMeaning
Spot balancePer-(user, token) pair: available + frozen. Frozen funds are reserved for an in-flight withdrawal.
Internal transferAtomic move between perp margin and spot wallet (USDT only in MVP). On-chain TX not involved.
DepositUser sends ERC-20 to the BSC vault contract. A backend listener credits the spot balance after confirmation_depth blocks.
WithdrawalTwo-step: (1) backend signs an EIP-712 release message; (2) user submits the signed message on-chain to pull funds from the vault.

Base URL

EnvironmentREST
Testnethttps://api-sepolia.p99.world/api/v1
Mainnet(not deployed yet)

Authentication

All /spot/* endpoints sit behind the project-wide auth middleware. Two schemes are accepted:

SchemeHeaderAllowed for
Bearer JWTAuthorization: Bearer <JWT>All spot endpoints
API KeyX-API-Key: <key>Read-only endpoints. POST /spot/transfer and POST /spot/withdraw/request reject API-key callers (returns 403 API Key permission denied).

GET /wallet/tokens is public (no auth required).

Response Envelope

GET /wallet/tokens uses the project standard envelope:

{ "success": true, "data": [...], "error": null, "timestamp": 1778312000 }

The other spot endpoints currently return data directly (bare object / array) on success. Errors are {"error": "..."} with the appropriate HTTP status.

On-Chain Components

ItemNetworkAddress / Value
BSC chain idBSC Testnet97
Vault contract (ZtdxSpotVault)BSC Testnet0x4Fe0b354c5865ee9deb979a99030d757ae47664a
DF token (ERC-20)BSC Testnet0x8063a43ed88397c1B10DA23dcC60ba1E7A0Bf555
DF decimals18
Perp collateral (USDT)Arbitrum Sepolia (421614)0xfA70c5A9221d239Cd51DBf48967ABc79d7B9D61d
USDT decimals6

EIP-712 Domain (Withdrawal)

Withdraw signatures are EIP-712 typed data verified by the vault contract.

Domain

FieldValue
nameZTDX Spot Vault
version1
chainId97 (BSC Testnet)
verifyingContractVault address (above)

Primary Type — SpotReleaseFunds

SpotReleaseFunds(
address account,
address token,
uint256 value,
uint256 nonce,
uint256 deadline
)

The signed message is consumed by the vault's withdraw(token, amount, deadline, signature) function.

Lifecycles

Deposit

User wallet ──approve──▶ DF token
User wallet ──deposit(token, amount)──▶ Vault contract

▼ SpotDeposit event
Backend listener

confirmation_depth blocks (currently 20)


spot_balances.available += amount

Withdraw

1. POST /spot/withdraw/request 2. Submit on-chain
─────────────────────────────────▶ Backend ─────────────────▶ Vault contract
│ │
▼ ▼
status = "signed" SpotWithdrawal event
available -= amount │
frozen += amount ▼
Backend listener


status = "confirmed"
frozen -= amount

If a signed withdrawal is not submitted on-chain within SPOT_WITHDRAW_NONCE_TTL_SECS (currently 86400 = 24h), a reaper marks it expired and returns the frozen funds to available.

Withdrawal Status

StatusMeaning
signedBackend signed the release message; user has not (yet) submitted on-chain. Funds are frozen.
confirmedOn-chain SpotWithdrawal event observed. Funds left the vault.
expiredSignature passed its deadline before being used. Funds returned to available.

Endpoint Index

Public

MethodPathDescription
GET/wallet/tokensList supported tokens for the deposit/withdraw UI

Authenticated

MethodPathDescription
GET/spot/balancesList spot balances for the current user
POST/spot/transferMove USDT between perp margin and spot wallet (JWT only)
GET/spot/depositsList the user's confirmed deposits
POST/spot/withdraw/requestRequest a withdrawal signature (JWT only)
GET/spot/withdrawalsList the user's withdrawals
GET/spot/withdrawals/:idGet one withdrawal by id

Notes

  • The spot subsystem is gated by SPOT_ENABLED=true server-side. When disabled, /spot/* routes are not mounted (404), and /wallet/tokens returns the perp collateral only.
  • The BSC listener catches up at ~30 blocks/sec from a cold start. After catch-up, deposits are credited within ~40 seconds (20 confirmations × ~2s/block on BSC).