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

# 批量下发

批量下发（Bulk Send）指一次性向多个收款地址批量转出加密货币，适用于工资发放、返佣奖励、合作方结算、电商/广告平台商家收益分发等场景。您只需提交包含多条批量下发条目（Bulk Send Item）的请求，Cobo 将为每个条目执行地址校验与合规验证（KYA），并根据执行模式（execution mode）决定是否继续链上转账。

## **前提条件**

在开始前，请确保您已完成以下准备：

* 确保 source\_account（资金来源账户）有足够余额。
* （推荐）配置 Webhook Endpoint，用于接收 Bulk Send 的状态更新并驱动自动化重试流程。

## **1. 如何做批量下发**

### **1.1 创建 Bulk Send**

创建 Bulk Send 时，您需要提供：

* source\_account：资金来源账户 ID（例如 M1001）
* execution\_mode：执行模式（**Strict / Partial**）
* payout\_params：批量下发条目明细，每条包含：
  * token\_id
  * receiving\_address
  * amount
  * description（建议填写业务侧可追溯信息，如工资单号/用户ID/发放批次）

```shell theme={null}
curl -X POST "https://api.example.com/v1/payments/bulk_sends" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <YOUR_ACCESS_TOKEN>" \
  -d '{
    "source_account": "M1001",
    "execution_mode": "Partial",
    "description": "january salary",
    "payout_params": [
      {
        "token_id": "ETH_USDT",
        "receiving_address": "0xabc123456789def0000000000000000000000000",
        "amount": "500.00",
        "description": "Salary 2026-01 | eid=10001"
      },
      {
        "token_id": "TRX_USDT",
        "receiving_address": "TQx8yq9hFJt2cNxxxxxxxxyyyyyyyyyy",
        "amount": "320.50",
        "description": "Salary 2026-01 | eid=10002"
      }
    ]
  }'
```

### **1.2 execution\_mode（执行模式）如何选择**

Cobo 在收到 Bulk Send 请求后，会先对 **请求中所有地址进行校验与验证**（包含格式校验与 KYA 等合规验证）。execution\_mode 决定了当**任意地址存在问题**时，Cobo 是否继续对其他正常地址执行链上转账。

#### **Strict vs Partial 的行为差异**

| execution\_mode | 校验阶段发现任意地址有问题（ KYA 不通过） | 链上转账结果                                 | 您需要做什么                                   |
| --------------- | ------------------------ | -------------------------------------- | ---------------------------------------- |
| Strict          | 只要 **任意一条** 条目地址有问题      | **不会发起任何链上转账**（整单不执行）                  | 更换有问题的地址后，**重新提交整单 Bulk Send**（需要包含全部条目） |
| Partial         | 允许存在部分地址有问题              | 会对 **所有校验通过的地址** 执行链上转账；有问题的地址 **不会转** | 查询失败条目，更换地址后，**仅补发失败条目**（只提交需要补发的 item）  |

#### **场景建议**

* 选择 Strict（强一致 / 要么全成要么全不成）
  * 合作方结算（同批次必须一致生效）
  * 供应商付款（财务侧要求整单一致）
  * 任何“这批不允许部分成功”的场景
* 选择 Partial（高吞吐 / 允许部分失败，后续补发）
  * 全球员工工资发放（大批量，少数地址异常不应阻塞整体）
  * 返佣/奖励空投（地址质量参差）
  * 内容平台打赏金分发、电商/广告平台商家收益发放（可补发）

影响总结：

* **Strict 更利于财务一致性**，但会增加“因个别地址问题导致整批重提”的操作成本；
* **Partial 更利于规模化与自动化**，但需要配套失败补发流程。

### **1.3 批量下发的智能合约机制**

在 Ethereum 上进行批量下发时，Cobo 使用智能合约执行批量转账；其他链则使用普通交易。无论哪种方式，Cobo 都会对每笔操作向您注册的 Callback Endpoint 发送 Callback 消息，以便您检查详情并批准或拒绝执行。具体机制也可能因您的团队配置而有所不同，如需了解详情，请联系 Cobo 支持团队。要了解如何设置和注册 Callback Endpoint，请参阅 [Callback 介绍](/payments/cn/developer-tools/callback)。

#### **Approve 操作（allowance 不足时触发）**

当智能合约对某币种的 allowance 不足时，会先触发该币种合约的 `approve` 操作：

* **调用函数**：`approve`
* **calldata 参数**：
  * `_spender`：批量转账合约地址（见下方表格）
  * `_value`：本次出金数额

#### **转账操作**

Approve 完成后，后续转账将调用批量转账合约的 `sendToken` 函数：

