> ## 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.

# 钱包身份认证

> 使用以太坊钱包以程序方式注册或登录 QFEX，并使用返回的用户会话

QFEX 支持基于 EIP-4361 标准的 Sign-In with Ethereum（SIWE）。用户签署一条可读消息，以证明其拥有某个 EVM 地址。QFEX 验证签名后，返回包含访问令牌和刷新令牌的用户会话。

对于需要 API 密钥来交易、读取数据及向已登记地址提取加密资产的开发者集成，请参阅 [Builder 钱包注册](https://docs.qfex.com/cn/api-reference/builder-wallet-registration.md)。该流程使用 QFEX 生成的授权消息，并返回密钥而不是用户会话。

钱包认证同时用于注册和登录：

* 某地址首次提供有效签名时，创建 QFEX 用户。
* 此后同一地址签名会恢复该用户。
* 签署消息不会提交链上交易、不消耗 gas，也不会授权 QFEX 转移资产。

> [!NOTE]
>
> QFEX 目前支持兼容以太坊的钱包认证，尚未启用 Solana 钱包认证。钱包可以连接任意 EVM 链，因为签名是证明地址所有权，而非执行交易。

## 开始之前

安装 QFEX 网页终端使用的 SIWE 工具：

```sh theme={null}
npm install viem@2.55.10
```

还需要：

* QFEX Auth URL：`https://verify.qfex.com`。
* 可公开的 QFEX Auth 客户端密钥。
* 已加入 QFEX Auth 重定向白名单的登录 URL。
* 如从浏览器直接调用 QFEX REST 服务，需要该来源获得 CORS 许可。
* 启用 CAPTCHA 防护时，需要 Cloudflare Turnstile 令牌。
* EIP-1193 钱包提供者，例如通过 EIP-6963、wagmi、MetaMask 或 Phantom 发现的提供者。

请联系 [support@qfex.com](mailto:support@qfex.com)，获取当前可公开的 Auth 客户端密钥并登记集成 URL。可公开密钥可以放在浏览器代码中，但绝不能暴露 QFEX 私密密钥或具有特权的服务端密钥。

SIWE 会根据已登记的重定向 URL 验证消息中的域名与 URI。签名时使用登录页面的真实 URL；不要填写并未托管该登录页面的 QFEX 或第三方域名。

## 完整 SIWE 流程

以下示例请求钱包地址和链信息，构造 EIP-4361 消息，请求钱包签名，并用已签名消息交换 QFEX 会话：

```javascript theme={null}
import { getAddress, stringToHex } from "viem";
import { createSiweMessage, generateSiweNonce } from "viem/siwe";

const QFEX_AUTH_URL = "https://verify.qfex.com";
const QFEX_AUTH_CLIENT_KEY = "YOUR_QFEX_AUTH_CLIENT_KEY";

export async function signInToQfex({
  provider,
  signInUrl,
  captchaToken,
}) {
  const accounts = await provider.request({ method: "eth_requestAccounts" });
  if (!Array.isArray(accounts) || !accounts[0]) {
    throw new Error("The wallet did not return an account");
  }

  const address = getAddress(accounts[0]);
  const chainIdHex = await provider.request({ method: "eth_chainId" });
  const chainId = Number.parseInt(chainIdHex, 16);
  const pageUrl = new URL(signInUrl);

  const message = createSiweMessage({
    address,
    chainId,
    domain: pageUrl.host,
    issuedAt: new Date(),
    nonce: generateSiweNonce(),
    statement: "Sign in to QFEX",
    uri: pageUrl.href,
    version: "1",
  });

  const signature = await provider.request({
    method: "personal_sign",
    params: [stringToHex(message), address],
  });

  const response = await fetch(
    `${QFEX_AUTH_URL}/auth/v1/token?grant_type=web3`,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        apikey: QFEX_AUTH_CLIENT_KEY,
        Authorization: `Bearer ${QFEX_AUTH_CLIENT_KEY}`,
      },
      body: JSON.stringify({
        chain: "ethereum",
        message,
        signature,
        ...(captchaToken
          ? { gotrue_meta_security: { captcha_token: captchaToken } }
          : {}),
      }),
    },
  );

  const result = await response.json();
  if (!response.ok) {
    throw new Error(result.msg ?? result.message ?? "Wallet authentication failed");
  }

  return result;
}
```

在浏览器中，以当前页面 URL 调用：

```javascript theme={null}
const session = await signInToQfex({
  provider: selectedWalletProvider,
  signInUrl: window.location.href,
  captchaToken,
});
```

提供者必须实现 EIP-1193 `request` 方法。完整顺序如下：

1. 调用 `eth_requestAccounts` 请求地址。
2. 调用 `eth_chainId` 读取当前链。
3. 使用地址、链 ID、已登记 URL、当前时间、新 nonce 及完全一致的声明 `Sign in to QFEX` 构造 EIP-4361 消息。
4. 调用钱包的 `personal_sign`。
5. 将消息和签名发送至 `POST /auth/v1/token?grant_type=web3`。
6. 恢复现有用户或创建新用户，并获得 QFEX 会话。

> [!WARNING]
>
> 向用户展示钱包确认界面，并由钱包完成签名。不要索取、传输或保存钱包私钥或助记词。

## 选择正确的注入式钱包提供者

只安装一个钱包扩展时，可以使用 `window.ethereum`。若安装了多个钱包，请使用 EIP-6963 或钱包库发现它们，并传入用户选择的提供者。

不要静默选择第一个钱包。签名前，用户应能看到正在授权的地址和域名。

## SIWE 消息要求

签署的消息必须包含：

* 经过校验和格式化的 EVM 地址；
* 已登记登录页面的真实主机名；
* 完整的已登记登录 URI；
* 版本 `1`；
* 钱包当前的数字链 ID；
* 新生成的字母数字 nonce；
* 当前 `Issued At` 时间戳；
* 完全一致的声明 `Sign in to QFEX`。

在签名前立即生成消息。QFEX 会拒绝无效签名、重复使用或格式错误的消息，以及域名或 URI 不在许可范围内的消息。超过 10 分钟的消息视为过期。

非浏览器集成应显式传入已登记的登录 URL，并使用提供 EIP-1193 兼容签名接口的钱包或托管服务。不要仅为了自动化该流程而加载原始私钥。

## 认证 HTTP 请求

已签名凭据交换请求如下：

```http theme={null}
POST /auth/v1/token?grant_type=web3
Host: verify.qfex.com
Content-Type: application/json
apikey: <QFEX_AUTH_CLIENT_KEY>
Authorization: Bearer <QFEX_AUTH_CLIENT_KEY>

{
  "chain": "ethereum",
  "message": "<EIP-4361 message>",
  "signature": "<0x-prefixed signature>"
}
```

启用 CAPTCHA 防护时，还需包含 Turnstile 结果：

```json theme={null}
{
  "gotrue_meta_security": {
    "captcha_token": "<TURNSTILE_TOKEN>"
  }
}
```

成功响应包含：

```json theme={null}
{
  "access_token": "<JWT>",
  "token_type": "bearer",
  "expires_in": 3600,
  "expires_at": 1790000000,
  "refresh_token": "<REFRESH_TOKEN>",
  "user": {
    "id": "11111111-1111-4111-8111-111111111111"
  }
}
```

两种令牌都属于凭据，不要写入 URL、日志、分析事件或源码仓库。

## 使用访问令牌

### REST API

将会话访问令牌作为 bearer token 发送：

```javascript theme={null}
const response = await fetch("https://api.qfex.com/user/positions", {
  headers: {
    Authorization: `Bearer ${session.access_token}`,
  },
});
```

同一个 bearer token 也适用于[程序化出入金 API](https://docs.qfex.com/cn/api-reference/programmatic-funding.md)：

```javascript theme={null}
const response = await fetch(
  "https://banker.qfex.com/address?network=ARBITRUM_ONE",
  {
    headers: {
      Authorization: `Bearer ${session.access_token}`,
    },
  },
);
```

浏览器直接请求要求页面来源被目标 QFEX 服务允许。后端使用用户 bearer token 时不受浏览器 CORS 限制。

### 交易 WebSocket

在连接 URL 和认证消息中传递访问令牌：

```javascript theme={null}
const token = session.access_token;
const ws = new WebSocket(
  `wss://trade.qfex.com/?jwt=${encodeURIComponent(token)}`,
);

ws.addEventListener("open", () => {
  ws.send(
    JSON.stringify({
      type: "auth",
      params: { jwt: token },
    }),
  );
});
```

账户选择与 Builder 归因见[交易 WebSocket 身份认证](https://docs.qfex.com/cn/websocket/channels/trade/authenticate.md)。

## 刷新会话

访问令牌会过期。继续调用 QFEX 服务前，用刷新令牌换取新会话：

```javascript theme={null}
export async function refreshQfexSession(refreshToken) {
  const response = await fetch(
    `${QFEX_AUTH_URL}/auth/v1/token?grant_type=refresh_token`,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        apikey: QFEX_AUTH_CLIENT_KEY,
        Authorization: `Bearer ${QFEX_AUTH_CLIENT_KEY}`,
      },
      body: JSON.stringify({ refresh_token: refreshToken }),
    },
  );

  const result = await response.json();
  if (!response.ok) {
    throw new Error(result.msg ?? result.message ?? "Session refresh failed");
  }
  return result;
}
```

刷新令牌会轮换。每次刷新成功后，保存新返回的刷新令牌并丢弃旧值。浏览器应用应根据其威胁模型采用安全存储；服务端应用应使用加密凭据存储。

## 账户行为与限制

* 新钱包地址会创建新的 QFEX 账户，必须完成准入流程后才能交易或转移资金。
* 钱包认证不能绕过地区限制、身份验证、条款接受或 2FA。
* 若用户已设置第二认证因素，钱包登录后仍需完成现有 QFEX MFA 流程。
* 已在 QFEX 登记的地址始终恢复同一用户。
* QFEX 目前不允许先用邮箱、Google 或 Apple 注册的账户附加钱包身份。使用不同地址启动钱包认证，可能创建独立账户。
* 首先通过钱包注册的用户，可在 QFEX 安全设置中添加支持的邮箱或社交登录方式。
* 目前无法解绑或更换钱包身份。

## 常见错误

| 错误 | 处理方式 |
| - | - |
| 没有兼容提供者 | 安装或解锁 EVM 钱包，或显式传入所选的 EIP-1193 提供者。 |
| 用户拒绝请求 | 终止流程，等待用户重新发起；不要自动重试签名弹窗。 |
| SIWE 消息无效 | 使用版本 `1`、当前时间戳、新字母数字 nonce 及校验和地址重新构造。 |
| 域名或 URI 被拒绝 | 使用真实登录页面 URL，并请 QFEX 支持团队登记。 |
| 需要 CAPTCHA 或验证码无效 | 完成 Turnstile，将新令牌放入 `gotrue_meta_security.captcha_token`。 |
| 触发限流 | 采用退避，并等待后再允许下一次尝试。 |
| 会话成功但 API 拒绝访问 | 完成 QFEX 准入、必要条款、身份检查及已启用的 MFA 挑战。 |


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