Appearance
提现与站内转账
一个接口 POST /v1/transactions 同时支持链上提现和站内转账。平台根据收款方自动决定结算方式:
| 收款方 | 结算方式 | 交易 kind | 手续费 |
|---|---|---|---|
| 外部地址(含其他租户的充值地址) | 链上(ON_CHAIN) | WITHDRAWAL | 提现手续费 withdrawFeeRaw;链上网络费由租户金库代付,用户成本通过手续费承担 |
| 本租户某个钱包的充值地址 | 站内(OFF_CHAIN),自动识别 | INTERNAL_TRANSFER | 跨用户:租户设置的站内转账费;同一用户的钱包之间:0 |
本租户的钱包 ID(WALLET)或用户(END_USER) | 站内(OFF_CHAIN) | INTERNAL_TRANSFER | 同上 |
| 平台运营地址(热钱包、归集、Gas 地址等) | 拒绝 | — | 422 INTERNAL_DESTINATION_NOT_ALLOWED |
站内结算不上链、即时到账(除非触发审批),没有 txHash。
站内转账的收款方也要记账
如果你方维护用户余额,收到 kind = INTERNAL_TRANSFER、status = COMPLETED 的交易时,要同时给付款方(source.customerRefId)扣减 amountRaw + feeRaw,给收款方(destination.customerRefId)增加 amountRaw。向本租户充值地址的“提现”会变成站内转账,收款方不会收到 DEPOSIT 记录。
交易对象
查询接口和 TRANSACTION_* 回调的 data 使用同一个交易对象(充值、提现、站内转账通用):
| 字段 | 类型 | 说明 |
|---|---|---|
id | string (UUID) | 交易 ID(查询、取消都用它) |
kind | string | DEPOSIT / WITHDRAWAL / INTERNAL_TRANSFER |
settlement | string | ON_CHAIN / OFF_CHAIN |
status | string | 对外状态,见 交易状态 |
subStatus | string | null | 补充说明,见 交易状态 |
assetId | string | 资产 ID |
networkKey | string | 资产所在网络 |
decimals | integer | 资产精度 |
amountRaw | string | 金额(最小单位)。提现/转账为收款方收到的金额;充值为链上转入金额 |
feeRaw | string | null | 向用户收取的手续费。提现/转账另外从付款方扣除;充值在入账时从 amountRaw 中扣除(入账前为 null) |
netAmountRaw | string | 仅充值且已确定手续费时出现:实际入账金额 = amountRaw - feeRaw |
networkFeeRaw | string | null | 链上提现实际支付的网络费(租户金库代付,用户成本通过手续费承担,仅供参考),在提现最终确认后写入;单位是该网络原生币的最小单位(例如 USDT_ERC20 提现时为 ETH 的 wei),不是本资产的单位。充值与站内转账为 null |
networkFeeAssetId | string | null | networkFeeRaw 所用的币种,即该网络原生币的资产 ID(例如 ETH、TRX),按其 decimals(见 GET /v1/supported_assets)换算 |
source | object | 付款方:walletId、address、customerRefId、uid(不适用时为 null;充值的 source.address 为链上转出地址) |
destination | object | 收款方:walletId、address、customerRefId、uid(链上提现只有 address) |
txHash | string | null | 链上交易哈希;站内转账为 null;提现在广播后才有 |
externalTxId | string | null | 你方提交时传入的 externalTxId |
note | string | null | 提交时传入的备注 |
createdAt / updatedAt | string | RFC 3339 |
json
{
"id": "0192a4e0-5c00-7d00-8e00-0f1011121314",
"kind": "WITHDRAWAL",
"settlement": "ON_CHAIN",
"status": "COMPLETED",
"subStatus": null,
"assetId": "USDT_ERC20",
"networkKey": "ethereum",
"decimals": 6,
"amountRaw": "10000000",
"feeRaw": "2500000",
"networkFeeRaw": "1084522000000000",
"networkFeeAssetId": "ETH",
"source": { "walletId": "0192a4c1-8b10-7e22-b3a1-6f1e2d3c4b5a", "address": "0x8ba1f109551bd432803012645ac136ddd64dba72", "customerRefId": "u-10001", "uid": "48213907" },
"destination": { "walletId": null, "address": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", "customerRefId": null, "uid": null },
"txHash": "0x9a8b7c6d5e4f30211203f4e5d6c7b8a9f0e1d2c3b4a5968778695a4b3c2d1e0f",
"externalTxId": "wd-20261003-000001",
"note": null,
"createdAt": "2026-10-03T07:00:00.100000Z",
"updatedAt": "2026-10-03T07:02:41.870000Z"
}发起提现 / 转账
http
POST /v1/transactions请求头
| 头 | 必填 | 说明 |
|---|---|---|
X-API-Key、Authorization | 是 | 见 认证与签名 |
Content-Type | 是 | application/json |
Idempotency-Key | 条件 | 请求体没有 externalTxId 时必填;有 externalTxId 时忽略本头。见 幂等 |
X-User-2FA-Code | 条件 | 租户开启“API 出金需终端用户两步验证”时必填,见 下文 |
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
assetId | string | 是 | 资产 ID,必须与付款钱包同一链族 |
source | object | 是 | 付款方 |
source.type | string | 是 | 固定为 WALLET |
source.id | string (UUID) | 是 | 付款钱包 ID(本租户的钱包) |
destination | object | 是 | 收款方,三选一,见下表 |
amountRaw | string | 是 | 收款金额(最小单位),必须大于 0。手续费另外从付款钱包扣除 |
externalTxId | string | 否(强烈推荐) | 你方的唯一单号,1–128 字符,租户内唯一;同时作为幂等键 |
note | string | 否 | 备注,原样出现在交易对象中 |
destination 的三种形式:
destination.type | 其他字段 | 说明 |
|---|---|---|
WALLET | id:钱包 ID | 本租户任意钱包(本人或他人),站内结算 |
END_USER | uid 或 customerRefId(必须且只能给一个) | 本租户另一个用户,转入其与付款钱包同链族的默认钱包,站内结算。不能指定自己(用 WALLET 在本人钱包间划转) |
ONE_TIME_ADDRESS | oneTimeAddress.address:地址字符串 | 任意地址。本租户充值地址 → 自动站内结算;其他 → 链上提现 |
链上提现示例:
json
{
"assetId": "USDT_ERC20",
"source": { "type": "WALLET", "id": "0192a4c1-8b10-7e22-b3a1-6f1e2d3c4b5a" },
"destination": { "type": "ONE_TIME_ADDRESS", "oneTimeAddress": { "address": "0x90f79bf6eb2c4f870365e785982e1f101e93b906" } },
"amountRaw": "10000000",
"externalTxId": "wd-20261003-000001"
}按用户站内转账示例:
json
{
"assetId": "USDT_ERC20",
"source": { "type": "WALLET", "id": "0192a4c1-8b10-7e22-b3a1-6f1e2d3c4b5a" },
"destination": { "type": "END_USER", "customerRefId": "u-20002" },
"amountRaw": "2000000",
"externalTxId": "tr-20261003-000007"
}响应 202 Accepted。响应体不是交易对象,而是提交回执,形状取决于结算方式(用 settlement 区分);后续状态请查询交易对象。
链上提现(settlement = ON_CHAIN):
json
{
"settlement": "ON_CHAIN",
"withdrawalId": "0192a4e0-5b00-7c00-8d00-0e0f10111213",
"transactionId": "0192a4e0-5c00-7d00-8e00-0f1011121314",
"status": "SUBMITTED",
"subStatus": null,
"state": "REQUESTED",
"assetId": "USDT_ERC20",
"amountRaw": "10000000",
"feeRaw": "2500000",
"reservedRaw": "12500000",
"destination": "0x90f79bf6eb2c4f870365e785982e1f101e93b906"
}| 字段 | 说明 |
|---|---|
transactionId | 交易 ID,用于 GET /v1/transactions/{id} 与取消 |
withdrawalId | 平台内部提现单 ID |
status / subStatus | 对外状态,提交时为 SUBMITTED |
state | 平台内部状态(仅供排查,可能增加取值,请勿依赖) |
reservedRaw | 已从可用余额冻结的金额 = amountRaw + feeRaw |
destination | 规范化后的收款地址 |
站内结算(settlement = OFF_CHAIN,包括 ONE_TIME_ADDRESS 被识别为本租户地址的情况):
json
{
"settlement": "OFF_CHAIN",
"transferId": "0192a4e1-0100-7200-8300-040506070809",
"transactionId": "0192a4e1-0200-7300-8400-05060708090a",
"status": "COMPLETED",
"fromWalletId": "0192a4c1-8b10-7e22-b3a1-6f1e2d3c4b5a",
"toWalletId": "0192a4c3-1111-7222-8333-444455556666",
"assetId": "USDT_ERC20",
"amount": "2000000",
"fee": "0"
}| 字段 | 说明 |
|---|---|
status | COMPLETED(已到账)或 PENDING_AUTHORIZATION(命中租户审批策略,资金已冻结,待管理员审批) |
amount / fee | 金额与手续费(最小单位字符串;注意此处字段名没有 Raw 后缀) |
幂等重放:用相同的 externalTxId(或 Idempotency-Key)和相同请求体重试,返回与第一次完全相同的状态码和响应体,不会重复扣款。
常见错误
| HTTP | code | 场景 |
|---|---|---|
| 422 | BAD_REQUEST | JSON 格式或字段类型错误;source.type must be WALLET;externalTxId must be 1-128 chars;无 externalTxId 时缺少 Idempotency-Key;END_USER needs exactly one of uid / customerRefId;amount must be positive |
| 403 | FORBIDDEN | 付款用户已冻结(user is frozen) |
| 403 | TWO_FACTOR_REQUIRED / TWO_FACTOR_INVALID / COOLDOWN_ACTIVE | 开启了终端用户两步验证时,见 下文 |
| 403 | BLOCKED_BY_POLICY | 站内转账被租户策略拦截 |
| 404 | NOT_FOUND | 付款或收款钱包不存在 / 不属于本租户(wallet not found) |
| 404 | RECIPIENT_NOT_FOUND | END_USER 收款人不存在,或已被冻结(recipient unavailable) |
| 409 | IDEMPOTENCY_CONFLICT | 幂等键对应不同请求体;上一次请求仍在处理;externalTxId already used(被另一个 API Key 使用过) |
| 422 | INSUFFICIENT_BALANCE | 可用余额不足以支付 amountRaw + 手续费 |
| 422 | INVALID_ADDRESS | 地址格式错误、EIP-55 校验失败、BTC 网络不匹配、SOL 非钱包地址 |
| 422 | INTERNAL_DESTINATION_NOT_ALLOWED | 收款地址是平台运营地址 |
| 422 | SAME_WALLET_TRANSFER | 收款方就是付款钱包本身 |
| 422 | SAME_USER_USE_WALLET_TRANSFER | END_USER 指向付款用户自己 |
| 422 | RECIPIENT_NO_WALLET | 收款用户在该链族下没有默认钱包 |
| 422 | ASSET_NOT_SUPPORTED | 未知 assetId |
| 422 | ASSET_MISMATCH | 资产与钱包链族不符,或收付双方钱包链族不同,或资产不可用 |
| 422 | ASSET_DISABLED | 该资产/网络当前不允许链上提现 |
| 422 | NETWORK_DISABLED | 资产所在网络未对本租户开通 |
| 422 | WALLET_NOT_ACTIVE | 付款钱包不是 ACTIVE,或收款钱包不能收款 |
| 422 | BELOW_MIN_WITHDRAWAL | 链上提现金额低于 minWithdraw |
| 422 | LIMIT_EXCEEDED | 超过单笔限额或每日限额 |
| 429 | RATE_LIMITED | 限流(重新签名后退避重试) |
| 500 / 503 | INTERNAL / PROVIDER_UNAVAILABLE / SIGNER_UNAVAILABLE | 可重试:用相同请求体与 externalTxId 重试 |
链上提现被受理(202)之后的反洗钱、审批、签名、广播等环节失败不会以 HTTP 错误返回,而是体现为交易状态(REJECTED、FAILED 等),见 交易状态。
终端用户两步验证(X-User-2FA-Code)
租户后台 设置 → API 出金需终端用户两步验证(apiRequireUser2fa),默认关闭:
- 关闭(默认):API 签名即可发起提现/转账,用户的二次验证由交易所自己负责。
- 开启:
POST /v1/transactions(所有收款方类型)必须带请求头X-User-2FA-Code: <付款用户当前的 6 位 TOTP 码>。
开启后的校验与错误:
| 情况 | 结果 |
|---|---|
付款用户没有绑定 TOTP(用户对象 totpEnabled = false) | 403 TWO_FACTOR_REQUIRED(bind TOTP before moving funds) |
| 用户处于安全冷静期 | 403 COOLDOWN_ACTIVE |
缺少 X-User-2FA-Code | 403 TWO_FACTOR_REQUIRED(TOTP code required) |
| 15 分钟内校验超过 10 次 | 429 RATE_LIMITED |
| 验证码错误,或该验证码已被使用过(每个验证码只能用一次) | 403 TWO_FACTOR_INVALID |
TOTP 由用户在托管 C 端绑定;B 端接口不提供绑定能力,开启此选项前请确认你的用户都已绑定。
估算手续费
http
POST /v1/transactions/estimate_fee按与提现相同的规则计算手续费,并告诉你该地址会走链上还是站内。不冻结资金、不创建交易。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
assetId | string | 是 | 资产 ID |
sourceWalletId | string (UUID) | 是 | 付款钱包 ID |
amountRaw | string | 是 | 收款金额(最小单位),大于 0 |
destination | string | 是 | 收款地址字符串 |
json
{
"assetId": "USDT_ERC20",
"sourceWalletId": "0192a4c1-8b10-7e22-b3a1-6f1e2d3c4b5a",
"amountRaw": "10000000",
"destination": "0x90f79bf6eb2c4f870365e785982e1f101e93b906"
}响应 200 OK
json
{
"assetId": "USDT_ERC20",
"amountRaw": "10000000",
"feeRaw": "2500000",
"totalRaw": "12500000",
"destination": "0x90f79bf6eb2c4f870365e785982e1f101e93b906",
"isInternal": false,
"settlement": "ON_CHAIN",
"networkFeePaidBy": "PLATFORM",
"validUntil": "2026-10-03T07:02:00.100000Z"
}| 字段 | 说明 |
|---|---|
feeRaw | 将收取的手续费(链上:提现手续费;站内:站内转账费或 0) |
totalRaw | 付款钱包需要的可用余额 = amountRaw + feeRaw |
destination | 规范化后的地址 |
isInternal / settlement | 是否为本租户地址(站内结算) |
networkFeePaidBy | 历史标记固定 PLATFORM,表示链上付款方;租户金库代付网络费,用户充提成本通过手续费承担 |
validUntil | 估算有效期(约 2 分钟)。实际提现时会重新计算 |
常见错误:与 发起提现 中地址、资产、金额、限额相关的错误相同(INVALID_ADDRESS、BELOW_MIN_WITHDRAWAL、ASSET_DISABLED、LIMIT_EXCEEDED、INTERNAL_DESTINATION_NOT_ALLOWED 等);钱包不存在为 404 NOT_FOUND。
查询交易
http
GET /v1/transactions/{id}响应 200 OK:交易对象。常见错误:404 NOT_FOUND(transaction not found)。
按 externalTxId 查询
http
GET /v1/transactions/external_tx_id/{externalTxId}用你方单号查询(路径参数需 URL 编码)。提交超时后用它确认交易是否已创建。
响应 200 OK:交易对象。常见错误:404 NOT_FOUND。
交易列表
http
GET /v1/transactions?customerRefId=u-10001&kind=DEPOSIT&status=COMPLETED&createdFrom=2026-10-01T00:00:00Z&createdTo=2026-10-02T00:00:00Z&limit=200查询参数(均可选,可组合)
| 参数 | 类型 | 说明 |
|---|---|---|
walletId | UUID | 付款或收款钱包为该钱包 |
customerRefId | string | 付款或收款用户为该用户 |
kind | string | DEPOSIT / WITHDRAWAL / INTERNAL_TRANSFER |
status | string | 对外状态,如 COMPLETED |
assetId | string | 资产 ID |
createdFrom | RFC 3339 | 创建时间下界(含) |
createdTo | RFC 3339 | 创建时间上界(不含) |
limit | integer | 1–200,默认 50 |
before | UUID | 上一页的 nextBefore |
响应 200 OK
json
{
"items": [ { "id": "0192a4c9-2f00-7aaa-8bbb-ccccdddd0001", "kind": "DEPOSIT", "status": "COMPLETED", "…": "交易对象的其余字段" } ],
"nextBefore": null
}按创建时间倒序。常见错误:422 BAD_REQUEST(createdFrom/createdTo must be RFC 3339)。
取消提现
http
POST /v1/transactions/{id}/cancel取消尚未进入签名阶段的链上提现,冻结的 amountRaw + feeRaw 退回可用余额。无请求体。
可以取消的状态:SUBMITTED、PENDING_AUTHORIZATION、QUEUED(含 subStatus = INSUFFICIENT_FUNDS_PLATFORM)。进入 PENDING_SIGNATURE 及之后的状态不能取消。站内转账不能取消。
响应 200 OK
json
{
"withdrawalId": "0192a4e0-5b00-7c00-8d00-0e0f10111213",
"transactionId": "0192a4e0-5c00-7d00-8e00-0f1011121314",
"status": "CANCELLED",
"subStatus": "CANCELLED_BY_USER",
"state": "CANCELLED",
"assetId": "USDT_ERC20",
"amountRaw": "10000000",
"feeRaw": "2500000",
"reservedRaw": "12500000",
"destination": "0x90f79bf6eb2c4f870365e785982e1f101e93b906"
}同时推送 TRANSACTION_STATUS_UPDATED(status = CANCELLED)。
常见错误
| HTTP | code | 场景 |
|---|---|---|
| 404 | NOT_FOUND | 交易不存在 |
| 409 | INVALID_STATE | only withdrawals can be cancelled;withdrawal can no longer be cancelled |