自有地址
瞭解如何向 StableOps 匯入並管理自有的 EVM、TRON 或 Solana 收款地址,在資金和私鑰始終由商戶控制的前提下完成地址分配、支付監聽、確認追蹤與 Webhook 投遞,並正確處理地址狀態、複用策略和網路差異。
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',
addresses: [
'0x1234567890123456789012345678901234567890',
'0x2345678901234567890123456789012345678901',
'0x3456789012345678901234567890123456789012',
],
mode: 'single', // 預設值
})
// 每筆訂單都會拿到不同地址
const order1 = await stableops.paymentOrders.create({
merchantOrderId: 'order_1',
amount: '10.00',
acceptedAssets: [{ chain: 'base', asset: 'USDC' }],
expiresAt: new Date(Date.now() + 30 * 60 * 1000).toISOString(),
})
// order1.paymentInstructions[0].address = '0x1234...'
const order2 = await stableops.paymentOrders.create({
merchantOrderId: 'order_2',
amount: '20.00',
acceptedAssets: [{ chain: 'base', asset: 'USDC' }],
expiresAt: new Date(Date.now() + 30 * 60 * 1000).toISOString(),
})
// order2.paymentInstructions[0].address = '0x2345...'(另一個地址)共享模式
多個訂單可以共享同一個地址,靠精確金額區分。
特點:
- 多個訂單共用一個地址
- 必須金額精確匹配
- 不允許多付或少付
- 用完地址仍然可用
適用場景:
- 固定價訂閱
- API 信用點(固定檔位)
- 週期性付款
- 地址數受限的場景
示例:
// 匯入共享地址
await stableops.addresses.import({
chain: 'base',
addresses: ['0x1234567890123456789012345678901234567890'],
mode: 'shared',
})
// 多個訂單共用同一個地址
const order1 = await stableops.paymentOrders.create({
merchantOrderId: 'sub_user1_jan',
amount: '10.01', // 精確金額
acceptedAssets: [{ chain: 'base', asset: 'USDC' }],
expiresAt: new Date(Date.now() + 30 * 60 * 1000).toISOString(),
})
// order1.paymentInstructions[0].address = '0x1234...'
const order2 = await stableops.paymentOrders.create({
merchantOrderId: 'sub_user2_jan',
amount: '10.02', // 不同金額
acceptedAssets: [{ chain: 'base', asset: 'USDC' }],
expiresAt: new Date(Date.now() + 30 * 60 * 1000).toISOString(),
})
// order2.paymentInstructions[0].address = '0x1234...'(同一個地址!)
// 使用者轉帳 10.01 USDC 匹配 order1,轉帳 10.02 USDC 匹配 order2⚠️ 共享模式限制:
- 使用者必須傳送 精確金額(不能四捨五入)
- 需要在 UX 上做使用者教育
金額相同的情況:
同一共享地址上,同一金額在任一時刻只能屬於一個在途訂單。StableOps 在分配時會自動保證這一點,但 exact 和 auto 兩種模式處理方式不同:
amount_mode: 'exact'(預設):先嚐試單地址、再嘗試共享地址,各組內按匯入時間(createdAt ASC)逐個嘗試,遇到同金額在途衝突就跳過,找下一個地址。若所有地址都有衝突,建立會失敗。請匯入更多地址或換一個金額。amount_mode: 'auto':分兩階段分配。第一階段不改基準金額,按最久未用優先在地址間輪詢——只要有任一地址上基準金額空閒,訂單就直接用基準金額,不做微調。只有當基準金額在所有地址上都有在途衝突時,才進入第二階段,以 token 最小單位(如 USDC 的0.000001)逐級微調(每個地址最多 1000 步),直到找到唯一金額。- 終態後複用:前一個訂單到達終態(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',
acceptedAssets: [{ chain: 'base', asset: 'USDC' }],
expiresAt: new Date(Date.now() + 30 * 60 * 1000).toISOString(),
})
// order.amount → 基準金額在某個地址上空閒時就是 '10.00',
// 全部衝突時才是 '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,
})
// 匯入地址
const result = await stableops.addresses.import({
chain: 'base',
addresses: [
'0x1234567890123456789012345678901234567890',
'0x2345678901234567890123456789012345678901',
'0x3456789012345678901234567890123456789012',
],
mode: 'single', // 或 'shared'
})
console.log(`匯入了 ${result.imported} 個地址`)
// 已匯入過的地址會被靜默跳過,不計入 imported透過控制台
- 進入控制台 → 地址
- 點 匯入地址
- 選擇鏈
- 選模式(Single 或 Shared)
- 貼上地址,一行一個
- 點 匯入
批次匯入
地址數量很多時,分批匯入:
// 從檔案讀取
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',
addresses: batch,
mode: 'single',
})
console.log(`完成第 ${i / BATCH_SIZE + 1} 批`)
}地址池管理
池子健康度
StableOps 會盯著可用地址數量,低於閾值發告警:
// 查詢池子狀態
const pools = await stableops.addresses.getPools()
console.log('地址池:')
pools.forEach((pool) => {
console.log(`${pool.chain}:`)
console.log(` 可用: ${pool.available}`)
console.log(` 已佔用: ${pool.allocated}`)
console.log(` 總數: ${pool.total}`)
if (pool.available < pool.threshold) {
console.warn(`⚠️ 可用地址太少,儘快補充!`)
}
})說明:共享地址的
status始終為AVAILABLE,因此對共享鏈而言available恆等於total。在途分配數需透過訂單狀態間接推算,而不是看allocated欄位。
低水位告警(僅單地址池)
注意:低水位告警僅對純 SINGLE 地址池的鏈生效。只要某鏈存在至少一個 SHARED 地址(共享地址可複用,無需囤積),該鏈跳過餘量告警。
可用地址低於閾值時 StableOps 會發 address.pool.low Webhook:
{
"type": "address.pool.low",
"data": {
"chain": "base",
"environment": "live",
"available": 2,
"threshold": 3
}
}地址池按鏈統計(不區分資產)。預設閾值為 3,自部署時可透過環境變數 ADDRESS_POOL_LOW_THRESHOLD 調整。
處理低水位告警:
app.post('/webhooks/stableops', async (req, res) => {
const event = req.body
if (event.type === 'address.pool.low') {
const { chain, available } = event.data
// 通知運維
await sendAlert(`${chain} 可用地址只剩 ${available} 個`)
// 自動補池(基於確定性錢包)
await generateAndImportAddresses(chain, 50)
}
res.sendStatus(200)
})地址生命週期
單地址模式(SINGLE):
AVAILABLE → ALLOCATED → AVAILABLE (訂單進入終態後歸還)共享模式(SHARED):
AVAILABLE (始終保持,永不翻轉為 ALLOCATED)SHARED 地址的 status 永遠為 AVAILABLE,不隨分配變化。分配記錄寫入 AddressAllocation 表,狀態機在訂單級別追蹤。
狀態:
- AVAILABLE:可分配
- ALLOCATED:目前繫結某個活躍訂單(僅 SINGLE 地址使用)
- 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',
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',
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',
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',
addresses,
mode: 'single',
})安全最佳實踐
1. 永遠不要分享私鑰
// ✅ 正確:只匯入地址
await stableops.addresses.import({
addresses: ['0x1234...'],
})
// ❌ 錯誤:永遠不要把私鑰發出去
// StableOps 也絕不會向你索取私鑰2. 高價值地址使用硬體錢包
接收大額支付的地址:
- 用硬體錢包派生(Ledger、Trezor)
- 私鑰離線儲存
- 多籤提升安全
3. 區分熱錢包與冷錢包
// 熱錢包:小額、頻繁
await stableops.addresses.import({
chain: 'base',
addresses: hotWalletAddresses,
mode: 'single',
})
// 冷錢包:大額、低頻
// 只在需要時手動匯入4. 定期輪換地址
為隱私輪換地址:
// 每月生成新地址
const rotateAddresses = async () => {
const newAddresses = await generateAddresses(100)
await stableops.addresses.import({
chain: 'base',
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 為
0x+ 40 位十六進位制;TRON 為 base58 原樣儲存) - 歸一化:EVM 地址統一轉小寫入庫,不做 EIP-55 checksum 校驗
- 去重:重複匯入同一地址會被靜默跳過
- 鏈匹配:地址必須與目標鏈相容
示例
// ✅ 合法的 EVM 地址(大小寫混合亦可,入庫統一轉小寫)
'0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed'
// ✅ 合法的 EVM 地址(小寫)
'0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed'
// ✅ 合法的 TRON 地址
'TRX9ZfPBvgS8Z8HnFvCfKvFXqvQzMvXkXJ'
// ❌ 不合法:格式錯誤
'not-an-address'
// ❌ 不合法:鏈不匹配
// 例如把以太坊地址匯入到 TRON合規與監管
自託管訴求
某些司法轄區要求企業自託管客戶資金:
- MiCA(歐盟):加密服務提供商必須託管客戶資產
- NYDFS(紐約):BitLicense 要求託管安排
- MAS(新加坡):支付服務提供商必須保護資金
BYO 地址讓你既滿足合規,又能使用 StableOps 監聽。
審計追溯
StableOps 留存完整審計日誌,前往 控制台 → 審計日誌 檢視地址匯入、分配、狀態變更等操作記錄。
最佳實踐
1. 池子保有量
// 經驗值:每日訂單量的 2 倍
const dailyOrders = 100
const recommendedPoolSize = dailyOrders * 2
const pools = await stableops.addresses.getPools()
const pool = pools.find((p) => p.chain === 'base')
if (pool && 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 會自動保證唯一(見上文「共享模式」)。
exact 模式下,同一共享地址上同一金額在任一時刻只能有一個在途訂單。因此只要每個訂單金額不同,就能正常區分:
// ✅ 正確:每個訂單金額不同,可正常區分
await stableops.addresses.import({
chain: 'base',
addresses: ['0x1234...'],
mode: 'shared',
})
const order1 = await stableops.paymentOrders.create({
merchantOrderId: `sub_${userId}_jan`,
amount: '10.00',
acceptedAssets: [{ chain: 'base', asset: 'USDC' }],
expiresAt: new Date(Date.now() + 30 * 60 * 1000).toISOString(),
})
const order2 = await stableops.paymentOrders.create({
merchantOrderId: `sub_${userId}_feb`,
amount: '15.00',
acceptedAssets: [{ chain: 'base', asset: 'USDC' }],
expiresAt: new Date(Date.now() + 30 * 60 * 1000).toISOString(),
})
// ❌ 錯誤:同一共享地址上同時存在兩個 10.00 的在途訂單,系統無法區分
// const orderBad = await stableops.paymentOrders.create({
// amount: '10.00', // order1 尚未終態,此金額已被佔用
// ...
// })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,
})
// 匯入測試地址
await stableops.addresses.import({
chain: 'base-sepolia', // 測試網
addresses: testAddresses,
mode: 'single',
})
// 建立測試訂單
const order = await stableops.paymentOrders.create({
merchantOrderId: 'test_order_1',
amount: '0.01',
acceptedAssets: [{ chain: 'base-sepolia', asset: 'USDC' }],
expiresAt: new Date(Date.now() + 30 * 60 * 1000).toISOString(),
})
// 發一筆測試支付,驗證 Webhook 鏈路下一步
這篇文件怎麼樣?
最後更新