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
| Concept | Meaning |
|---|---|
| Spot balance | Per-(user, token) pair: available + frozen. Frozen funds are reserved for an in-flight withdrawal. |
| Internal transfer | Atomic move between perp margin and spot wallet (USDT only in MVP). On-chain TX not involved. |
| Deposit | User sends ERC-20 to the BSC vault contract. A backend listener credits the spot balance after confirmation_depth blocks. |
| Withdrawal | Two-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
| Environment | REST |
|---|---|
| Testnet | https://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:
| Scheme | Header | Allowed for |
|---|---|---|
| Bearer JWT | Authorization: Bearer <JWT> | All spot endpoints |
| API Key | X-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
| Item | Network | Address / Value |
|---|---|---|
| BSC chain id | BSC Testnet | 97 |
Vault contract (ZtdxSpotVault) | BSC Testnet | 0x4Fe0b354c5865ee9deb979a99030d757ae47664a |
| DF token (ERC-20) | BSC Testnet | 0x8063a43ed88397c1B10DA23dcC60ba1E7A0Bf555 |
| DF decimals | — | 18 |
| Perp collateral (USDT) | Arbitrum Sepolia (421614) | 0xfA70c5A9221d239Cd51DBf48967ABc79d7B9D61d |
| USDT decimals | — | 6 |
EIP-712 Domain (Withdrawal)
Withdraw signatures are EIP-712 typed data verified by the vault contract.
Domain
| Field | Value |
|---|---|
name | ZTDX Spot Vault |
version | 1 |
chainId | 97 (BSC Testnet) |
verifyingContract | Vault 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
| Status | Meaning |
|---|---|
signed | Backend signed the release message; user has not (yet) submitted on-chain. Funds are frozen. |
confirmed | On-chain SpotWithdrawal event observed. Funds left the vault. |
expired | Signature passed its deadline before being used. Funds returned to available. |
Endpoint Index
Public
| Method | Path | Description |
|---|---|---|
| GET | /wallet/tokens | List supported tokens for the deposit/withdraw UI |
Authenticated
| Method | Path | Description |
|---|---|---|
| GET | /spot/balances | List spot balances for the current user |
| POST | /spot/transfer | Move USDT between perp margin and spot wallet (JWT only) |
| GET | /spot/deposits | List the user's confirmed deposits |
| POST | /spot/withdraw/request | Request a withdrawal signature (JWT only) |
| GET | /spot/withdrawals | List the user's withdrawals |
| GET | /spot/withdrawals/:id | Get one withdrawal by id |
Notes
- The spot subsystem is gated by
SPOT_ENABLED=trueserver-side. When disabled,/spot/*routes are not mounted (404), and/wallet/tokensreturns 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).