## 资金 API 文档

资金接口用于从组织账户向 Infini UID 转账、提现、查询提现状态与手续费，以及查询 USDT、USDC 和 USD 可用余额。

所有资金接口前缀：

`/v2/funds`

### 认证与权限

所有接口使用 [Ed25519 v2 认证](/zh/docs/en/4-authorization)，每次请求（包括 GET）都必须携带 Key ID、时间戳、nonce、Digest 和签名。签名必须包含完整查询串。`fund.withdraw`、`card.create`、`card.reveal`、`payout.write` 必须配置匹配的非空 IP 白名单。

| 权限 | 适用接口 |
|  --- | --- |
| `fund.withdraw` | `POST /v2/funds/swap`、`POST /v2/funds/internal-transfer`、`POST /v2/funds/withdraw`、`GET /v2/funds/withdraw/status`、`POST /v2/funds/withdraw/status/batch`、`GET /v2/funds/withdraw/fees`、`GET /v2/funds/balances` |


> **IP 白名单：** 包含 `fund.withdraw` 权限的 API Key，必须在商户后台创建或更新时配置非空的 IP 白名单。


### 提现幂等

创建提现时，`request_id` 为可选字段：

- 不传时，由 Infini 生成提现请求 ID，并保持原有提现逻辑。调用方重试时如果没有复用同一个 ID，则不具备幂等保障。
- 传入时必须为 UUID，并作为本次提现的幂等键。
- 同一商户使用相同 `request_id` 重试时，接口会返回之前创建的提现订单信息，并设置 `is_duplicate: true`。
- 一个 `request_id` 只能用于一笔逻辑提现。请勿将其复用于不同金额、链、代币或目标地址。


### 响应信封

通过网关鉴权后，应用层响应统一使用 `code` / `message` / `data` 信封。`code === 0` 表示业务成功，非零 `code` 表示错误，详情见 `message`。部分下游业务错误可能以 HTTP 200 返回，此时 `code` 非零且 `data` 为 `null`。

认证失败返回 HTTP 401；权限或 IP 不符返回 HTTP 403。其他业务错误仍可能返回 HTTP 200，请同时检查 HTTP 状态与业务 code。

### 账户换汇

**POST** `/v2/funds/swap`，需要 `fund.withdraw` 权限。

仅支持当前 API Key 所属商户的 `USDC`、`USDT`、`USD` WALLET 账户之间兑换，转入、转出币种不能相同，两个账户都必须已存在。涉及 USD 时必须已有 USD Cash Account；USDC ↔ USDT 不要求 USD 账户。接口不会自动开户，也不会使用 Easy Earn 余额。

| 字段 | 类型 | 必填 | 说明 |
|  --- | --- | --- | --- |
| request_id | string (UUID) | 是 | 幂等键；不支持 BW/RW 格式 |
| source_currency | string | 是 | 转出币种：USDC、USDT 或 USD |
| target_currency | string | 是 | 转入币种：USDC、USDT 或 USD；与转出不同 |
| amount | string | 是 | 源账户总扣款，包含 FX 手续费，必须大于零 |
| min_output_amount | string | 否 | 目标币种最低净到账金额，不能为负数 |


USD 支持两位小数，USDC/USDT 支持六位；最低到账金额按目标币种精度校验。金额使用普通十进制字符串，不支持科学计数法；换算成最小单位后不能超过有符号 64 位整数范围。

实际 FX 费率大于零时，手续费按源币种最小单位向上取整：USD 为 0.01，USDC/USDT 为 0.000001。商户显式配置零费率或币种组合免手续费时仍收零，不适用最低手续费。

接口按执行时的适用汇率和费率同步成交，不需要 quote，也不锁定未来汇率。`price` 表示 1 单位源币可兑换的目标币数量；`fx_fee` 已包含在总扣款 `amount` 中，`fx_fee_currency` 与源币种相同。实际到账 `output_amount = 按目标精度截断((amount - fx_fee) × price)`。下方价格和费用仅为示例。

实际净到账低于 `min_output_amount`，或截断后为零时，拒绝兑换，不扣款、不入账。不传最低金额表示接受执行时价格；传零表示不设置最低限制。账户缺失、资金冻结、余额不足、参数错误或限流也会拒绝操作。业务结果以响应信封中的 `code` 为准。

