动钱代码,按"联网建/广播 + 离线签"气隙流程,离线签名独立验签防篡改: - wallet: DecodeTronAddress/TronAddressBodyHex(base58check 解码校验)、AddressFromMnemonic(离线校验用)。 - tron/sign: TxID(sha256 raw_data)、SignRawData(secp256k1 → R||S||recid 65B)、RecoverAddressBody。 - tron/abi: ABIEncodeTransferParams(transfer(address,uint256) 参数)、keccak 地址体。 - tron/tx(联网): BuildTransfer(triggersmartcontract 建未签名)、TRC20Balance、Broadcast。 - cmd/sweep: plan/build/sign/broadcast 四段;sign 对每笔独立验:①重算 txid 防篡改 ②收款人+额 ABI 内嵌 ③USDT 合约内嵌 ④派生地址==owner,任一不符拒签;助记词只经 PAY_SWEEP_MNEMONIC(离线机,不入 arg)。 - 测试:签名可恢复到正确地址、ABI 编码、地址解码 roundtrip 全绿。 - 联网上链部分标注需 Phase E 真链验证;README 补 sweep 用法 + 验签说明 + gas 提醒。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pangolin-pay
自托管 USDT(TRC20) 收款服务。给每个订单派生一个唯一收款地址(watch-only,从账户 xpub 派生,不持私钥),轮询 TronGrid 侦测到账、确认后标记订单已付。归集(把钱扫到冷 钱包)是独立的离线步骤,本服务不碰私钥。
属 #34「独角数卡 + USDT 收款闭环」的加密货币交易引擎(计划见
docs/superpowers/plans/2026-07-09-crypto-tx-engine.md)。概念见 brainnotes/dev/crypto-hd-wallet-basics.html。
安全模型(必读)
- 本服务(热、联网)只持 account xpub——能派生收款地址、能查到账,拿不到任何私钥。被脱库也转不走钱。
- 私钥/助记词冷存;要动钱(归集)时才在离线端用私钥签名(见 seed.go,OFFLINE ONLY)。
- 密钥(xpub / TronGrid key)走 Bitwarden,不入 git、不写死。
Phase A —— 离线准备钱包(你在断网机器上做)
- 断网,用 Ian Coleman
bip39-standalone.html(或bip_utils)生成两套 24 词助记词: 钱包 A(运营收款)、钱包 B(冷备金库)。Coin=TRX、English。 - 取钱包 A 的 Account Extended Public Key(
m/44'/195'/0'的 xpub)→ 就是本服务的PAY_ACCOUNT_XPUB。 - 取钱包 B 的地址0(
T...)→ 归集目标(Phase D 用)。 - 交叉核对(关键):本仓自带的金标准向量(测试助记词
abandon…about)必须与 Ian Coleman 一致 —— 跑go test ./internal/wallet -run TestKnownVector -v,再在 Ian Coleman 里用同一测试 助记词、Coin=TRX 对照前 3 个地址。一致 = 派生实现可信;不一致 = 有 bug,别上线。 - 助记词 A/B 分开冷存 + Bitwarden。只把 xpub_A 交给本服务。
运行
export PAY_ACCOUNT_XPUB="xpub..." # 钱包 A 的 account xpub(必填)
export TRONGRID_API_KEY="..." # TronGrid key(建议)
export PAY_DB="pay.db" # SQLite 路径(默认 pay.db)
export PAY_ADDR=":8090" # 监听(默认 :8090)
export PAY_POLL_SECONDS="20" # 轮询间隔秒(默认 20)
# USDT_CONTRACT / TRONGRID_BASE 默认主网
go run ./cmd/paywatch
API
POST /order {"sku":"pro-year","amount":5000000} # amount = micro-USDT(1e-6)
→ 201 {"order_no","address","expect_amount","status":"pending","expires_at"}
GET /order/{orderNo} → 200 {..., "status":"pending|paid|expired","tx_id"}
GET /healthz → 200 ok
门面(独角数卡)下单时调 POST /order 拿收款地址;支付页轮询 GET /order/{id} 直到 paid。
Phase D —— 归集(气隙签名,cmd/sweep)
把散在各收款地址的 USDT 扫到冷钱包,助记词只在离线机上出现,联网机永远拿不到私钥。 四段式,跨气隙用文件传递(unsigned.json / signed.json):
# ① 联网:列出有余额的收款地址
PAY_ACCOUNT_XPUB=xpub... TRONGRID_API_KEY=... go run ./cmd/sweep plan --max 50
# ② 联网:构造未签名转账(全额 → 冷钱包),不碰私钥
PAY_ACCOUNT_XPUB=xpub... TRONGRID_API_KEY=... \
go run ./cmd/sweep build --cold TColdAddr... --max 50 --fee-limit 30000000 > unsigned.json
# ③ 离线(断网机):从 Bitwarden 取助记词进环境,独立验签后签名
PAY_SWEEP_MNEMONIC="word1 ... word24" \
go run ./cmd/sweep sign --cold TColdAddr... < unsigned.json > signed.json
# ④ 联网:广播
TRONGRID_API_KEY=... go run ./cmd/sweep broadcast < signed.json
sign 独立验签(气隙安全的关键)——对每笔交易:① 重算 txid=sha256(raw_data) 必须等于声称值
(防 raw_data 被篡改);② 收款人+金额的 ABI 参数必须内嵌在 raw_data(防换收款人/改额);③ USDT 合约
必须内嵌(防换币);④ 由助记词派生的地址必须等于 owner(防错钥匙)。任一不符即拒签。
gas:TRC20 转账要 energy,收款地址身上没 TRX——归集前先给这些地址垫少量 TRX(gas 钱包),
或用能量租赁。(垫 gas 的辅助后续加;当前 build 已设 --fee-limit。)
⚠️ 联网上链部分(
build/余额/broadcast)只在真链(Phase E)验证;纯 crypto (地址解码/ABI/txid/签名可恢复)已单测。首次务必小额实跑一遍再放量。
测试
go test ./...
wallet:派生一致性(xpub 路径 == 私钥路径)+ 金标准向量(需 A.4 核对)。store/pay/watcher/httpapi:建单/派生地址/侦测到账/幂等/超时/HTTP。