充值与提现
保证金存放在各链的 Sparky Vault 合约。充值是普通合约调用,由后端监听事件入账 ;提现需要后端签发的 EIP-712 签名,Vault 据此校验金额与链下余额一致。
本页所有接口位于 /api/v1 下,需要 EIP-712 登录 得到的 Authorization: Bearer <JWT>。API Key 会话不能提现(403 Forbidden)。
金额
| 场景 | 格式 | 示例 |
|---|---|---|
| REST 请求 / 响应 | 人类可读小数字符串 | "100.5" |
| 合约调用 | 代币最小单位整数(USDT 6 位精度) | 100500000 |
链上单位 = REST 金额 × 10^6
当前仅支持 USDT 作为保证金。
充值
客户端
├─① POST /api/v1/deposit/prepare → Vault 地址 + 代币地址
├─② ERC-20 approve(vault_address, amount) (链上)
├─③ Vault.deposit(amount, referralCode) (链上)→ 发出 Deposit 事件
│ └─ 后端索引器记入 `available`
└─④ GET /api/v1/deposit/history → 确认到账
1. 准备
POST /api/v1/deposit/prepare
Content-Type: application/json
{ "token": "USDT", "amount": "100" }
响应 — 200
{
"contract_address": "0xVaultContractAddress",
"token_address": "0xUSDTContractAddress",
"amount": "100",
"estimated_gas": 120000
}
2 – 3. 链上调用
const amountWei = BigInt(Math.floor(parseFloat(amount) * 1e6));
const usdt = new ethers.Contract(token_address, ERC20_ABI, signer);
await (await usdt.approve(contract_address, amountWei)).wait();
// 无推荐码时 referralCode 传 bytes32(0)
const vault = new ethers.Contract(contract_address, VAULT_ABI, signer);
await (await vault.deposit(amountWei, ethers.ZeroHash)).wait();
后端约每个出块周期(约 12 秒)轮询一次链上事件;Deposit 事件被索引后余额即到账。
4. 历史
GET /api/v1/deposit/history
{
"deposits": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"token": "USDT",
"amount": "100.000000",
"tx_hash": "0xabc123...",
"status": "confirmed",
"created_at": 1700000000
}
]
}
最近 100 条,倒序。status 到账后为 confirmed;created_at 为 Unix 秒。
提现
客户端
├─① POST /api/v1/withdraw/request → 冻结资金,返回 EIP-712 签名
├─② Vault.withdraw(user, amount, nonce, expiry, backend_signature) (链上,1 小时有效)
├─③ POST /api/v1/withdraw/{id}/confirm { tx_hash }
└─④ 后端监听 Withdraw 事件 → 解冻,状态 = confirmed
1. 发起
POST /api/v1/withdraw/request
Content-Type: application/json
{ "token": "USDT", "amount": "50" }
响应 — 200
{
"withdraw_id": "550e8400-e29b-41d4-a716-446655440001",
"token": "0xUSDTContractAddress",
"amount": "50000000",
"backend_signature": "0x1234...abcd",
"nonce": 3,
"expiry": 1700003600,
"vault_address": "0xVaultContractAddress"
}
| 字段 | 说明 |
|---|---|
amount | 已是链上单位(× 10^6),直接传合约 |
nonce | 读自 Vault.withdrawNonces(user),单次使用 |
expiry | now + 3600 秒,过期签名失效 |
backend_signature | 对 Withdraw(address user,uint256 amount,uint256 nonce,uint256 deadline) 的 EIP-712 签名 |
发起时余额变化:available -= X,frozen += X。
2. 链上调用
const vault = new ethers.Contract(vault_address, VAULT_ABI, signer);
await vault.withdraw(userAddress, amount, nonce, expiry, backend_signature);
3. 确认
POST /api/v1/withdraw/{withdraw_id}/confirm
{ "tx_hash": "0xdef456..." }
仅对 signed 状态有效,确认后进入 submitted;索引到 Withdraw 事件后变为 confirmed。
取消
DELETE /api/v1/withdraw/{withdraw_id}/cancel
仅对 signed 状态有效,立即释放冻结(frozen -= X,available += X)。未提交的请求 1 小时后也会自动过期(60 秒一轮的扫描把它标为 expired 并解冻)。
查询
GET /api/v1/withdraw/{withdraw_id}
GET /api/v1/withdraw/history
GET /api/v1/withdraw/limit
{
"withdrawals": [
{
"id": "550e8400-...",
"token": "USDT",
"amount": "50.000000",
"nonce": 3,
"expiry": 1700003600,
"backend_signature": "0x1234...abcd",
"tx_hash": "0xdef456...",
"status": "confirmed",
"created_at": 1700000000
}
]
}
| 状态 | 含义 |
|---|---|
signed | 签名已生成,等待链上调用(1 小时) |
submitted | 已收到 tx_hash,等待确认 |
confirmed | Withdraw 事件已索引,资金已离开系统 |
cancelled | 用户取消 |
failed | 链上交易失败 |
expired | 签名过期,资金已解冻 |
余额模型
| 字段 | 含义 |
|---|---|
available | 可用于开仓或提现 |
frozen | 被挂单保证金或进行中的提现锁定 |
total | available + frozen |
可提现 = available + min(未实现盈亏, 0)
浮亏会减少可提现金额;超额请求返回 422 insufficient_balance(details 内含 available、frozen、unrealized_pnl、withdrawable、requested)。
错误
| HTTP | error | 原因 |
|---|---|---|
400 | — | 金额错误、不支持的代币 |
401 | — | JWT 缺失 / 过期 |
403 | forbidden | API Key 会话尝试提现 |
404 | — | 未知提现 ID |
422 | insufficient_balance | 超过 withdrawable |
400 | withdrawal_expired | 签名已过 expiry |
400 | invalid_status | 对非 signed 记录执行确认 / 取消 |
Vault 接口(相关部分)
event Deposit(address indexed user, uint256 amount, bytes32 referralCode);
event Withdraw(address indexed user, uint256 amount, uint256 nonce);
function deposit(uint256 amount, bytes32 referralCode) external;
function withdraw(address user, uint256 amount, uint256 nonce, uint256 expiry, bytes calldata signature) external;
function getBalance(address user) external view returns (uint256);
function withdrawNonces(address user) external view returns (uint256);
注意:
- 同一时间只能存在一笔
signed状态的提现;先等待确认或取消再发起下一笔。 - 索引器每轮最多扫描 1000 个区块;拥堵时入账可能延迟。
- 每个
nonce只能使用一次,因此过期签名无法重放。