同一商户用同一 UUID 重试已完成请求，会返回首次成交的到账金额、价格和费用，并设置 `is_duplicate=true`，更换 API Key 后仍有效。同一 ID 修改币种、金额或最低到账金额会报参数冲突。不同商户和不同操作之间隔离幂等键。等值十进制金额会归一化；未传最低金额与显式传零视为不同参数。失败请求不保存成功结果，也不保留汇率；超时后请使用原 ID 和原参数重试。

```json
{
  "request_id": "e94b4e88-36c2-4550-907e-839742cf5fae",
  "source_currency": "USDC",
  "target_currency": "USD",
  "amount": "100",
  "min_output_amount": "99"
}
```

```json
{
  "code": 0,
  "message": "",
  "data": {
    "request_id": "e94b4e88-36c2-4550-907e-839742cf5fae",
    "output_amount": "99.5",
    "price": "1",
    "fx_fee": "0.5",
    "fx_fee_currency": "USDC",
    "is_duplicate": false
  }
}
```

### 向 Infini UID 转账

**POST** /v2/funds/internal-transfer

从当前认证组织向 Infini UID 对应的收款方转账。

- **所需权限：** `fund.withdraw`
- V1 仅支持 `USDT`、`USDC` 或 `USD` 同币种转账。
- 内部转账不收取手续费：发送方扣款金额等于收款方到账金额。
- V1 成功时会同步完成账本转账，并返回 `status: completed`。


#### Request Body

| 字段 | 类型 | 必填 | 说明 |
|  --- | --- | --- | --- |
| request_id | string (UUID) | 否 | 幂等键。不传时由 Infini 生成 |
| recipient_uid | string | 是 | 收款方 Infini UID；不能属于转出组织自身 |
| token_type | string | 是 | 收款账户币种：`USDT`、`USDC` 或 `USD` |
| source_currency | string | 否 | 扣款账户币种；不传时默认等于 `token_type`。V1 要求与 `token_type` 相同 |
| amount | string | 是 | 扣款和到账金额，必须为正数。`USDT` 和 `USDC` 最多支持六位小数，最小为 `0.000001`；`USD` 最多支持两位小数，最小为 `0.01` |
| note | string | 否 | 可选转账备注 |


#### 请求示例

```json
{
  "request_id": "4b4436ba-90a1-4f26-940d-97177a7a3400",
  "recipient_uid": "12345678",
  "token_type": "USDC",
  "source_currency": "USDC",
  "amount": "10.25",
  "note": "Customer refund"
}
```

#### 响应示例

```json
{
  "code": 0,
  "message": "",
  "data": {
    "request_id": "4b4436ba-90a1-4f26-940d-97177a7a3400",
    "status": "completed",
    "is_duplicate": false
  }
}
```

`request_id` 为可选字段。传入时必须为 UUID，并作为幂等键。同一组织使用已经完成的 `request_id` 重试时，会返回原请求 ID，并设置 `is_duplicate: true`。一个请求 ID 只能用于一笔逻辑转账，请勿将其复用于不同转账信息。

#### 错误码

| HTTP 状态码 | Code | 说明 |
|  --- | --- | --- |
| 401 | `401` | 应用层无法认证 API Key |
| 403 | `403` | API Key 缺少 `fund.withdraw` 权限，或 IP 白名单校验未通过 |
| 400 或 200 | `30013` | 请求参数无效，包括 UUID、UID、币种、金额、精度、跨币种转账、向自身转账，或请求 ID 已属于其他组织 |
| 200 | `10005` | 找不到发送方或收款方 |
| 200 | `10030` | 转出组织的 Owner 处于转账保护状态 |
| 200 | `20012` | 扣款账户的可用余额不足 |
| 200 | `30005` | 转账金额超过剩余操作限额 |
| 200 | `30015` | 转出组织被限制内部转账 |
| 200 | `30034` | 扣款币种为 `USD`，但组织未开通 USD Cash Account |
| 500 | `500` | 发生未预期的内部错误或下游服务错误 |


### 提取组织资金

**POST** /v2/funds/withdraw

从当前认证组织账户向外部钱包发起提现。

- **所需权限：** `fund.withdraw`


#### Request Body

| 字段 | 类型 | 必填 | 说明 |
|  --- | --- | --- | --- |
| request_id | string (UUID) | 否 | 幂等键。不传时由 Infini 生成 |
| chain | string | 是 | 公网链名称，例如 `ETHEREUM` 或 `ARBITRUM` |
| token_type | string | 是 | 代币类型，例如 `USDT` 或 `USDC` |
| source_currency | string | 否 | 扣款账户币种；不传时默认等于 `token_type`。提取 `USDT` 或 `USDC` 时可设为 `USD`，从组织的 USD Cash Account 扣款；不支持其他跨币种组合 |
| amount | string | 是 | 从扣款账户最多扣除的金额；第六位小数后的数字会直接截断而非四舍五入；`source_currency` 为 `USD` 时最多支持两位小数。截断后金额必须至少为 `1` 且大于相关费用 |
| wallet_address | string | 是 | 与所选链匹配的目标钱包地址 |
| note | string | 否 | 可选的提现备注 |