* **调用函数**：`sendToken`
* **calldata 参数**：
  * `token`：币种合约地址（见下方表格）
  * `recipients`：收款地址列表
  * `values`：对应的转账数额列表

#### **合约地址参考**

由于各币种的合约地址固定，建议您在 callback 回调处理中校验对应的合约地址及函数调用信息，以确保回调能够正常完成。

| 链               | Token ID          | 币种合约地址                                       | 批量转账合约地址                                     |
| --------------- | ----------------- | -------------------------------------------- | -------------------------------------------- |
| Ethereum        | ETH\_USDT         | `0xdac17f958d2ee523a2206206994597c13d831ec7` | `0x3d963e23a9229d2acd25e9ffc358be1a35460ecc` |
| Ethereum        | ETH\_USDC         | `0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48` | `0x3d963e23a9229d2acd25e9ffc358be1a35460ecc` |
| Arbitrum        | ARBITRUM\_USDT    | `0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9` | `0x3d963e23a9229d2acd25e9ffc358be1a35460ecc` |
| Arbitrum        | ARBITRUM\_USDCOIN | `0xaf88d065e77c8cc2239327c5edb3a432268e5831` | `0x3d963e23a9229d2acd25e9ffc358be1a35460ecc` |
| Base            | BASE\_USDT        | `0xfde4c96c8593536e31f229ea8f37b2ada2699bb2` | `0x3d963e23a9229d2acd25e9ffc358be1a35460ecc` |
| Base            | BASE\_USDC        | `0x833589fcd6edb6e08f4c7c32d4f71b54bda02913` | `0x3d963e23a9229d2acd25e9ffc358be1a35460ecc` |
| Polygon         | MATIC\_USDT       | `0xc2132d05d31c914a87c6611c10748aeb04b58e8f` | `0x3d963e23a9229d2acd25e9ffc358be1a35460ecc` |
| Polygon         | MATIC\_USDC2      | `0x3c499c542cef5e3811e1192ce70d8cc03d5c3359` | `0x3d963e23a9229d2acd25e9ffc358be1a35460ecc` |
| BNB Smart Chain | BSC\_USDT         | `0x55d398326f99059ff775485246999027b3197955` | `0x3d963e23a9229d2acd25e9ffc358be1a35460ecc` |
| BNB Smart Chain | BSC\_USDC         | `0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d` | `0x3d963e23a9229d2acd25e9ffc358be1a35460ecc` |
| TRON            | TRON\_USDT        | `TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t`         | `TEmDa9FY9YBCg1HPL41RUnJorXRRV3rX7A`         |

***

## **2. Webhook 驱动自动化工作流（失败通知 + 查询条目 + 批量补发）**

### **2.1 订阅 Bulk Send 状态更新事件**

当 Bulk Send 的状态切换到以下任一状态时，Cobo 会触发 webhook event：

* Completed
* PartiallyCompleted
* Failed

Webhook 通知数据结构如下（Bulk Send Notification）：

```json theme={null}
{
    "data_type": "PaymentBulkSend",
    "bulk_send_id": "8ecf2933-5ef2-4f1d-9b59-3f4ff017d16a",
    "request_id": "SendBatch-001",
    "source_account": "M1001",
    "execution_mode": "Partial",
    "status": "Completed",
    "created_timestamp": 1768875988,
    "updated_timestamp": 1768878524
}
```

其中 status 枚举为：

* Pending
* Validating
* Transfering
* Completed
* PartiallyCompleted
* Failed

实践建议

* **仅当状态进入 PartiallyCompleted / Failed** 时启动补发流程；Validating / Transfering 阶段建议只做展示与监控。
* 对 webhook 进行签名校验与幂等处理（同一事件可能重放）。

***

### **2.2 收到失败通知后：查询 Bulk Send items 明细**

当收到 PartiallyCompleted 或 Failed 的 webhook 后，使用 **List bulk send items** 接口查询该 bulk send 的每条 item 状态：

* Path 参数：bulk\_send\_id
* Operation：list\_bulk\_send\_items
* Summary：List bulk send items

（这里用伪路径示例表示）

```shell theme={null}
curl -X GET "https://api.example.com/v1/payments/bulk_sends/{bulk_send_id}/items" \
  -H "Authorization: Bearer <YOUR_ACCESS_TOKEN>"
```

每个 item 包含两个关键状态字段：

* status（item 执行状态）
* validation\_status（地址/合规验证状态）

您提供的规则要求重点关注：

* 若 status 为 **Failed** 或 **NotExecuted** → 该条目失败
* 若 validation\_status 为 **ValidationFailed** → 该地址未通过 Cobo 的 KYA 验证

对这两类情况，都建议更换地址。

### **2.3 自动化重试：更换地址后批量补发**

