@rozoai/checkout 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/QUICKSTART.es.md +192 -0
- package/QUICKSTART.ja.md +187 -0
- package/QUICKSTART.md +188 -0
- package/QUICKSTART.zh.md +178 -0
- package/README.es.md +287 -0
- package/README.ja.md +265 -0
- package/README.md +270 -0
- package/README.zh.md +187 -0
- package/llms.txt +95 -0
- package/package.json +60 -0
- package/scripts/dist/blacklist.json +141 -0
- package/scripts/dist/cli.js +57390 -0
- package/scripts/dist/create-order.js +1332 -0
- package/scripts/dist/quote.js +396 -0
- package/scripts/dist/send-evm.js +22557 -0
- package/scripts/dist/send-sol.js +34695 -0
- package/scripts/dist/status.js +567 -0
package/README.zh.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# rozo-checkout
|
|
2
|
+
|
|
3
|
+
[English](README.md) | **简体中文** | [日本語](README.ja.md) | [Español](README.es.md)
|
|
4
|
+
|
|
5
|
+
**[快速开始 →](QUICKSTART.zh.md)** —— 五分钟付掉一个链接,无需读完本文。
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @rozoai/checkout pay <coinbase-link> --with usdt-solana
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
用一种 **OpenRouter Coinbase 收款链接 (Payment Link)** 本身无法直接接受的币种去付款 —— 走闪电网络的 BTC,或者 Solana、BNB Chain、Ethereum、Polygon、Base、Stellar 上的 USDT/USDC。
|
|
12
|
+
|
|
13
|
+
Coinbase 收款链接只接受 Base 上的 USDC。本仓库是一个 agent skill(以及背后的 Node 脚本),它把上面这些币种通过一座桥 (bridge) 路由过去:你会拿到一个针对你实际持有的币种的一次性充值地址 (deposit address),等你的充值到账后,一个出资钱包会代你结清这张 Coinbase 账单。
|
|
14
|
+
|
|
15
|
+
在账单本身上**没有折扣**:`callerPays` 等于账单金额。你要发送的**充值金额**是另一个数字,而且通常更大 —— 它包含了把账单金额以 Base USDC 形式送达所需的跨桥费用和源链费用。永远严格按后端返回的 `deposit.amount` 发送;绝不要假定它等于账单金额。
|
|
16
|
+
|
|
17
|
+
- `SKILL.md` —— 面向 agent 的指令(Claude Code skill 格式)。
|
|
18
|
+
- `llms.txt` —— 给只读一个文件的 agent 准备的精简摘要。
|
|
19
|
+
- `scripts/` —— Node 实现;`src/` 是源码,`dist/` 存放可以用纯 `node` 直接运行的自包含打包产物。
|
|
20
|
+
- `test/` —— 针对资金处理与安全逻辑的离线单元测试。
|
|
21
|
+
- `PLAN.md` —— 实现所遵循的设计文档。
|
|
22
|
+
|
|
23
|
+
使用它**不**需要账号、API key,也不需要与运营方有任何关系。它调用的每一个端点都是公开且无需密钥的。
|
|
24
|
+
|
|
25
|
+
## 工作原理
|
|
26
|
+
|
|
27
|
+
```mermaid
|
|
28
|
+
flowchart TD
|
|
29
|
+
A["Coinbase Payment Link<br/>payments.coinbase.com/payment-links/pl_*"] --> B
|
|
30
|
+
B["quote-invoice<br/>(public POST)"] --> C["create-invoice<br/>(public POST)"]
|
|
31
|
+
C --> D["Bridge order<br/>one-time deposit address<br/>+ exact amount + expiry"]
|
|
32
|
+
D --> E["You send USDT/USDC/BTC<br/>on your chosen chain"]
|
|
33
|
+
E --> F["Bridge converts the pay-in"]
|
|
34
|
+
F --> G["Funder wallet settles<br/>the Coinbase invoice"]
|
|
35
|
+
G --> H["Merchant is paid<br/>(e.g. OpenRouter credit)"]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
同样的流程用 ASCII 表示,供不支持 mermaid 的终端查看:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
Coinbase Payment Link (pl_* / paymentSession_*)
|
|
42
|
+
|
|
|
43
|
+
v
|
|
44
|
+
[ quote-invoice ] -> merchant, amount, expiry, short-lived quote receipt
|
|
45
|
+
|
|
|
46
|
+
v
|
|
47
|
+
[ create-invoice ] -> bridge order: rozoPaymentId + deposit address
|
|
48
|
+
| + exact amount + order expiry
|
|
49
|
+
v
|
|
50
|
+
you send USDT / USDC / BTC on your chain ------> deposit address
|
|
51
|
+
|
|
|
52
|
+
v
|
|
53
|
+
bridge converts the pay-in ------> funder wallet pays the Coinbase invoice
|
|
54
|
+
|
|
|
55
|
+
v
|
|
56
|
+
merchant credited; poll until the state is `settled`
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
全文出现三个标识符,它们彼此绝不可混用:
|
|
60
|
+
|
|
61
|
+
| 标识符 | 它是什么 |
|
|
62
|
+
|---|---|
|
|
63
|
+
| `linkId` | Coinbase 的 id:`pl_*`(收款链接)或 `paymentSession_*`(v3 会话) |
|
|
64
|
+
| `rozoPaymentId` | 桥订单的 UUID —— 查充值详情和状态用它 |
|
|
65
|
+
| `paymentLink` | 该桥订单对应的托管支付页面 URL(供人工兜底使用) |
|
|
66
|
+
|
|
67
|
+
## 支持的来源
|
|
68
|
+
|
|
69
|
+
| 链 | 链 id | 代币 | 备注 |
|
|
70
|
+
|---|---|---|---|
|
|
71
|
+
| Ethereum | `1` | USDC, USDT | 6 位小数 |
|
|
72
|
+
| BNB Chain | `56` | USDC, USDT | **18 位小数** —— 差 10^12 这类 bug 的常见来源 |
|
|
73
|
+
| Polygon | `137` | USDC, USDT | 6 位小数 |
|
|
74
|
+
| Base | `8453` | USDC | 6 位小数 |
|
|
75
|
+
| Solana | `900` | USDC, USDT | 6 位小数;SPL,可能需要 memo。原生 SOL **不**支持 |
|
|
76
|
+
| Stellar | `1500` | USDC | 7 位小数;必须带 memo |
|
|
77
|
+
| Bitcoin Lightning | `lightning` | BTC | 金额是整数**聪 (satoshis)**,通过闪电网络发票 (BOLT11) 支付 |
|
|
78
|
+
|
|
79
|
+
原生 gas 币(SOL、BNB、ETH、MATIC)以及链上 BTC 均不接受。
|
|
80
|
+
|
|
81
|
+
## 快速上手
|
|
82
|
+
|
|
83
|
+
### 1. 用 curl 试试公开端点
|
|
84
|
+
|
|
85
|
+
把 `pl_01YOURLINKID` 换成一个真实的 Coinbase 收款链接。下面所有请求都不需要任何 auth header。
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
MPP="https://apiserver.mpprouter.dev/v1/services/rozo-agent-api"
|
|
89
|
+
INTENTS="https://intentapiv4.rozo.ai/functions/v1/payment-api"
|
|
90
|
+
LINK="https://payments.coinbase.com/payment-links/pl_01YOURLINKID"
|
|
91
|
+
|
|
92
|
+
# 报价:返回商家、金额、过期时间,以及一个约 60 秒有效的 quoteReceipt。
|
|
93
|
+
curl -s -X POST "$MPP/quote-invoice" \
|
|
94
|
+
-H 'content-type: application/json' \
|
|
95
|
+
-d "{\"url\":\"$LINK\"}"
|
|
96
|
+
|
|
97
|
+
# 创建一个桥订单,比如用 Solana 上的 USDT。
|
|
98
|
+
# 这一步只创建订单,不动任何资金;未付款的订单会自动过期。
|
|
99
|
+
curl -s -X POST "$MPP/create-invoice" \
|
|
100
|
+
-H 'content-type: application/json' \
|
|
101
|
+
-d "{\"url\":\"$LINK\",\"source\":{\"chainId\":\"900\",\"tokenSymbol\":\"USDT\"}}"
|
|
102
|
+
|
|
103
|
+
# 充值指令(以此为准),用上一步拿到的 rozoPaymentId。
|
|
104
|
+
curl -s "$INTENTS/payments/<rozoPaymentId>"
|
|
105
|
+
|
|
106
|
+
# 履约状态,用 Coinbase 的 linkId 查询。
|
|
107
|
+
curl -s "$MPP/invoice-status?payment_id=pl_01YOURLINKID"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`create-invoice` 按 IP 限流(约每小时 30 次);读取类端点不限流。
|
|
111
|
+
|
|
112
|
+
### 2. 使用 CLI
|
|
113
|
+
|
|
114
|
+
无需安装任何东西 —— `npx` 会自动拉取并运行它:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
npx @rozoai/checkout quote <coinbase-link>
|
|
118
|
+
npx @rozoai/checkout pay <coinbase-link> --with usdt-solana
|
|
119
|
+
npx @rozoai/checkout status <rozoPaymentId>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`pay` 会把整个流程走完:报价、创建订单、展示打码后的核对信息、等你确认,然后打印充值指令并轮询直到结清。加 `--send` 就用热钱包付款而不是用你自己的钱包,加 `--json` 输出机器可读格式,其余选项看 `--help`。
|
|
123
|
+
|
|
124
|
+
### 3. 或者直接运行脚本
|
|
125
|
+
|
|
126
|
+
克隆本仓库后,你会拿到同样的流程,只不过是一个个独立脚本 —— agent skill 用的就是它们,CLI 底层调用的也是它们。
|
|
127
|
+
|
|
128
|
+
每个脚本在 stdout 上只打印一个 JSON 对象。退出码 `0` 表示成功,`1` 表示被拒绝/失败(读 `error.code`),`2` 表示用法错误,`3` 表示已提交但未确认。
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# 第 1 步 —— 只读报价,不产生任何费用
|
|
132
|
+
node scripts/dist/quote.js --url "$LINK"
|
|
133
|
+
|
|
134
|
+
# 第 2 步 —— 创建订单(不动资金;未付款的订单会过期)。
|
|
135
|
+
# 此阶段完整充值地址会被扣留:你只拿到打码后的摘要用于核对,
|
|
136
|
+
# 以及 `depositWithheld: true`。
|
|
137
|
+
node scripts/dist/create-order.js --url "$LINK" --chain 900 --token USDT
|
|
138
|
+
|
|
139
|
+
# 第 3 步 —— 核对金额、链、打码地址、是否需要 memo 以及过期时间。
|
|
140
|
+
# 只有在你决定付款之后,才用 --confirm 重新执行同一条命令。
|
|
141
|
+
# 这会释放完整的充值信息块,并记录下发送脚本所需的确认。
|
|
142
|
+
node scripts/dist/create-order.js --url "$LINK" --chain 900 --token USDT --confirm
|
|
143
|
+
|
|
144
|
+
# 第 4a 步 —— 模式 A:用你自己的钱包按充值信息块付款,
|
|
145
|
+
# 然后观察它结清
|
|
146
|
+
node scripts/dist/status.js --rozo-payment-id <uuid> --watch --timeout 600
|
|
147
|
+
|
|
148
|
+
# 第 4b 步 —— 模式 B:让脚本从热钱包付款。--dry-run 不会签任何名;
|
|
149
|
+
# 真正发送还额外需要 --send。
|
|
150
|
+
ROZO_CHECKOUT_SOL_KEY=<base58 secret key> \
|
|
151
|
+
node scripts/dist/send-sol.js --rozo-payment-id <uuid> --dry-run
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`scripts/dist/*.js` 是自包含的打包产物 —— 在调用现场无需 `npm install`。上面那个 CLI 就是同一份代码,只是外层更友好:它是导入这些流程,而不是重新实现一遍,所以下面列出的每一项检查,不管你从哪个入口进来都同样生效。
|
|
155
|
+
|
|
156
|
+
## 构建与测试
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
npm install # 只有重新构建或跑测试时才需要
|
|
160
|
+
npm run build # esbuild -> scripts/dist/*.js(node18 目标)+ blacklist.json
|
|
161
|
+
npm test # node:test,完全离线
|
|
162
|
+
npm run check # 构建 + 测试
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
测试**不发起任何网络调用**;每一个后端响应都是 `test/fixtures/` 里的 fixture。它们覆盖了原子金额换算(6/18 位小数与闪电网络的聪)、过期余量的算术、被盗地址归一化与 fail-closed 行为、订单复用决策、创建后的校验比对器,以及充值指令完整性规则。
|
|
166
|
+
|
|
167
|
+
有两组测试会真正 spawn 子进程而不是直接调用函数,因为它们测的是单进程测试测不了的东西:并发测试组会让多个进程去抢同一个订单(只允许恰好一个赢)并把会话额度耗尽;入口点测试组会运行构建好的发送脚本,证明它们在缺少 `--send` 时、缺少确认时、以及已被先前的 claim 占用之后都会拒绝执行。
|
|
168
|
+
|
|
169
|
+
## 安全设计
|
|
170
|
+
|
|
171
|
+
这个仓库真正有意思的地方,在于它拒绝去做哪些事。
|
|
172
|
+
|
|
173
|
+
- **两阶段确认,强制执行。** `create-order.js` 会扣留完整的充值地址、memo 和 BOLT11,直到用 `--confirm` 重新执行;此时会记录一条确认,并绑定到这份确切指令的 sha256 上。发送脚本在同时满足 `--send` 和一条摘要仍与实时数据匹配的确认之前,一律拒绝执行 —— 所以无论是误触发调用,还是被掉包的充值地址,都无法动用资金。
|
|
174
|
+
- **足额付款,永远如此。** `callerPays` 必须等于账单金额,`discount` 必须是 `"0"`;任何其他情况都以 `NO_DISCOUNT_VIOLATION` 中止。安全攸关的字段(`linkId`、`merchant`、`original`、`callerPays`,以及回显的 source)不仅要相等,还必须存在 —— 字段缺失即视为漂移。
|
|
175
|
+
- **复用防护。** 为一个已经存在未过期订单的链接再次创建订单,会返回那个已有订单 —— 哪怕它已经被付过款。因此每次运行都要求实时订单处于未付款状态(`payment_unpaid`,没有 tx hash、没有收到金额、没有确认),并且与调用方选择的链和代币一致。否则报:`ORDER_ALREADY_FUNDED` 或 `REUSED_SOURCE_MISMATCH`。
|
|
176
|
+
- **一旦检测到资金即 fail closed。** 只要存在任何入账,工具就绝不报告为一个普通失败,绝不建议再付一次,也绝不重试去开一个新订单。一个非 null 但无法解析的 `amountReceived` 算作"有钱",而不是"没钱"。后端不可读时报告 `unknown`,绝不报 `awaiting_deposit`。
|
|
177
|
+
- **完整的充值指令。** 金额为零、为负或无法解析都会中止。闪电网络必须有 BOLT11(它随 `source.lnInvoice` 返回,同时地址为空)。Stellar 必须有它的 memo —— memo 缺失是硬性中止,绝不会被渲染成"无需 memo"。
|
|
178
|
+
- **过期余量。** 取订单过期时间与 Coinbase 过期时间中较早的那个,除非它距现在超过按链设定的余量,否则拒绝付款(EVM 与 Stellar 为 10 分钟,Solana 为 5 分钟)。闪电网络还额外要求 BOLT11 至少还有 10 分钟有效期。截止时间缺失或无法解析都会中止。
|
|
179
|
+
- **可支付性复检。** 有了 quote receipt,创建订单时会跳过实时的 Coinbase 检查,而该链接随时可能被别人消费掉 —— 所以在展示充值地址之前会立刻重新检查一次可支付性,并在完成全部 RPC 准备工作之后、广播之前的最后一刻再检查一次。Coinbase 状态不完整时按"无法证明可支付"处理,而不是按可支付处理。
|
|
180
|
+
- **被盗地址清单,fail closed。** `scripts/src/lib/blacklist.json` 内置了一份 vendored 清单,带有来源出处头部以及对这些地址的 sha256。该摘要只能证明这份 vendored 副本自同步日期以来未被改动,它并不是上游来源的签名。充值地址和发送钱包都会被检查。如果该文件缺失、格式损坏、为空,或摘要对不上,那么所有发送都会被拒绝,而不是跳过检查继续执行。
|
|
181
|
+
- **只发一次,跨进程有效。** 订单状态存放在 `$HOME/.rozo-checkout/state/<uuid>.json`,以原子方式写入(临时文件、fsync、rename)。对状态文件的每一次读-改-写 —— claim、花费额度、订单记录和确认 —— 都在同一把独占 lockfile 内进行,因此两个并发调用既不会都认为自己是第一个,也不会互相覆盖对方的发送记录。发送会在广播*之前*被 claim,所以一个语义不明的 RPC 错误永远不会变成第二次转账。交易在广播前就已签名,因此 hash 是提前已知的;遇到结果不明确时,脚本会去查这笔确切的交易,而不是重新广播。
|
|
182
|
+
- **热钱包管控。** 私钥只从环境变量读取(`ROZO_CHECKOUT_EVM_KEY`、`ROZO_CHECKOUT_SOL_KEY`),永不打印,也绝不接受从命令行传入;库和 RPC 的错误在展示前会被脱敏,包括带凭据的 provider URL、bearer token 和形似密钥的字符串。当工作目录下任何 `.env`/`.env.*` 被 git 跟踪时,脚本拒绝运行;当 git 无法证明它未被跟踪时,同样坚决拒绝。签名前会校验 RPC 的链 id(Solana 则是 genesis hash)以及代币的链上小数位。额度上限:单笔 $100,单会话累计 $200。
|
|
183
|
+
- **地址打码。** 正文中显示为 `first6...last4`。完整的充值地址、memo 和 BOLT11 字符串只出现在机器可读的 `deposit` 对象里,这样它们既保持可复制粘贴,又不会散落在各处日志中。
|
|
184
|
+
|
|
185
|
+
## 许可证
|
|
186
|
+
|
|
187
|
+
MIT。
|
package/llms.txt
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# rozo-checkout
|
|
2
|
+
|
|
3
|
+
> Pay an OpenRouter Coinbase Payment Link (payments.coinbase.com/payment-links/pl_* or
|
|
4
|
+
> /payment-sessions/paymentSession_*) with BTC over Lightning, or USDT/USDC on Solana,
|
|
5
|
+
> BNB Chain, Ethereum, Polygon, Base, or Stellar. Public HTTP endpoints, no API key.
|
|
6
|
+
> The payer funds a one-time deposit address; a bridge and a funder wallet settle the
|
|
7
|
+
> Coinbase link automatically. No discount: you pay the invoice amount, plus the
|
|
8
|
+
> deposit normally carries a small bridge/source-chain fee on top.
|
|
9
|
+
|
|
10
|
+
This file teaches the whole flow to any agent or script. Repo (scripts, safety rails,
|
|
11
|
+
full docs): https://github.com/RozoAI/rozo-checkout-skill — see QUICKSTART.md.
|
|
12
|
+
|
|
13
|
+
## Fastest path: the CLI (npm @rozoai/checkout)
|
|
14
|
+
|
|
15
|
+
If you can run commands, skip the raw HTTP below — the CLI performs the same four
|
|
16
|
+
calls with every safety rule already enforced:
|
|
17
|
+
|
|
18
|
+
npx @rozoai/checkout pay <coinbase-link> --with usdt-solana
|
|
19
|
+
npx @rozoai/checkout quote <coinbase-link>
|
|
20
|
+
npx @rozoai/checkout status <rozoPaymentId | coinbase-link>
|
|
21
|
+
|
|
22
|
+
--with accepts usdt-solana, usdc-solana, usdt-bnb, usdc-bnb, usdt-ethereum,
|
|
23
|
+
usdc-ethereum, usdt-polygon, usdc-polygon, usdc-base, usdc-stellar, btc-lightning
|
|
24
|
+
(or raw: --chain 900 --token USDT). Flags: --send (pay from a hot wallet, keys from
|
|
25
|
+
ROZO_CHECKOUT_EVM_KEY / ROZO_CHECKOUT_SOL_KEY, never from argv), --yes (skip the
|
|
26
|
+
interactive confirm; required when stdin is not a TTY), --dry-run, --json (exactly
|
|
27
|
+
one JSON object on stdout), --no-watch, --timeout <s>. Exit codes: 0 success,
|
|
28
|
+
1 refused/failed (read error.code), 2 usage, 3 submitted but unconfirmed.
|
|
29
|
+
|
|
30
|
+
`pay` withholds the full deposit address until you confirm, then prints it as a
|
|
31
|
+
copy-pastable block. Creating an order moves no money; an unfunded order expires.
|
|
32
|
+
|
|
33
|
+
## Supported sources
|
|
34
|
+
|
|
35
|
+
chainId "1" Ethereum (USDC, USDT) · "56" BNB Chain (USDC, USDT; 18-decimals) ·
|
|
36
|
+
"137" Polygon (USDC, USDT) · "8453" Base (USDC) · "900" Solana (USDC, USDT) ·
|
|
37
|
+
"1500" Stellar (USDC; deposit memo REQUIRED) · "lightning" (BTC; BOLT11 invoice,
|
|
38
|
+
amounts in integer satoshis via source.amountUnit=="sats").
|
|
39
|
+
Native SOL/ETH/BNB are NOT supported — stablecoins (or Lightning BTC) only.
|
|
40
|
+
|
|
41
|
+
## Flow (4 HTTP calls, all public)
|
|
42
|
+
|
|
43
|
+
1) Quote (read-only, safe):
|
|
44
|
+
curl -X POST https://apiserver.mpprouter.dev/v1/services/rozo-agent-api/quote-invoice \
|
|
45
|
+
-H 'content-type: application/json' \
|
|
46
|
+
-d '{"url":"https://payments.coinbase.com/payment-links/pl_XXXX"}'
|
|
47
|
+
-> { linkId, merchant, original, callerPays, discount:"0", quoteReceipt, ... }
|
|
48
|
+
callerPays always equals the invoice amount (discount is always "0").
|
|
49
|
+
|
|
50
|
+
2) Create (or reuse) the order:
|
|
51
|
+
curl -X POST https://apiserver.mpprouter.dev/v1/services/rozo-agent-api/create-invoice \
|
|
52
|
+
-H 'content-type: application/json' \
|
|
53
|
+
-d '{"url":"https://payments.coinbase.com/payment-links/pl_XXXX",
|
|
54
|
+
"source":{"chainId":"900","tokenSymbol":"USDT"}}'
|
|
55
|
+
-> { ok, reused, linkId, rozoPaymentId, paymentLink, expiresAt, source, ... }
|
|
56
|
+
WARNING: if an unexpired order already exists for this link it is REUSED as-is,
|
|
57
|
+
even with a different chain/token than you asked for. Always verify the returned
|
|
58
|
+
source matches your request; abort on mismatch.
|
|
59
|
+
|
|
60
|
+
3) Fetch authoritative deposit instructions (public, keyless GET):
|
|
61
|
+
curl https://intentapiv4.rozo.ai/functions/v1/payment-api/payments/<rozoPaymentId>
|
|
62
|
+
-> { status, expiresAt, source:{ receiverAddress, receiverMemo, amount, amountUnit,
|
|
63
|
+
lnInvoice, txHash, amountReceived, confirmedAt, ... }, destination:{...} }
|
|
64
|
+
Pay EXACTLY source.amount of the requested token on the requested chain to
|
|
65
|
+
source.receiverAddress (include receiverMemo when present — Stellar requires it).
|
|
66
|
+
For Lightning, pay the BOLT11 in source.lnInvoice (receiverAddress is empty).
|
|
67
|
+
|
|
68
|
+
4) Poll until settled:
|
|
69
|
+
curl "https://apiserver.mpprouter.dev/v1/services/rozo-agent-api/invoice-status?payment_id=<linkId>"
|
|
70
|
+
and re-GET the payments/<rozoPaymentId> URL above. The Coinbase link is settled
|
|
71
|
+
only when the router reports paid / coinbase.settled — an intents status of
|
|
72
|
+
payment_completed alone is the bridge lifecycle, not final settlement.
|
|
73
|
+
|
|
74
|
+
## Safety rules (encode these, they prevent fund loss)
|
|
75
|
+
|
|
76
|
+
- Before paying, require: status == "payment_unpaid" AND source.txHash == null AND
|
|
77
|
+
source.amountReceived in {null,"0"} AND source.confirmedAt == null. Anything else
|
|
78
|
+
means the order is already funded — do NOT pay again; escalate for reconciliation.
|
|
79
|
+
- Deposit addresses are one-time and quote-bound. Never reuse an address from an old
|
|
80
|
+
order, a cached response, or a document. Always re-fetch step 3 in the same run.
|
|
81
|
+
- Respect expiry with margin: do not send if now > min(order expiresAt, Coinbase
|
|
82
|
+
link expiry) minus a safety margin (10 min EVM/Stellar, 5 min Solana; Lightning:
|
|
83
|
+
only pay a BOLT11 with >= 10 min validity left).
|
|
84
|
+
- Re-check step 4 payability immediately before broadcasting a transaction: the
|
|
85
|
+
Coinbase link can be consumed by another payer at any moment.
|
|
86
|
+
- Wrong token, wrong network, wrong amount, or paying twice is usually unrecoverable.
|
|
87
|
+
- After any on-chain payment is detected, never retry into a new order and never
|
|
88
|
+
label the payment a normal failure — keep the tx hash and rozoPaymentId, keep
|
|
89
|
+
polling, and escalate if it stays stuck.
|
|
90
|
+
|
|
91
|
+
## Errors
|
|
92
|
+
|
|
93
|
+
409 = link already used · 410 = link expired · UNSUPPORTED_SOURCE = the response's
|
|
94
|
+
"supported" object lists valid (chainId, tokenSymbol) pairs (Lightning is supported
|
|
95
|
+
but omitted from that list) · RATE_LIMITED = per-IP creation cap, retry later.
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rozoai/checkout",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Pay an OpenRouter Coinbase Payment Link with BTC Lightning, or USDT/USDC on Solana, BNB Chain, Ethereum, Polygon, Base or Stellar.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Rozo AI",
|
|
8
|
+
"homepage": "https://github.com/RozoAI/rozo-checkout-skill#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/RozoAI/rozo-checkout-skill.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/RozoAI/rozo-checkout-skill/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"openrouter",
|
|
18
|
+
"coinbase",
|
|
19
|
+
"x402",
|
|
20
|
+
"lightning",
|
|
21
|
+
"solana",
|
|
22
|
+
"usdt",
|
|
23
|
+
"usdc",
|
|
24
|
+
"crypto-payments"
|
|
25
|
+
],
|
|
26
|
+
"bin": {
|
|
27
|
+
"rozo-checkout": "scripts/dist/cli.js"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"scripts/dist",
|
|
31
|
+
"llms.txt",
|
|
32
|
+
"README.md",
|
|
33
|
+
"README.zh.md",
|
|
34
|
+
"README.ja.md",
|
|
35
|
+
"README.es.md",
|
|
36
|
+
"QUICKSTART.md",
|
|
37
|
+
"QUICKSTART.zh.md",
|
|
38
|
+
"QUICKSTART.ja.md",
|
|
39
|
+
"QUICKSTART.es.md",
|
|
40
|
+
"LICENSE"
|
|
41
|
+
],
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "node scripts/build.mjs",
|
|
50
|
+
"test": "node --test test/*.test.mjs",
|
|
51
|
+
"check": "npm run build && npm test",
|
|
52
|
+
"prepack": "npm run build"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@solana/spl-token": "^0.4.9",
|
|
56
|
+
"@solana/web3.js": "^1.95.0",
|
|
57
|
+
"esbuild": "^0.24.0",
|
|
58
|
+
"viem": "^2.21.0"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
{
|
|
2
|
+
"provenance": {
|
|
3
|
+
"source": "compromised-wallet table in the ainative repository CLAUDE.md (operator security policy)",
|
|
4
|
+
"description": "Addresses recorded there as compromised (leaked key, drained wallet) or as attacker aggregation points, vendored into this repo so the check works offline and cannot be silently disabled.",
|
|
5
|
+
"syncDate": "2026-08-08",
|
|
6
|
+
"syncedOn": "2026-08-08",
|
|
7
|
+
"addressCount": 18,
|
|
8
|
+
"addressesSha256": "12cb2019f4b93bde94d8f5bea594009620a3e4ae9d6b6d4d389912da7144d085",
|
|
9
|
+
"shaAlgorithm": "sha256(JSON.stringify(entries.map(e => e.address)))",
|
|
10
|
+
"digestScope": "The digest proves only that this vendored list has not been edited since the sync date. It is NOT a signature by the upstream source and does not attest that the upstream table still matches.",
|
|
11
|
+
"failMode": "closed — if this file is missing, malformed, empty, or the digest does not match, every send is refused"
|
|
12
|
+
},
|
|
13
|
+
"entries": [
|
|
14
|
+
{
|
|
15
|
+
"address": "GD2UZOA5RFWILHTPQL6CDTLIT6XPEGZQXJX4NWQC7ZU7DCEXK5NSQ2GH",
|
|
16
|
+
"chain": "stellar",
|
|
17
|
+
"family": "stellar",
|
|
18
|
+
"reportedOn": "2026-05-24",
|
|
19
|
+
"note": "Reported compromised by the operator; blacklisted preemptively"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"address": "0x8FE7155119d2975780c9e19B07dD98393965Bc2a",
|
|
23
|
+
"chain": "evm",
|
|
24
|
+
"family": "evm",
|
|
25
|
+
"reportedOn": "2026-06-26",
|
|
26
|
+
"note": "Wallet drained in a reported key-compromise incident"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"address": "0xa9E3Da13EF5eADFC6EcB2BB6BDddE95016B567dB",
|
|
30
|
+
"chain": "evm",
|
|
31
|
+
"family": "evm",
|
|
32
|
+
"reportedOn": "2026-06-26",
|
|
33
|
+
"note": "Wallet drained in a reported key-compromise incident"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"address": "0x5772FBe7a7817ef7F586215CA8b23b8dD22C8897",
|
|
37
|
+
"chain": "evm",
|
|
38
|
+
"family": "evm",
|
|
39
|
+
"reportedOn": "2026-06-26",
|
|
40
|
+
"note": "Wallet drained in a reported key-compromise incident"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"address": "AEEtekA2EBYVy3e5Xx8fD3GkjWSoCsLvLzdD6pZTgHiH",
|
|
44
|
+
"chain": "solana",
|
|
45
|
+
"family": "solana",
|
|
46
|
+
"reportedOn": "2026-06-26",
|
|
47
|
+
"note": "Wallet drained in a reported key-compromise incident"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"address": "TBcegJf63xa5r7hi5jmqSsTnSEAAFkUcGu",
|
|
51
|
+
"chain": "tron",
|
|
52
|
+
"family": "tron",
|
|
53
|
+
"reportedOn": "2026-06-26",
|
|
54
|
+
"note": "Wallet drained in a reported key-compromise incident"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"address": "0x44d6B5a11FFc5Ba1043734d88af5E5dea36a648A",
|
|
58
|
+
"chain": "evm",
|
|
59
|
+
"family": "evm",
|
|
60
|
+
"reportedOn": "2026-06-26",
|
|
61
|
+
"note": "Attacker aggregation address — never interact"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"address": "0x467AeD16d024405116cF4Ba12976Bf63B404517b",
|
|
65
|
+
"chain": "evm",
|
|
66
|
+
"family": "evm",
|
|
67
|
+
"reportedOn": "2026-06-26",
|
|
68
|
+
"note": "Attacker aggregation address — never interact"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"address": "a23F2uanwzTDtWJmPK1y1DiKbnR7vCNZTqdQEacRz8W",
|
|
72
|
+
"chain": "solana",
|
|
73
|
+
"family": "solana",
|
|
74
|
+
"reportedOn": "2026-06-26",
|
|
75
|
+
"note": "Attacker aggregation address — never interact"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"address": "0xF621Ee3BaE3cbE924Ec05f795d14E31384Bd11b6",
|
|
79
|
+
"chain": "evm",
|
|
80
|
+
"family": "evm",
|
|
81
|
+
"reportedOn": "2026-06-26",
|
|
82
|
+
"note": "Attacker aggregation address — never interact"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"address": "9Ms2FNXMY9ucKwzxcnGxvqMcfSkBhRdPZhrsj1Ui1KiN",
|
|
86
|
+
"chain": "solana",
|
|
87
|
+
"family": "solana",
|
|
88
|
+
"reportedOn": "2026-06-26",
|
|
89
|
+
"note": "Attacker aggregation address — never interact"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"address": "0x49CD5655Cc9bf7c7C93fBb2DF36AA3020d11eEe0",
|
|
93
|
+
"chain": "evm",
|
|
94
|
+
"family": "evm",
|
|
95
|
+
"reportedOn": "2026-07-03",
|
|
96
|
+
"note": "Retired test wallet declared leaked"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"address": "EHhTSkqPpu4TENNpdyLewgxaMckSUke3c9hoNVu6zNMA",
|
|
100
|
+
"chain": "solana",
|
|
101
|
+
"family": "solana",
|
|
102
|
+
"reportedOn": "2026-07-03",
|
|
103
|
+
"note": "Retired test wallet declared leaked"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"address": "He9F3sHpDLN1br4Ts7AHpqQbGZaDkqgu2QK6dw93RpAk",
|
|
107
|
+
"chain": "solana",
|
|
108
|
+
"family": "solana",
|
|
109
|
+
"reportedOn": "2026-07-03",
|
|
110
|
+
"note": "Retired test wallet declared leaked"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"address": "GAN3YSPDH5VW7YFJJFUJH7LIYTJBWGH3GJMKOG6FP5RKHXGNMPX44UYY",
|
|
114
|
+
"chain": "stellar",
|
|
115
|
+
"family": "stellar",
|
|
116
|
+
"reportedOn": "2026-07-03",
|
|
117
|
+
"note": "Retired test wallet declared leaked"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"address": "TE78sm1mFajxtPjKMWQkLQTaBKQVqJVum4",
|
|
121
|
+
"chain": "tron",
|
|
122
|
+
"family": "tron",
|
|
123
|
+
"reportedOn": "2026-07-09",
|
|
124
|
+
"note": "Reported leaked wallet"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"address": "0xa9BacE1614d6cFf8aa159A2A41eE8BaA9a91Cc7B",
|
|
128
|
+
"chain": "evm",
|
|
129
|
+
"family": "evm",
|
|
130
|
+
"reportedOn": "2026-07-15",
|
|
131
|
+
"note": "Rotated-out hot wallet; private key leaked"
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"address": "0xfD0e6fA2ABA8436e95f3Fb3523AC14Ba299c0e79",
|
|
135
|
+
"chain": "evm",
|
|
136
|
+
"family": "evm",
|
|
137
|
+
"reportedOn": "2026-08-06",
|
|
138
|
+
"note": "Hot wallet with a compromised key; sweeper assumed active"
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
}
|