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

# 钱包所有权小组件 - Travel Rule 控制权证明

> Didit 托管小组件，通过消息签名、Satoshi 测试、截图或自我声明证明客户控制加密钱包，用于 Travel Rule 合规。

钱包所有权小组件是一个托管页面，用于证明您的某位客户控制特定的加密钱包地址。根据欧盟 TFR，VASP 在向自托管（非托管）钱包转入或从中转出超过 sunrise 阈值的资金之前，必须验证该钱包的所有权。小组件让 Didit 为您收集这一证明，您无需自行构建签名流程。

验证通过的证明会将匹配的[钱包地址簿条目](https://docs.didit.me/zh/transaction-monitoring/travel-rule.md#地址簿)标记为 `is_ownership_verified: true`，从而使关联的 [Travel Rule 转账](https://docs.didit.me/zh/transaction-monitoring/travel-rule.md)能够从 `UNCONFIRMED_OWNERSHIP` 状态继续推进。

## 何时由 Didit 为您创建

您很少需要自行创建会话。
当您的 [Travel Rule 设置](https://docs.didit.me/zh/transaction-monitoring/travel-rule.md#设置)中的 `auto_wallet_verification` 开启时（默认开启），只要转账需要终端用户证明其钱包控制权，Didit 就会在创建交易时自动创建小组件会话：

* 转账处于 `UNCONFIRMED_OWNERSHIP` 状态，即对手方钱包在提交时已[被声明为自托管](https://docs.didit.me/zh/transaction-monitoring/travel-rule.md#已声明的自托管钱包)。
* 转账为转出，并处于 `COUNTERPARTY_VASP_NOT_FOUND` 状态，即无法识别对手方 VASP，因此所有权证明是完成转账的剩余途径。

自动创建的会话会在交易的 `action_required` 块中返回（`type: "wallet_ownership"`，并包含要打开的 `url`），SDK 会在[客户端提交](https://docs.didit.me/zh/transaction-monitoring/sdk-transaction-submission.md#用户所需操作)时自动启动该会话。
Didit 为您创建的入账转账（sunrise 登记、入账 `TRP` 查询，或 `INTERNAL` 通道的入账段）即使进入 `UNCONFIRMED_OWNERSHIP` 状态，也永远不会自动创建会话。
如需在任何转账存在之前预先验证某个钱包，如已关闭 `auto_wallet_verification`，或针对上述某笔入账转账，请使用 `POST /v3/travel-rule/widget-session/` 手动创建会话。

## 证明内容

该小部件最多提供四种证明方式。显示哪些方式取决于您创建会话的方式以及您的 [Travel Rule 设置](https://docs.didit.me/zh/transaction-monitoring/travel-rule.md#设置)：

| 方式 | 工作原理 | 何时提供 |
| - | - | - |
| `MESSAGE_SIGNING` | 客户使用其钱包对 Didit 签发的挑战字符串进行签名。支持 EVM、Solana、Bitcoin 和 Tron 钱包（对于 Bitcoin，支持 legacy、嵌套 SegWit、原生 SegWit 和 Taproot 地址）。Didit 会根据地址验证签名；如果使用的账户与待验证账户不同，签名将被拒绝，并显示清晰的切换账户提示。EVM 智能合约账户（ERC-4337 智能钱包）也可以签名：Didit 会在钱包所声明的链上向账户自身的合约查询签名是否有效（ERC-1271）；对于尚未部署的账户，Didit 会检查该钱包返回的 ERC-6492 签名，且不会部署账户或发送任何交易。智能合约账户都会在所有受支持的 EVM 链上进行检查。详见下文的[消息签名验证合约](#消息签名验证合约)。安装在客户浏览器中的钱包通过 EIP-6963 和 Wallet Standard 协议被发现，因此每个扩展程序都会作为独立的签名选项出现。目前不支持通过二维码等方式配对独立的移动钱包应用。 | 对于受支持的链系列，始终提供。 |
| `SATOSHI_TEST` | 客户从钱包向您控制的充值地址发送一笔极小金额的链上交易。Didit 的监测服务会检查发送方（对于 EVM 智能合约账户，检查账户本身，即使交易由 ERC-4337 bundler 提交也一样）、金额，以及充值是否已在链上确认；确认后自动完成证明。详见下文的[Satoshi 测试验证合约](#satoshi-测试验证合约)。 | 仅当您在创建时传入 `satoshi_deposit_address`，且链为 EVM、Bitcoin 或 Solana 链时提供，因此自动创建的会话绝不提供此方式。 |
| `SCREENSHOT` | 客户上传一张显示地址的截图（JPEG/PNG/WebP，最大 10 MB）。与其他方式不同，它从不自动验证：您的合规团队会在截图审核队列（[`GET /v3/travel-rule/wallet-proofs/`](https://docs.didit.me/zh/management-api/travel-rule/list-wallet-proofs)）中进行审核，并通过（[`POST .../decision/`](https://docs.didit.me/zh/management-api/travel-rule/decide-wallet-proof)）批准或拒绝；或者在控制台中该交易的 **Travel Rule** 标签页中确认转账，或通过 [`POST .../travel-rule/ownership/`](https://docs.didit.me/zh/management-api/travel-rule/confirm-ownership) 确认（适用于状态为 `UNCONFIRMED_OWNERSHIP` 的转账，或被暂停的入站转账）。 | 仅当您的设置中 `allow_screenshot_proof` 为 `true` 时提供。 |
| `SELF_DECLARATION` | 客户在没有密码学证明的情况下声明所有权，这是在管辖区允许的情况下的最后手段。 | 仅当 `allow_self_declaration` 为 `true`，且您的 [`ownership_validation_mode`](https://docs.didit.me/zh/transaction-monitoring/travel-rule#所有权验证模式) 接受关联转账的声明时提供：在 `FULL` 模式下绝不提供；在 `SIMPLIFIED` 模式下，仅当金额低于 `simplified_validation_threshold` 时提供。如果发起的声明不被该模式接受，将返回 `403`。 |

### 消息签名验证合约

提交签名后，一旦证明您控制该钱包，将返回 `200` 和 `status: "VERIFIED"`。
如果返回 `400`，证明保持开启状态，客户可以重新签名，同时返回以下 `code` 值之一：

| `code` | 含义 |
| - | - |
| `SIGNATURE_INVALID` | 该签名无法证明钱包所有权：其他账户或密钥进行了签名，或消息不一致。对于智能合约账户，其合约表示该签名不是其本身的签名。 |
| `SIGNATURE_UNSUPPORTED_SCHEME` | 钱包使用的签名格式 Didit 无法验证，例如 Bitcoin 多签或 BIP-322 完整签名。 |
| `SIGNATURE_LOOKUP_FAILED` | 该钱包可能是智能合约账户，但当前无法检查其所在链。此响应下证明绝不会被验证；重新签名将再次进行检查。 |

### Satoshi 测试验证合约

验证 Satoshi 测试充值是异步的，因为交易必须先在链上被看到，然后再被确认。
提交充值的 `txid` 后，若充值尚未在链上确认，将返回 `200` 和 `status: "PENDING"`；`txid` 会被保留，而非丢弃。
充值确认后，将返回 `200` 和 `status: "VERIFIED"`。
对于确实错误的充值，将返回 `400` 和 `status: "REJECTED"`：例如来自其他钱包、金额与充值地址不符、早于挑战时间，或该交易已证明过其他钱包。此时证明关闭，只要会话仍有剩余尝试次数，客户即可发起新的证明。
如果充值尚未被索引，同样返回 `200` 和 `status: "PENDING"`；如果 Didit 端查询失败，则返回 `400` 和代码 `DEPOSIT_LOOKUP_FAILED`。两种情况下证明均保持开启，Didit 会继续检查。
如果在看到有效充值之前测试窗口已过期，将返回 `410`。

提交 `txid` 后，后台任务每五分钟重新检查一次待处理的充值，并在交易确认后完成证明，因此客户无需重新提交 `txid`。
当您传入 `satoshi_deposit_address` 时，小部件会话自身的 `expires_at` 会延长至至少 24 小时的 Satoshi 测试窗口，以便充值有时间完成确认，避免会话先于充值过期。

## 流程

**1. 创建小组件会话**

调用 `POST /v3/travel-rule/widget-session/`，并传入需要验证的钱包。您可以选择将其关联到一笔转账，这样在钱包所有权得到证明后，该转账会自动推进。

**2. 打开返回的 URL**

将客户引导至响应中的 `url`。客户在该页面完成证明，无需 Didit 账户或 API 密钥，URL 中的令牌即为身份认证凭据。

**3. 检测完成情况**

证明验证通过后，小组件会将地址簿条目标记为已验证；如果会话关联了转账，还会推进该转账。请关注 `travel_rule.status.updated` 事件（针对关联转账），或轮询小组件上下文。

### 创建小组件会话

本示例将会话关联到 [Travel Rule 指南中声明的自托管转账](https://docs.didit.me/zh/transaction-monitoring/travel-rule.md#已声明的自托管钱包)，因此 `wallet_address` 即为该转账的目标钱包。
关联了转账的会话只接受该转账交易对手钱包的证明。

```bash theme={null}
curl -X POST https://verification.didit.me/v3/travel-rule/widget-session/ \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "wallet_address": "0xd3b62acb76fb27a7f8961ec40fa11746b113aca9",
    "chain": "ETH",
    "holder_name": "Jane Doe",
    "vendor_data": "user-042",
    "transaction_id": "99999999-aaaa-bbbb-cccc-dddddddddddd",
    "satoshi_deposit_address": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F",
    "callback_url": "https://yourapp.example/wallet-verified",
    "expires_in_minutes": 60
  }'
```

| 字段 | 类型 | 说明 |
| - | - | - |
| `wallet_address` | string | 要验证的地址。 |
| `chain` | string | 该地址所在链的标识符（例如 `ETH`、`BTC`、`SOL`）。请使用转账中所用的相同标识（`ETH`，而非 `ethereum`）：小组件会以此 `chain` 保存地址簿条目，链不同的转账将无法匹配。消息签名需要 Didit 支持的链。 |
| `holder_name` | string | 钱包持有人的姓名，用于收款人姓名匹配。 |
| `vendor_data` | string | 您内部对该持有人的标识符，会写入所创建的地址簿条目。 |
| `transaction_id` | string (uuid) | 可选。待在证明验证通过时推进的 Travel Rule 转账的 Didit 交易 UUID。UUID 未知时返回 `404`；对该转账交易对手钱包以外的任何钱包提交的证明会被以 `400` 拒绝。省略则单独验证钱包。 |
| `satoshi_deposit_address` | string | 可选。您控制的存款地址；提供该地址即可启用 `SATOSHI_TEST` 方法。 |
| `callback_url` | string | 可选。客户完成后，小组件将客户重定向到的地址。 |
| `expires_in_minutes` | integer | 可选。生成链接的有效期（分钟），取值范围为 `5` 至 `10080`（默认 `60`）。传入 `satoshi_deposit_address` 时，有效期会自动提高至至少 24 小时的 Satoshi 测试窗口。 |

```json theme={null}
{
  "widget_session_id": "77777777-8888-9999-aaaa-bbbbbbbbbbbb",
  "token": "3s9x...urlsafe",
  "url": "https://verify.didit.me/wallet-ownership/3s9x...urlsafe",
  "expires_at": "2026-07-07T13:00:00Z"
}
```

请始终原样打开响应中返回的 `url`，切勿自行构造。

### 打开返回的 URL

> [!WARNING]
>
> **本版本中小组件仅支持网页端。请在系统浏览器或自定义标签页中打开 `url`，切勿在 Didit 移动端 SDK 的 WebView 中打开。**
>
> 消息签名适用于客户浏览器中已安装的钱包，小组件通过 EIP-6963 和 Wallet Standard 协议发现这些钱包。
> 目前不支持配对独立的移动钱包应用（例如通过二维码配对），因此小组件不会跳转至其他应用。
> 应用内 WebView（例如 Didit Web/Flutter/React Native/iOS/Android SDK 的 WebView）**无法访问客户浏览器中的钱包**，因此在其中打开的小组件会使客户无法完成签名。
>
> 在移动端，请使用操作系统浏览器（`SFSafariViewController` / Chrome Custom Tabs，或默认浏览器）打开 `url`，而不要在 WebView 中渲染。在网页端，普通标签页或弹出窗口均可。

### 检测完成情况

小组件提供一个以令牌为键的公开只读接口。您可以轮询该接口查看进度，或在会话关联转账时依赖 Webhook：

```bash theme={null}
curl https://verification.didit.me/v3/travel-rule/widget/{token}/
```

```json theme={null}
{
  "vasp_name": "Origin CASP SL",
  "wallet_address": "0xd3b62acb76fb27a7f8961ec40fa11746b113aca9",
  "chain": "ETH",
  "holder_name": "Jane Doe",
  "allowed_methods": ["MESSAGE_SIGNING", "SCREENSHOT", "SATOSHI_TEST", "SELF_DECLARATION"],
  "transfer_status": "UNCONFIRMED_OWNERSHIP",
  "callback_url": "https://yourapp.example/wallet-verified",
  "expires_at": "2026-07-07T13:00:00Z",
  "completed_at": null,
  "proofs": [
    {
      "proof_id": "88888888-9999-aaaa-bbbb-cccccccccccc",
      "method": "SATOSHI_TEST",
      "status": "PENDING",
      "wallet_address": "0xd3b62acb76fb27a7f8961ec40fa11746b113aca9",
      "chain": "ETH",
      "deposit_address": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F",
      "expected_amount": "0.000042817305521963",
      "expires_at": "2026-07-07T13:05:00.412731+00:00",
      "created_at": "2026-07-06T13:05:00.412731Z"
    }
  ],
  "customization": null
}
```

* `completed_at` 在证明验证通过后变为非空值。
* `proofs` 中的每一项都带有其方法所需的字段：`SATOSHI_TEST` 证明会额外返回 `deposit_address`、`expected_amount` 及其自身的 `expires_at`；`MESSAGE_SIGNING` 证明会额外返回 `challenge`，即客户需要签名的字符串。
* `SATOSHI_TEST` 证明在其存款已在链上检测到但尚未确认时，可能处于 `status: "PENDING"`，此时您和客户均无需采取任何操作。详见上文的 [Satoshi 测试验证合约](#satoshi-测试验证合约)。
* 创建会话时若带有 `transaction_id`，证明验证通过后将推进关联的转账；在生产环境的应用中，还会触发 [`travel_rule.status.updated`](https://docs.didit.me/zh/integration/webhooks#事件类型) Webhook。
  建议优先使用 Webhook 而非轮询。
  入站 `TRP` 询问在批准结果回传给发送方并送达后，将转为 `COMPLETED`。
  其他处于 `UNCONFIRMED_OWNERSHIP` 的转账（或处于 `ON_HOLD` 的入站转账）会转为 `COMPLETED`；若收款人姓名与钱包持有人不匹配，则转为 `COUNTERPARTY_MISMATCHED_DATA`；已结算的 sunrise 存款完成后会继续转为 `FINISHED`。
  处于 `COUNTERPARTY_VASP_NOT_FOUND` 的出站转账会转为 `COMPLETED`。
* `SCREENSHOT` 证明在此场景下即使您确认了所有权，仍保持 `status: "PENDING"`。
  您需通过 [`POST .../travel-rule/ownership/`](https://docs.didit.me/zh/transaction-monitoring/travel-rule#确认钱包归属) 批准该证明，该接口仅接受处于等待所有权确认状态的转账。
  因此，未关联转账的会话，或关联了处于 `COUNTERPARTY_VASP_NOT_FOUND` 的出站转账的会话，都无法批准截图证明。如果这些会话不应提供该方式，请关闭 `allow_screenshot_proof`。
* 证明被拒绝后，只要仍有剩余次数，会话即可再次尝试。`max_attempts`、`attempts_used` 和 `attempts_remaining` 反映尝试次数，被拒绝的证明会携带 `rejection_reason`。当 `attempts_remaining` 为 `0` 时，再次提交证明将返回 `409`，此时您需要创建新会话。
* 对于未知令牌，上下文接口返回 `404`；会话过期且未完成后，返回 `410`。

> [!NOTE]
>
> 这里不存在一次性依赖：小组件与您的后端最终会收敛到同一份持久状态（已验证的地址簿条目和转账状态）。因此，无论 Webhook 丢失还是标签页被关闭，都可以通过重新读取转账或小组件上下文来恢复。

## 本地化与品牌定制

该组件以 **54 种语言** 呈现，语言随链接中的语言路径决定（未指定时使用浏览器默认语言）。

为您的应用启用白标后，该组件会自动继承您的验证流程品牌设置：Logo、品牌色、按钮和面板样式、自定义字体、favicon，以及标题中的应用公开名称。
无需单独配置，您为 KYC 会话设置的定制同样适用于此处。

用户返回已完成的链接时，会再次看到完成页面；如果您传递了 `callback_url`，组件会将用户带回该地址。
已过期的链接会显示带品牌标识的过期页面，提示用户申请新链接。请为其调用 `POST /v3/travel-rule/widget-session/` 创建新会话：使用 `action: "resend"` 重新发送转账并不会创建会话。

## 后续步骤

> [card] [Travel Rule](https://docs.didit.me/zh/transaction-monitoring/travel-rule.md)
>
> 完整的交换生命周期、渠道、设置以及接收流程。

> [card] [钱包地址簿](https://docs.didit.me/zh/transaction-monitoring/travel-rule.md#地址簿)
>
> 登记您控制的钱包，以便入站转账与这些钱包匹配。

> [card] [Webhook](https://docs.didit.me/zh/integration/webhooks.md)
>
> 订阅 travel\_rule.status.updated，以便在证明促使转账进入下一阶段时作出响应。

> [card] [原生 SDK](https://docs.didit.me/zh/integration/native-sdks/overview.md)
>
> 请使用系统浏览器打开组件 URL，而不是 SDK 的 webview。


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