BYO Addresses
导入和管理自有钱包地址。
BYO(Bring Your Own)地址 允许你把自己的链上地址导入 StableOps 收款。资金始终在 你的私钥下,同时享受 StableOps 的支付监听、确认追踪与 Webhook 投递。
总览
不必使用 StableOps 生成的地址,你可以:
- 导入已有地址(来自钱包或托管方案)
- 完全掌控私钥(StableOps 永远看不到)
- 使用硬件钱包或多签
- 对接 Fireblocks、Coinbase Custody、BitGo 等托管商
- 满足要求自托管的合规口径
工作流
┌─────────────────┐ ┌─────────────┐ ┌─────────────┐
│ Your Wallet │────────▶│ StableOps │────────▶│ Your App │
│ (Private Keys) │ │ (Monitor) │ │ (Webhooks) │
└─────────────────┘ └─────────────┘ └─────────────┘
你掌控资金 仅做监听 收到事件- 你自己生成地址(任意钱包或托管方案)
- 导入到 StableOps(API 或 dashboard)
- StableOps 监听链上入账
- 你收到 Webhook(detected / confirmed / finalized)
- 资金始终归你,提取时用自己的私钥
地址分配模式
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
- 进入 Settings → Addresses
- 点 Import Addresses
- 选择链与资产
- 选模式(Single 或 Shared)
- 粘贴地址,一行一个
- 点 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 链路