跳到主要内容

充值与提现

保证金存放在各链的 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 到账后为 confirmedcreated_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),单次使用
expirynow + 3600 秒,过期签名失效
backend_signatureWithdraw(address user,uint256 amount,uint256 nonce,uint256 deadline) 的 EIP-712 签名

发起时余额变化:available -= Xfrozen += 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 -= Xavailable += 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,等待确认
confirmedWithdraw 事件已索引,资金已离开系统
cancelled用户取消
failed链上交易失败
expired签名过期,资金已解冻

余额模型

字段含义
available可用于开仓或提现
frozen被挂单保证金或进行中的提现锁定
totalavailable + frozen
可提现 = available + min(未实现盈亏, 0)

浮亏会减少可提现金额;超额请求返回 422 insufficient_balancedetails 内含 availablefrozenunrealized_pnlwithdrawablerequested)。

错误

HTTPerror原因
400金额错误、不支持的代币
401JWT 缺失 / 过期
403forbiddenAPI Key 会话尝试提现
404未知提现 ID
422insufficient_balance超过 withdrawable
400withdrawal_expired签名已过 expiry
400invalid_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 只能使用一次,因此过期签名无法重放。