根据 execution\_mode 的不同，补发策略不同：

#### **A) 前一次是 Strict 模式**

* Strict 模式下：只要有一个地址有问题，整单不会发生任何链上转账
* 因此补发（重新提交）时：**需要包含全部条目**（不是只提交失败条目）

补发步骤：

1. 查询 items，定位 ValidationFailed 或 Failed/NotExecuted 的条目
2. 更换这些条目的地址（或修正其它导致失败的参数）
3. **重新提交整单 Bulk Send（包含所有条目）**

#### **B) 前一次是 Partial 模式**

* Partial 模式下：已成功条目已转账完成
* 因此补发时：**只需要提交失败条目**（减少重复转账风险）

补发步骤：

1. 查询 items，筛选失败条目（status=Failed/NotExecuted 或 validation\_status=ValidationFailed）
2. 更换失败条目的地址
3. 新建一个 Bulk Send，只提交这些失败条目

***

## **3. 参考实现：Webhook 处理 + 自动补发（示例）**

下面示例展示一个典型工作流：

1. 接收 webhook
2. 若状态为 PartiallyCompleted / Failed，则查询 items
3. 筛选失败条目并更换地址（地址更换逻辑由您业务系统完成）
4. 创建新的 Bulk Send 任务并执行补发

说明：以下是示意代码（Node.js 风格）， 您可按实际 SDK/签名方式改写。

```javascript theme={null}
import express from "express";
import fetch from "node-fetch";

const app = express();
app.use(express.json());

function isTerminalFailedStatus(bulkStatus) {
    return bulkStatus === "Failed" || bulkStatus === "PartiallyCompleted";
}

function isItemFailed(item) {
    return item.status === "Failed" || item.status === "NotExecuted" || item.validation_status === "ValidationFailed";
}

// 您的业务逻辑：根据用户ID/订单号找到新地址
async function replaceAddressForFailedItem(item) {
    // item.description 里建议带业务ID，方便定位
    const newAddress = await lookupNewAddressFromYourSystem(item);
    return { ...item, receiving_address: newAddress };
}

app.post("/webhooks/cobo", async (req, res) => {
    // 1)（建议）验签 + 幂等处理：略

    const event = req.body;
    const { bulk_send_id, status, source_account, execution_mode } = event;

    // 2) 仅处理 Failed / PartiallyCompleted
    if (!isTerminalFailedStatus(status)) {
        return res.status(200).send("ok");
    }

    // 3) 查询 bulk send items
    const itemsResp = await fetch(`https://api.example.com/v1/payments/bulk_sends/${bulk_send_id}/items`, {
        method: "GET",
        headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}` }
    });
    const items = await itemsResp.json(); // 假设返回数组

    const failedItems = items.filter(isItemFailed);

    // 4) 存在未转账成功条目需要补发
    const itemsToResend = [];
    if (isTerminalFailedStatus(status)) {
        for (const item of items) {
            if (item.status === 'Failed') {
                itemsToResend.push(item);  // 发放失败直接重试
            } else if (item.status === 'NotExecuted' && item.validation_status === 'ValidationFailed') {
                itemsToResend.push(await replaceAddressForFailedItem(item));  // 校验失败需要更换地址
            }
        }
    }

    // 5) 创建新的 bulk send（补发）
    const createResp = await fetch(`https://api.example.com/v1/payments/bulk_sends`, {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
            Authorization: `Bearer ${process.env.ACCESS_TOKEN}`
        },
        body: JSON.stringify({
            source_account,
            execution_mode: "Partial", // 补发一般建议 Partial，避免再次被单点阻塞（按您的策略调整）
            payout_params: itemsToResend.map(i => ({
                token_id: i.token_id,
                receiving_address: i.receiving_address,
                amount: i.amount,
                description: i.description
            }))
        })
    });

    const created = await createResp.json();
    console.log("Resend bulk send created:", created);

    return res.status(200).send("ok");
});

app.listen(3000, () => console.log("Webhook listener on :3000"));
```

## **4. 最佳实践**

* **description 必填业务侧可追溯信息**：例如 batch\_id + user\_id/employee\_id + period，用于审计、对账、定位失败原因与换地址。
* **严格区分 Strict/Partial 的补发策略**：
  * Strict：重提必须包含全量条目
  * Partial：只补发失败条目（避免重复发放）
* **Webhook 幂等性**：对 bulk\_send\_id + status + updated\_timestamp 做幂等键，避免重复触发补发。
* **失败分类处理**：
  * validation\_status=ValidationFailed：优先换地址
  * status=Failed/NotExecuted：建议也换地址（按您给的规则），并保留失败原因用于风控/用户提示