请以 **`GET /v2/funds/withdraw/fees`** 返回的当前可用链/代币组合和手续费为准。示例中的链和代币名称均使用大写。

#### 请求示例

```json
{
  "request_id": "e94b4e88-36c2-4550-907e-839742cf5fae",
  "chain": "ETHEREUM",
  "token_type": "USDT",
  "source_currency": "USD",
  "amount": "10.00",
  "wallet_address": "0x5f716e5775b18409917e2a2f0762d29d6c385cb0",
  "note": "Treasury withdrawal"
}
```

#### 响应示例

```json
{
  "code": 0,
  "message": "",
  "data": {
    "request_id": "e94b4e88-36c2-4550-907e-839742cf5fae",
    "status": "pending",
    "is_duplicate": false
  }
}
```

幂等重试时，`data.is_duplicate` 为 `true`，`data.status` 为已有提现单的当前状态。

#### 错误码

| HTTP 状态码 | Code | 说明 |
|  --- | --- | --- |
| 401 | `401` | 应用层无法认证 API Key |
| 403 | `403` | API Key 缺少 `fund.withdraw` 权限、IP 白名单校验未通过，或所选链/代币组合未启用提现 |
| 200 | `30001` | 核心提现配置拒绝了所选代币 |
| 200 | `30002` | 组织的扣款资金账户不存在 |
| 200 | `30003` | 扣款账户的可用余额不足 |
| 200 | `30005` | 提现金额超过当日剩余限额 |
| 200 | `30007` | 提现金额低于最低要求，或金额不大于相关手续费 |
| 200 | `30012` | 所选链/代币组合没有可用的提现手续费配置 |
| 400 或 200 | `30013` | 请求参数无效，例如 UUID、链/代币组合、金额、精度、扣款币种或钱包地址无效 |
| 200 | `30022` | 目标地址是 Infini 内部地址；请改用内部转账 |
| 200 | `30023` | 扣除锁定或预留资金后，可用余额不足 |
| 200 | `30034` | `source_currency` 为 `USD`，但组织未开通 USD Cash Account |
| 200 | `80016` | 暂时无法获取所需的资产价格或汇率 |
| 500 | `500` | 发生未预期的内部错误或下游服务错误 |


核心资金服务返回的错误使用标准应用层信封；即使 `code` 非零，HTTP 状态也可能是 200。由网关直接拒绝的 Ed25519 校验错误可能不包含应用层错误码。

### 查询提现状态

**GET** /v2/funds/withdraw/status?request_id={request_id}

查询当前认证组织名下提现单的状态和金额信息。

- **所需权限：** `fund.withdraw`
- Ed25519 签名路径必须包含 `?request_id={request_id}`。


#### 请求参数

| 字段 | 类型 | 必填 | 说明 |
|  --- | --- | --- | --- |
| request_id | string (UUID) | 是 | 提现请求 ID |


#### 响应示例

```json
{
  "code": 0,
  "message": "",
  "data": {
    "request_id": "e94b4e88-36c2-4550-907e-839742cf5fae",
    "status": "completed",
    "amount": "11",
    "fee": "0.1",
    "actual_amount": "10.9",
    "transaction_hash": "0xabc123",
    "chain": "ETHEREUM",
    "token_type": "USDT",
    "source_currency": "USD",
    "gas_fee": "0.08",
    "gas_fee_currency": "USD",
    "fx_fee": "0.02",
    "fx_fee_currency": "USD",
    "fee_paid_by": "BENEFICIARY"
  }
}
```

#### 响应字段

| 字段 | 类型 | 说明 |
|  --- | --- | --- |
| request_id | string | 提现请求 ID |
| status | string | 当前提现状态 |
| amount | string | 从扣款账户扣除的总金额 |
| fee | string | 兼容保留的 Gas 费字段；新接入请使用 `gas_fee` 和 `gas_fee_currency` |
| actual_amount | string | 以 `token_type` 计价的实际上链金额 |
| transaction_hash | string | 链上交易哈希；尚未提交时为空字符串 |
| chain | string | 公网链名称 |
| token_type | string | 代币类型 |
| source_currency | string | 扣款账户币种 |
| gas_fee | string | 本次提现向用户收取的 Gas 费 |
| gas_fee_currency | string | `gas_fee` 的币种 |
| fx_fee | string | 跨币种提现的换汇手续费；同币种提现为 `0` |
| fx_fee_currency | string | `fx_fee` 的币种 |
| fee_paid_by | string | 费用承担方：`PAYER` 或 `BENEFICIARY` |


