StableOps
概念

BYO Addresses

导入和管理自有钱包地址。

BYO(Bring Your Own)地址 允许你把自己的链上地址导入 StableOps 收款。资金始终在 你的私钥下,同时享受 StableOps 的支付监听、确认追踪与 Webhook 投递。

总览

不必使用 StableOps 生成的地址,你可以:

  • 导入已有地址(来自钱包或托管方案)
  • 完全掌控私钥(StableOps 永远看不到)
  • 使用硬件钱包或多签
  • 对接 Fireblocks、Coinbase Custody、BitGo 等托管商
  • 满足要求自托管的合规口径

工作流

┌─────────────────┐         ┌─────────────┐         ┌─────────────┐
│   Your Wallet   │────────▶│  StableOps  │────────▶│  Your App   │
│  (Private Keys) │         │  (Monitor)  │         │ (Webhooks)  │
└─────────────────┘         └─────────────┘         └─────────────┘
     你掌控资金                   仅做监听                收到事件
  1. 你自己生成地址(任意钱包或托管方案)
  2. 导入到 StableOps(API 或 dashboard)
  3. StableOps 监听链上入账
  4. 你收到 Webhook(detected / confirmed / finalized)
  5. 资金始终归你,提取时用自己的私钥

地址分配模式

StableOps 支持两种分配模式:

单地址模式(默认)

每个支付单分配一个独立地址,只用一次。

特点

  • 一单对应一地址
  • 分配后该地址锁定
  • 订单完成或过期后回收
  • 入账归属一目了然

适用场景

  • 电商结账
  • 发票收款
  • 一次性购买

示例

// 导入单地址池
await stableops.addresses.import({
  chain: 'base',
  asset: 'USDC',
  addresses: [
    '0x1234567890123456789012345678901234567890',
    '0x2345678901234567890123456789012345678901',
    '0x3456789012345678901234567890123456789012',
  ],
  mode: 'SINGLE', // 默认值
})

// 每笔订单都会拿到不同地址
const order1 = await stableops.paymentOrders.create({
  merchantOrderId: 'order_1',
  amount: '10.00',
  settlementAsset: 'USDC',
  acceptedAssets: [{ chain: 'base', asset: 'USDC' }],
})
// order1.paymentInstructions[0].address = '0x1234...'

const order2 = await stableops.paymentOrders.create({
  merchantOrderId: 'order_2',
  amount: '20.00',
  settlementAsset: 'USDC',
  acceptedAssets: [{ chain: 'base', asset: 'USDC' }],
})
// order2.paymentInstructions[0].address = '0x2345...'(另一个地址)

共享模式

多个订单可以共享同一个地址,靠精确金额区分。

特点

  • 多个订单共用一个地址
  • 必须金额精确匹配
  • 不允许多付或少付
  • 用完地址仍然可用

适用场景

  • 固定价订阅
  • API 信用点(固定档位)
  • 周期性付款
  • 地址数受限的场景

示例

// 导入共享地址
await stableops.addresses.import({
  chain: 'base',
  asset: 'USDC',
  addresses: ['0x1234567890123456789012345678901234567890'],
  mode: 'SHARED',
})

// 多个订单共用同一个地址
const order1 = await stableops.paymentOrders.create({
  merchantOrderId: 'sub_user1_jan',
  amount: '10.01', // 精确金额
  settlementAsset: 'USDC',
  acceptedAssets: [{ chain: 'base', asset: 'USDC' }],
})
// order1.paymentInstructions[0].address = '0x1234...'

const order2 = await stableops.paymentOrders.create({
  merchantOrderId: 'sub_user2_jan',
  amount: '10.02', // 不同金额
  settlementAsset: 'USDC',
  acceptedAssets: [{ chain: 'base', asset: 'USDC' }],
})
// order2.paymentInstructions[0].address = '0x1234...'(同一个地址!)

// 用户转账 10.01 USDC 匹配 order1,转账 10.02 USDC 匹配 order2

⚠️ 共享模式限制

  • 用户必须发送 精确金额(不能四舍五入)
  • 需要在 UX 上做用户教育

金额相同的情况

同一共享地址上,同一金额在任一时刻只能属于一个在途订单。StableOps 在分配时会自动保证这一点:

  • 并发同金额订单:前一个订单仍在途(created/detected/confirmed)时,新的同金额订单会被分配到另一个地址;若没有空闲地址,创建会失败——请导入更多地址、换一个金额,或改用 amount_mode: 'auto'
  • 终态后复用:前一个订单到达终态(finalized/reverted/expired/canceled)后,其金额被释放,可在同一地址上复用。

