> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qfex.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 程序化出入金

> 通过 REST API 为 QFEX 账户入金，并申请加密资产或美元提现

QFEX 通过独立的 REST 服务提供出入金接口：

| 环境 | 基础 URL |
| - | - |
| 生产环境 | `https://banker.qfex.com` |
| UAT | `https://banker.qfex.io` |

API 支持：

* Arbitrum One（`ARBITRUM_ONE`）上的 USDC 充值与提现；
* TRON（`TRON`）上的 USDT 充值与提现；
* 通过 ACH push、电汇或 FedNow 存入美元；
* 通过 ACH 或电汇提取美元。

> [!WARNING]
>
> 必须在地址接口返回的准确网络上，发送该接口返回的资产。发送错误资产或使用错误网络可能导致资金损失。

## 开始之前

使用出入金 API 前，请完成账户验证并接受 QFEX 条款。可使用钱包会话或 API 密钥认证。

使用钱包会话时，先完成[钱包认证](https://docs.qfex.com/cn/api-reference/wallet-authentication.md)，并在每个请求中发送返回的访问令牌：

```text theme={null}
Authorization: Bearer <access-token>
```

对于在开发者设置中创建的 API 密钥，各接口所需权限如下：

| 接口 | 所需 API 密钥权限 |
| - | - |
| `GET /address` | `view_balance` |
| `GET /withdrawal-addresses` | `view_balance` |
| `POST /withdraw` | `deposit_withdraw` |
| `POST /address/rotate` | `deposit_withdraw` |
| 美元出入金及银行账户接口 | `deposit_withdraw` |

[创建密钥](https://docs.qfex.com/cn/api-reference/api-keys.md)时启用 `view_balance`，即可读取充值地址及已登记提现地址。`deposit_withdraw` 权限要求访问范围为所有账户。

出入金适用于主账户。要为子账户入金，请先存入主账户，再[向子账户转账](https://docs.qfex.com/cn/api-reference/subaccounts-api#transfer-between-primary-account-and-subaccounts)。

## 使用 API 密钥认证

每个请求均需以下请求头：

```text theme={null}
x-qfex-public-key: <public-key>
x-qfex-hmac-signature: <hex-encoded-signature>
x-qfex-nonce: <unique-hex-nonce>
x-qfex-timestamp: <current-unix-time-in-seconds>
```

每次请求时：

1. 生成密码学安全、以十六进制编码且不超过 100 个字符的 nonce。
2. 获取以秒为单位的当前 Unix 时间。
3. 使用 API 私密密钥，对 UTF-8 字符串 `${nonce}:${timestamp}` 进行 HMAC-SHA256 签名。
4. 将签名编码为十六进制，并发送全部四个请求头。

时间戳与 QFEX 服务器时间的差值必须在五分钟内。不要重复使用 nonce。HTTP 方法、路径、查询参数和请求体不属于签名字符串。

以下辅助函数为每个请求创建新签名，并返回解码后的 JSON 响应：

```python Python theme={null}
import hashlib
import hmac
import json
import os
import secrets
import time
from urllib.error import HTTPError
from urllib.request import Request, urlopen

BASE_URL = os.getenv("QFEX_FUNDING_URL", "https://banker.qfex.com")
PUBLIC_KEY = os.environ["QFEX_PUBLIC_KEY"]
SECRET_KEY = os.environ["QFEX_SECRET_KEY"]

def qfex_request(path, method="GET", body=None):
    nonce = secrets.token_hex(16)
    timestamp = str(int(time.time()))
    message = f"{nonce}:{timestamp}".encode()
    signature = hmac.new(
        SECRET_KEY.encode(), message, hashlib.sha256
    ).hexdigest()

    headers = {
        "accept": "application/json",
        "x-qfex-public-key": PUBLIC_KEY,
        "x-qfex-hmac-signature": signature,
        "x-qfex-nonce": nonce,
        "x-qfex-timestamp": timestamp,
    }
    data = None
    if body is not None:
        headers["content-type"] = "application/json"
        data = json.dumps(body).encode()

    request = Request(BASE_URL + path, data=data, headers=headers, method=method)
    try:
        with urlopen(request) as response:
            response_body = response.read()
            return json.loads(response_body) if response_body else None
    except HTTPError as error:
        detail = error.read().decode()
        raise RuntimeError(f"QFEX returned {error.code}: {detail}") from error
```

```javascript Node.js theme={null}
import crypto from "node:crypto";

const baseUrl = process.env.QFEX_FUNDING_URL ?? "https://banker.qfex.com";
const publicKey = process.env.QFEX_PUBLIC_KEY;
const secretKey = process.env.QFEX_SECRET_KEY;

if (!publicKey || !secretKey) {
  throw new Error("Set QFEX_PUBLIC_KEY and QFEX_SECRET_KEY");
}

async function qfexRequest(path, { method = "GET", body } = {}) {
  const nonce = crypto.randomBytes(16).toString("hex");
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const signature = crypto
    .createHmac("sha256", secretKey)
    .update(`${nonce}:${timestamp}`)
    .digest("hex");

  const response = await fetch(`${baseUrl}${path}`, {
    method,
    headers: {
      accept: "application/json",
      ...(body === undefined ? {} : { "content-type": "application/json" }),
      "x-qfex-public-key": publicKey,
      "x-qfex-hmac-signature": signature,
      "x-qfex-nonce": nonce,
      "x-qfex-timestamp": timestamp,
    },
    body: body === undefined ? undefined : JSON.stringify(body),
  });

  const text = await response.text();
  if (!response.ok) {
    throw new Error(`QFEX returned ${response.status}: ${text}`);
  }
  return text ? JSON.parse(text) : undefined;
}
```

将私密密钥保存在服务端秘密存储中，不要放入浏览器代码、移动应用、日志、URL 或源码仓库。

## 加密资产充值

请求目标网络的充值地址：

```http theme={null}
GET /address?network=ARBITRUM_ONE
```

```python theme={null}
deposit = qfex_request("/address?network=ARBITRUM_ONE")
print(deposit["address"])
```

响应示例：

```json theme={null}
{
  "address": "0x0123456789abcdef0123456789abcdef01234567",
  "network": "ARBITRUM_ONE",
  "asset": "USDC"
}
```

对于 TRON 上的 USDT，使用 `network=TRON`。响应将包含 `"asset": "USDT"` 和 `"network": "TRON"`。

API 密钥调用 `GET /address` 需要 `view_balance` 权限。该接口返回账户现有地址，或在首次调用时创建地址，不会轮换现有地址。收到地址后，请通过钱包或托管服务提交链上转账。获取地址本身不会发起转账。

> [!NOTE]
>
> 当前最低入金金额和所需确认数请查看 QFEX 出入金界面。只有检测到充值且达到确认要求后，资金才会入账。

## 列出已登记提现地址

使用具备 `view_balance` 权限的 `GET /withdrawal-addresses`，列出当前已认证用户在各网络上登记的加密资产提现地址。用户身份来自认证信息，不提供用户 ID 查询参数。

```http theme={null}
GET /withdrawal-addresses
```

```python theme={null}
registered = qfex_request("/withdrawal-addresses")
print(registered["addresses"])
```

`200 OK` 响应示例：

```json theme={null}
{
  "addresses": [
    {
      "id": 42,
      "withdraw_address": "0x89abcdef0123456789abcdef0123456789abcdef",
      "network": "ARBITRUM_ONE",
      "nickname": "My wallet",
      "created_at": "2026-10-09T10:00:00Z"
    }
  ]
}
```

`nickname` 可以为 `null`。没有已登记地址时，返回 `{"addresses": []}`。此接口仅列出地址，不会登记、修改或删除地址。申请提现前，请先在 QFEX 登记目的地址。

## 加密资产提现

通过开发者设置创建的 API 密钥调用 `POST /withdraw` 时，需要 `deposit_withdraw` 权限。通过 [Builder 钱包注册](https://docs.qfex.com/cn/api-reference/builder-wallet-registration.md)签发的密钥，也支持向已登记目的地址提取加密资产。单独的 `view_balance` 或 `execute_orders` 权限不能授权提现。

每笔加密资产提现都必须使用当前已认证用户在目标网络上登记的地址。该要求适用于所有 API 密钥和钱包会话 JWT。QFEX 在扣款前检查目的地址；若未为该用户及网络登记，则返回 `403`。若无法检查已登记地址，请求会被拒绝且不会扣款。Arbitrum 地址匹配不区分字母大小写；TRON 地址必须完全一致。

提交需扣除的总金额、来自 `GET /withdrawal-addresses` 的目的地址及其网络：

```http theme={null}
POST /withdraw
Content-Type: application/json

{
  "amount": 100,
  "address": "0x89abcdef0123456789abcdef0123456789abcdef",
  "network": "ARBITRUM_ONE"
}
```

```python theme={null}
qfex_request(
    "/withdraw",
    method="POST",
    body={
        "amount": 100,
        "address": "0x89abcdef0123456789abcdef0123456789abcdef",
        "network": "ARBITRUM_ONE",
    },
)
```

成功时返回 `200 OK` 和空响应体。`amount` 为从 QFEX 账户扣除的总金额。QFEX 扣除适用提现费用后发送剩余金额；提交前请在 QFEX \*\*Deposit / Withdraw（充值／提现）\*\*界面查看当前费用。

TRON 提现需要有效的 TRON 地址，并将 `network` 设为 `TRON`。即使当前 API 省略网络时默认使用 `ARBITRUM_ONE`，也应始终显式指定网络。

> [!WARNING]
>
> `POST /withdraw` 不具备幂等性。重试可能创建第二笔提现并再次扣款。若发送请求后客户端超时，请先核对账户历史，再决定是否重试。

成功响应表示 QFEX 已接受提现请求并完成账户扣款，不保证链上转账已经完成；请求仍可能等待提交或人工审核。

## 美元入金

发起银行转账前，先获取所需转账附言：

```http theme={null}
GET /fiat-address?currency=usd&payment_rail=ach_push
```

有效入金渠道为 `ach_push`、`wire` 和 `fednow`。

```python theme={null}
instructions = qfex_request(
    "/fiat-address?currency=usd&payment_rail=ach_push"
)
print(instructions["deposit_message"])
```

响应示例：

```json theme={null}
{
  "deposit_message": "YOUR-UNIQUE-REFERENCE"
}
```

接口返回账户专属附言，而非完整收款银行信息。请从 QFEX \*\*Deposit / Withdraw（充值／提现）\*\*界面获取当前收款信息，通过银行发起转账，并将 `deposit_message` 原样填写在转账参考、附言或备注字段中。

## 美元提现

美元提现需要关联目的银行账户。首次创建后，保存返回的 QFEX 银行账户 ID。

### 1. 关联银行账户

```http theme={null}
POST /bank-account?currency=usd
Content-Type: application/json

{
  "first_name": "Jane",
  "last_name": "Doe",
  "routing_number": "123456789",
  "bank_name": "Example Bank",
  "account_number": "000123456789",
  "type": "checking",
  "street_line_1": "100 Main Street",
  "street_line_2": "Apt 4",
  "country": "USA",
  "state": "NY",
  "city": "New York",
  "postal_code": "10001"
}
```

`type` 必须为 `checking` 或 `savings`。`country` 为 ISO 3166-1 三字母国家代码，美国地址必须提供 `state`。街道地址须包含门牌号码。

```python theme={null}
bank_account = qfex_request(
    "/bank-account?currency=usd",
    method="POST",
    body={
        "first_name": "Jane",
        "last_name": "Doe",
        "routing_number": "123456789",
        "bank_name": "Example Bank",
        "account_number": "000123456789",
        "type": "checking",
        "street_line_1": "100 Main Street",
        "country": "USA",
        "state": "NY",
        "city": "New York",
        "postal_code": "10001",
    },
)
print(bank_account["id"])
```

API 返回 `201 Created`：

```json theme={null}
{
  "id": "11111111-1111-4111-8111-111111111111"
}
```

### 2. 申请提现

```http theme={null}
POST /fiat-withdraw
Content-Type: application/json

{
  "amount": 100,
  "currency": "usd",
  "payment_rail": "ach_push",
  "account_id": "11111111-1111-4111-8111-111111111111"
}
```

`amount` 至少为 10。提现渠道使用 `ach_push` 或 `wire`。

```python theme={null}
qfex_request(
    "/fiat-withdraw",
    method="POST",
    body={
        "amount": 100,
        "currency": "usd",
        "payment_rail": "ach_push",
        "account_id": bank_account["id"],
    },
)
```

成功时返回 `200 OK` 和空响应体。与加密资产提现相同，接受请求不等于最终结算，请求可能需要人工审核。法币手续费在结算前从申请金额中扣除。

移除已关联银行账户时，使用一组新的认证请求头发送 `DELETE /bank-account/{id}`。

## 安全处理错误

| 状态 | 含义 |
| - | - |
| `400` | 金额、地址、网络、支付渠道或银行信息无效，或可提现余额不足。 |
| `401` | 缺少或无效的认证信息，或尚未接受条款。 |
| `403` | 缺少接口所需权限、账户无法使用出入金功能，或加密资产目的地址未为该用户及网络登记。 |
| `404` | 银行账户 ID 不存在或不属于该用户。 |
| `5xx` | QFEX 或资金服务提供商无法完成操作。 |

大多数结构化错误包含 `title`、`status` 和 `detail`；校验或服务提供商错误还可能包含 `errors` 数组。可记录 HTTP 状态和响应体，但绝不能记录 API 私密密钥或完整银行账户信息。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.