#### 状态说明

| 状态 | 说明 |
|  --- | --- |
| `pending` | 提现已创建，等待提交或审核 |
| `processing` | 交易已提交，等待确认 |
| `completed` | 提现已完成 |
| `failed` | 提现失败 |


### 分页查询提现状态

**POST** /v2/funds/withdraw/status/batch

按创建时间范围分页查询当前认证组织名下的提现状态。

- **所需权限：** `fund.withdraw`
- `start_time` 和 `end_time` 使用 Unix 秒级时间戳，并构成闭区间。
- 返回结果按 `created_at` 降序排列。
- `page` 默认为 `1`；`page_size` 默认为 `20`，最大为 `100`。
- 时间范围内没有匹配提现时仍返回 HTTP 200，且 `withdrawals` 为空数组。
- 时间范围或分页参数无效时返回 HTTP 400；无法完成整个查询的内部或下游错误返回 HTTP 500。


#### Request Body

| 字段 | 类型 | 必填 | 说明 |
|  --- | --- | --- | --- |
| start_time | 整数 | 是 | 提现创建时间的起始值，Unix 秒级时间戳，包含该时间点 |
| end_time | 整数 | 是 | 提现创建时间的结束值，Unix 秒级时间戳，包含该时间点；必须大于等于 `start_time` |
| page | 整数 | 否 | 页码，默认为 `1` |
| page_size | 整数 | 否 | 每页提现数量，默认为 `20`，最大为 `100` |


#### 请求示例

```json
{
  "start_time": 1787500000,
  "end_time": 1787599999,
  "page": 1,
  "page_size": 20
}
```

本接口为带请求体的 POST 请求，Ed25519 请求需携带请求体 `Digest`，并签名路径 `/v2/funds/withdraw/status/batch`。

#### 响应示例

```json
{
  "code": 0,
  "message": "",
  "data": {
    "withdrawals": [
      {
        "request_id": "e94b4e88-36c2-4550-907e-839742cf5fae",
        "created_at": 1787564636,
        "status": "completed",
        "amount": "11",
        "fee": "0.1",
        "actual_amount": "10.9",
        "transaction_hash": "0xabc123",
        "chain": "ETHEREUM",
        "token_type": "USDT",
        "source_currency": "USDT",
        "gas_fee": "0.1",
        "gas_fee_currency": "USDT",
        "fx_fee": "0",
        "fx_fee_currency": "USDT",
        "fee_paid_by": "BENEFICIARY"
      }
    ],
    "total": 1,
    "page": 1,
    "page_size": 20,
    "total_pages": 1
  }
}
```

每个条目的金额、费用、链和状态语义与单笔查询一致，并额外返回 `created_at`。分页信息描述时间范围内的完整匹配结果，而不仅是当前页。

### 查询提现手续费

**GET** /v2/funds/withdraw/fees

返回当前已启用的公网链/代币组合及其手续费。手续费是动态配置，客户端应调用本接口，不要硬编码手续费表。

- **所需权限：** `fund.withdraw`


#### 响应示例

```json
{
  "code": 0,
  "message": "",
  "data": {
    "fees": [
      {
        "chain": "ARBITRUM",
        "token_type": "USDC",
        "withdraw_fee": "0.5"
      },
      {
        "chain": "ETHEREUM",
        "token_type": "USDT",
        "withdraw_fee": "2"
      }
    ]
  }
}
```

每个 `withdraw_fee` 都使用对应的 `token_type` 计价。

### 查询可用余额

**GET** /v2/funds/balances

仅返回当前认证组织的 USDT、USDC 和 USD Cash `AVAILABLE_BALANCE`。

- **所需权限：** `fund.withdraw`


#### 响应示例

```json
{
  "code": 0,
  "message": "",
  "data": {
    "available_balance_usdt": "120.50",
    "available_balance_usdc": "75.25",
    "available_balance_usd": "1000.00"
  }
}
```

如果组织没有某个币种的余额记录，对应值返回 `"0"`。其中，USD Cash Account 未开通时，`available_balance_usd` 返回 `"0"`。