自动金额微调(amount_mode: 'auto'

不想自己构造唯一金额时,创建订单时传 amount_mode: 'auto'。StableOps 会把金额按 token 最小单位(如 USDC 的 0.000001)向上微调,直到在共享该地址的活跃订单中唯一,所以你每次传相同的基准金额即可,完全不必担心金额撞车。

const order = await stableops.paymentOrders.create({
  merchantOrderId: 'sub_user_jan',
  amount: '10.00', // 基准金额——无需自己保证唯一
  amountMode: 'auto',
  settlementAsset: 'USDC',
  acceptedAssets: [{ chain: 'base', asset: 'USDC' }],
})

// order.amount          → '10.000001'  用户实际要付的精确金额(已唯一)
// order.requestedAmount → '10.00'      你传入的基准金额,用于对账

用户按返回的 amount 精确支付(6 位小数精度)。默认 amount_mode: 'exact' 行为不变,仍由你自己保证金额唯一。

导入地址

通过 API

import { StableOps } from '@stableops/api-sdk'

const stableops = new StableOps({
  apiKey: process.env.STABLEOPS_API_KEY,
  environment: 'production',
})

// 导入地址
const result = await stableops.addresses.import({
  chain: 'base',
  asset: 'USDC',
  addresses: [
    '0x1234567890123456789012345678901234567890',
    '0x2345678901234567890123456789012345678901',
    '0x3456789012345678901234567890123456789012',
  ],
  mode: 'SINGLE', // 或 'SHARED'
})

console.log(`导入了 ${result.imported} 个地址`)
console.log(`跳过了 ${result.skipped} 个重复地址`)

通过 Dashboard

  1. 进入 SettingsAddresses
  2. Import Addresses
  3. 选择链与资产
  4. 选模式(Single 或 Shared)
  5. 粘贴地址,一行一个
  6. Import

批量导入

地址数量很多时,分批导入:

// 从文件读取
const addresses = fs
  .readFileSync('addresses.txt', 'utf-8')
  .split('\n')
  .filter((addr) => addr.trim())

// 每批 100 个
const BATCH_SIZE = 100
for (let i = 0; i < addresses.length; i += BATCH_SIZE) {
  const batch = addresses.slice(i, i + BATCH_SIZE)

  await stableops.addresses.import({
    chain: 'base',
    asset: 'USDC',
    addresses: batch,
    mode: 'SINGLE',
  })

  console.log(`完成第 ${i / BATCH_SIZE + 1} 批`)
}

地址池管理

池子健康度

StableOps 会盯着可用地址数量,低于阈值发告警:

// 查询池子状态
const overview = await stableops.overview.get()

console.log('地址池:')
overview.address_pools.forEach((pool) => {
  console.log(`${pool.chain} ${pool.asset}:`)
  console.log(`  可用: ${pool.available}`)
  console.log(`  已占用: ${pool.allocated}`)
  console.log(`  总数: ${pool.total}`)

  if (pool.available < pool.threshold) {
    console.warn(`⚠️  可用地址太少,尽快补充!`)
  }
})

低水位告警

可用地址低于阈值时 StableOps 会发 address.pool.low Webhook:

{
  "type": "address.pool.low",
  "data": {
    "chain": "base",
    "asset": "USDC",
    "available": 5,
    "threshold": 10,
    "total": 100
  }
}

处理低水位告警

app.post('/webhooks/stableops', async (req, res) => {
  const event = req.body

  if (event.type === 'address.pool.low') {
    const { chain, asset, available } = event.data

    // 通知运维
    await sendAlert(`${chain} ${asset} 可用地址只剩 ${available} 个`)

    // 自动补池(基于确定性钱包)
    await generateAndImportAddresses(chain, asset, 50)
  }

  res.sendStatus(200)
})

地址生命周期

AVAILABLE → ALLOCATED → AVAILABLE (订单进入终态后归还)

状态

  • AVAILABLE:可分配
  • ALLOCATED:当前绑定某个活跃订单
  • RESERVED:手动保留,不进入分配池(例如留作热钱包,但暂时不希望系统派单)
  • DISABLED:不参与分配,仅作审计可见(例如已弃用但需要保留记录)

SINGLE 地址在其订单进入终态(finalized / reverted / expired / canceled)后会变回 AVAILABLE

生成地址

HD(分层确定性)钱包

用 HD 钱包确定性地批量生成:

import { ethers } from 'ethers'

// 从助记词派生
const mnemonic = process.env.WALLET_MNEMONIC
const hdNode = ethers.HDNodeWallet.fromPhrase(mnemonic)

const addresses: string[] = []
for (let i = 0; i < 100; i++) {
  const path = `m/44'/60'/0'/0/${i}` // 以太坊 BIP-44 路径
  const wallet = hdNode.derivePath(path)
  addresses.push(wallet.address)
}

// 导入 StableOps
await stableops.addresses.import({
  chain: 'base',
  asset: 'USDC',
  addresses,
  mode: 'SINGLE',
})

硬件钱包

用 Ledger / Trezor 等硬件钱包派生:

import TransportNodeHid from '@ledgerhq/hw-transport-node-hid'
import Eth from '@ledgerhq/hw-app-eth'

const transport = await TransportNodeHid.create()
const eth = new Eth(transport)

const addresses: string[] = []
for (let i = 0; i < 100; i++) {
  const path = `44'/60'/0'/0/${i}`
  const { address } = await eth.getAddress(path)
  addresses.push(address)
}

await stableops.addresses.import({
  chain: 'base',
  asset: 'USDC',
  addresses,
  mode: 'SINGLE',
})

托管商对接

Fireblocks

import { FireblocksSDK } from 'fireblocks-sdk'

const fireblocks = new FireblocksSDK(privateKey, apiKey)

const addresses: string[] = []
for (let i = 0; i < 100; i++) {
  const result = await fireblocks.createVaultAccount({
    name: `StableOps-${i}`,
    hiddenOnUI: false,
  })

  const address = await fireblocks.generateNewAddress({
    vaultAccountId: result.id,
    assetId: 'USDC_BASE',
  })

  addresses.push(address.address)
}

await stableops.addresses.import({
  chain: 'base',
  asset: 'USDC',
  addresses,
  mode: 'SINGLE',
})

Coinbase Custody

import { CoinbaseClient } from '@coinbase/coinbase-custody-sdk'

const coinbase = new CoinbaseClient({
  apiKey: process.env.COINBASE_API_KEY,
  apiSecret: process.env.COINBASE_API_SECRET,
})

const addresses: string[] = []
for (let i = 0; i < 100; i++) {
  const address = await coinbase.createAddress({
    currency: 'USDC',
    network: 'base',
  })

  addresses.push(address.address)
}

await stableops.addresses.import({
  chain: 'base',
  asset: 'USDC',
  addresses,
  mode: 'SINGLE',
})

安全最佳实践

1. 永远不要分享私钥

// ✅ 正确 —— 只导入地址
await stableops.addresses.import({
  addresses: ['0x1234...'],
})

// ❌ 错误 —— 永远不要把私钥发出去
// StableOps 也绝不会向你索取私钥

2. 高价值地址使用硬件钱包

接收大额支付的地址:

  • 用硬件钱包派生(Ledger、Trezor)
  • 私钥离线保存
  • 多签提升安全

3. 区分热钱包与冷钱包

// 热钱包 —— 小额、频繁
await stableops.addresses.import({
  chain: 'base',
  asset: 'USDC',
  addresses: hotWalletAddresses,
  mode: 'SINGLE',
})

// 冷钱包 —— 大额、低频
// 只在需要时手动导入

4. 定期轮换地址

为隐私轮换地址:

// 每月生成新地址
const rotateAddresses = async () => {
  const newAddresses = await generateAddresses(100)

  await stableops.addresses.import({
    chain: 'base',
    asset: 'USDC',
    addresses: newAddresses,
    mode: 'SINGLE',
  })

  // 旧地址不再分配后归档
}

5. 监控异常提币

// 监听链上出账
const monitorWithdrawals = async (address: string) => {
  const provider = new ethers.JsonRpcProvider(rpcUrl)

  provider.on({ address }, (log) => {
    if (!isAuthorizedWithdrawal(log)) {
      sendSecurityAlert(`检测到来自 ${address} 的未授权提币`)
    }
  })
}

资金提取

StableOps 只做监听,提币由你自己用私钥发起。

手动提取

import { ethers } from 'ethers'

const wallet = new ethers.Wallet(privateKey, provider)

const usdcContract = new ethers.Contract(
  USDC_ADDRESS,
  ['function transfer(address to, uint256 amount) returns (bool)'],
  wallet,
)

const tx = await usdcContract.transfer(
  destinationAddress,
  ethers.parseUnits('100.00', 6), // USDC 是 6 位小数
)

await tx.wait()
console.log(`已向 ${destinationAddress} 提取 100 USDC`)

自动归集

把资金定期归集到 Treasury:

const sweepAddress = async (address: string) => {
  const wallet = new ethers.Wallet(privateKey, provider)
  const usdcContract = new ethers.Contract(USDC_ADDRESS, USDC_ABI, wallet)

  const balance = await usdcContract.balanceOf(address)

  if (balance > 0) {
    const tx = await usdcContract.transfer(TREASURY_ADDRESS, balance)
    await tx.wait()

    console.log(`从 ${address} 归集 ${ethers.formatUnits(balance, 6)} USDC`)
  }
}

// 每天归集一次
cron.schedule('0 0 * * *', async () => {
  const addresses = await getActiveAddresses()
  for (const address of addresses) {
    await sweepAddress(address)
  }
})

地址校验

导入时 StableOps 会做这些校验:

校验规则

  • 格式:必须符合对应链的地址格式
  • 校验和:EVM 地址必须通过 checksum 校验
  • 去重:同一地址不可重复导入
  • 链匹配:地址必须与目标链兼容

示例

// ✅ 合法的 EVM 地址(已 checksum)
'0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed'

// ✅ 合法的 EVM 地址(小写,会自动 checksum)
'0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed'

// ✅ 合法的 TRON 地址
'TRX9ZfPBvgS8Z8HnFvCfKvFXqvQzMvXkXJ'

// ❌ 不合法 —— 格式错误
'not-an-address'

// ❌ 不合法 —— 链不匹配
// 例如把以太坊地址导入到 TRON

合规与监管

自托管诉求

某些司法辖区要求企业自托管客户资金:

  • MiCA(欧盟):加密服务提供商必须托管客户资产
  • NYDFS(纽约):BitLicense 要求托管安排
  • MAS(新加坡):支付服务提供商必须保护资金

BYO 地址让你既满足合规,又能使用 StableOps 监听。

审计追溯

StableOps 留存完整审计日志:

// 查询地址导入历史
const auditLogs = await stableops.auditLogs.list({
  resourceType: 'merchant_address',
  action: 'import',
})

auditLogs.items.forEach((log) => {
  console.log(`${log.createdAt}: 导入 ${log.metadata.count} 个地址`)
  console.log(`  链: ${log.metadata.chain}`)
  console.log(`  资产: ${log.metadata.asset}`)
  console.log(`  操作人: ${log.actor}`)
})

最佳实践

1. 池子保有量

// 经验值:每日订单量的 2 倍
const dailyOrders = 100
const recommendedPoolSize = dailyOrders * 2

const overview = await stableops.overview.get()
const pool = overview.address_pools.find(
  (p) => p.chain === 'base' && p.asset === 'USDC',
)

if (pool.available < recommendedPoolSize) {
  console.warn(`池子太小!再补 ${recommendedPoolSize - pool.available} 个地址`)
}

2. 配置低水位告警

const ALERT_THRESHOLD = 20 // 可用低于 20 时升级告警

app.post('/webhooks/stableops', async (req, res) => {
  const event = req.body

  if (event.type === 'address.pool.low') {
    if (event.data.available < ALERT_THRESHOLD) {
      await sendPagerDutyAlert('严重:地址池即将耗尽')
    }
  }

  res.sendStatus(200)
})

3. 共享模式确保金额唯一

提示:不想自己挑金额?创建订单时设 amount_mode: 'auto',StableOps 会自动保证唯一(见上文「共享模式」)。

// ✅ 正确 —— 每个订单金额唯一
await stableops.addresses.import({
  chain: 'base',
  asset: 'USDC',
  addresses: ['0x1234...'],
  mode: 'SHARED',
})

const order1 = await stableops.paymentOrders.create({
  merchantOrderId: `sub_${userId}_jan`,
  amount: '10.00',
  settlementAsset: 'USDC',
  acceptedAssets: [{ chain: 'base', asset: 'USDC' }],
})

const order2 = await stableops.paymentOrders.create({
  merchantOrderId: `sub_${userId}_feb`,
  amount: '15.00', // 不同月份不同金额
  settlementAsset: 'USDC',
  acceptedAssets: [{ chain: 'base', asset: 'USDC' }],
})

// ❌ 注意 —— 同时存在多个相同金额订单时,系统无法自动区分

4. 记录地址派生信息

// 保存派生信息以便后续恢复
await db.addresses.create({
  address: '0x1234...',
  derivationPath: "m/44'/60'/0'/0/0",
  walletType: 'ledger',
  importedAt: new Date(),
  chain: 'base',
  asset: 'USDC',
})

5. 上生产前先在沙盒测试

// 先在沙盒跑通
const stableops = new StableOps({
  apiKey: process.env.STABLEOPS_SANDBOX_API_KEY,
  environment: 'sandbox',
})

// 导入测试地址
await stableops.addresses.import({
  chain: 'base-sepolia', // 测试网
  asset: 'USDC',
  addresses: testAddresses,
  mode: 'SINGLE',
})

// 创建测试订单
const order = await stableops.paymentOrders.create({
  merchantOrderId: 'test_order_1',
  amount: '0.01',
  settlementAsset: 'USDC',
  acceptedAssets: [{ chain: 'base-sepolia', asset: 'USDC' }],
})

// 发一笔测试支付,验证 Webhook 链路

下一步

  • 支付单 —— 用你的地址创建支付单
  • 确认 —— 理解确认数阶段
  • Webhooks —— 接收支付通知

本页内容