@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/README.ja.md ADDED
@@ -0,0 +1,265 @@
1
+ # rozo-checkout
2
+
3
+ [English](README.md) | [简体中文](README.zh.md) | **日本語** | [Español](README.es.md)
4
+
5
+ **[クイックスタート →](QUICKSTART.ja.md)** — 本文を読まずに、5 分でリンクを支払う。
6
+
7
+ ```bash
8
+ npx @rozoai/checkout pay <coinbase-link> --with usdt-solana
9
+ ```
10
+
11
+ **OpenRouter の Coinbase 決済リンク (Payment Link)** を、そのリンクが直接受け取れない
12
+ 通貨で支払うためのものです — Lightning 経由の BTC、あるいは Solana、BNB Chain、Ethereum、
13
+ Polygon、Base、Stellar 上の USDT/USDC が使えます。
14
+
15
+ Coinbase の決済リンクは Base 上の USDC しか受け付けません。このリポジトリはエージェント
16
+ スキル (agent skill) であり、その裏で動く Node スクリプトも含みます。上記のいずれの通貨も
17
+ ブリッジ (bridge) を通して中継します。実際に保有している通貨向けの使い捨て入金アドレス
18
+ (deposit address) が発行され、入金が着金すると資金提供ウォレット (funder wallet) が
19
+ あなたに代わって Coinbase のインボイスを決済します。
20
+
21
+ インボイス自体には**割引はありません**。`callerPays` はインボイス金額と等しくなります。
22
+ 一方、あなたが送金する**入金額**はそれとは別の数値であり、通常はより大きくなります。
23
+ インボイス金額を Base USDC で届けるために必要なブリッジ手数料とソースチェーン手数料が
24
+ 含まれるためです。バックエンドが返した `deposit.amount` を必ずそのまま送金してください。
25
+ インボイス金額と等しいと決して思い込まないでください。
26
+
27
+ - `SKILL.md` — エージェント向けの指示書(Claude Code のスキル形式)。
28
+ - `llms.txt` — 1 ファイルだけ読むエージェント向けの簡潔な要約。
29
+ - `scripts/` — Node による実装。`src/` がソースで、`dist/` には素の `node` で実行できる
30
+ 自己完結型のバンドルが入っています。
31
+ - `test/` — 資金処理と安全性ロジックのオフライン単体テスト。
32
+ - `PLAN.md` — 実装が従っている設計ドキュメント。
33
+
34
+ 利用にあたって、アカウント、API キー、運営者との何らかの関係は**一切必要ありません**。
35
+ 呼び出す全エンドポイントは公開かつ鍵不要です。
36
+
37
+ ## 仕組み
38
+
39
+ ```mermaid
40
+ flowchart TD
41
+ A["Coinbase Payment Link<br/>payments.coinbase.com/payment-links/pl_*"] --> B
42
+ B["quote-invoice<br/>(public POST)"] --> C["create-invoice<br/>(public POST)"]
43
+ C --> D["Bridge order<br/>one-time deposit address<br/>+ exact amount + expiry"]
44
+ D --> E["You send USDT/USDC/BTC<br/>on your chosen chain"]
45
+ E --> F["Bridge converts the pay-in"]
46
+ F --> G["Funder wallet settles<br/>the Coinbase invoice"]
47
+ G --> H["Merchant is paid<br/>(e.g. OpenRouter credit)"]
48
+ ```
49
+
50
+ mermaid を表示できない端末向けに、同じ内容を ASCII で示します。
51
+
52
+ ```
53
+ Coinbase Payment Link (pl_* / paymentSession_*)
54
+ |
55
+ v
56
+ [ quote-invoice ] -> merchant, amount, expiry, short-lived quote receipt
57
+ |
58
+ v
59
+ [ create-invoice ] -> bridge order: rozoPaymentId + deposit address
60
+ | + exact amount + order expiry
61
+ v
62
+ you send USDT / USDC / BTC on your chain ------> deposit address
63
+ |
64
+ v
65
+ bridge converts the pay-in ------> funder wallet pays the Coinbase invoice
66
+ |
67
+ v
68
+ merchant credited; poll until the state is `settled`
69
+ ```
70
+
71
+ 全体を通して 3 つの識別子が登場します。これらは決して互換ではありません。
72
+
73
+ | 識別子 | 内容 |
74
+ |---|---|
75
+ | `linkId` | Coinbase 側の id: `pl_*`(決済リンク)または `paymentSession_*`(v3 セッション) |
76
+ | `rozoPaymentId` | ブリッジ注文の UUID — 入金詳細とステータスの照会にはこれを使います |
77
+ | `paymentLink` | ブリッジ注文用のホスト型決済ページ URL(人手によるフォールバック) |
78
+
79
+ ## 対応するソース
80
+
81
+ | チェーン | チェーン id | トークン | 備考 |
82
+ |---|---|---|---|
83
+ | Ethereum | `1` | USDC, USDT | 小数点以下 6 桁 |
84
+ | BNB Chain | `56` | USDC, USDT | **小数点以下 18 桁** — 10^12 のずれによるバグの典型的な原因 |
85
+ | Polygon | `137` | USDC, USDT | 小数点以下 6 桁 |
86
+ | Base | `8453` | USDC | 小数点以下 6 桁 |
87
+ | Solana | `900` | USDC, USDT | 小数点以下 6 桁。SPL で、メモ (memo) が必要な場合あり。ネイティブの SOL は**非対応** |
88
+ | Stellar | `1500` | USDC | 小数点以下 7 桁。メモが必須 |
89
+ | Bitcoin Lightning | `lightning` | BTC | 金額は整数の **satoshi**。BOLT11 インボイス (BOLT11) で支払います |
90
+
91
+ ネイティブのガス通貨(SOL、BNB、ETH、MATIC)およびオンチェーンの BTC は受け付けません。
92
+
93
+ ## クイックスタート
94
+
95
+ ### 1. curl で公開エンドポイントを試す
96
+
97
+ `pl_01YOURLINKID` を実際の Coinbase 決済リンクに置き換えてください。以下のどれにも認証
98
+ ヘッダーは不要です。
99
+
100
+ ```bash
101
+ MPP="https://apiserver.mpprouter.dev/v1/services/rozo-agent-api"
102
+ INTENTS="https://intentapiv4.rozo.ai/functions/v1/payment-api"
103
+ LINK="https://payments.coinbase.com/payment-links/pl_01YOURLINKID"
104
+
105
+ # 見積もりを取得: 加盟店、金額、有効期限、約 60 秒有効な quoteReceipt。
106
+ curl -s -X POST "$MPP/quote-invoice" \
107
+ -H 'content-type: application/json' \
108
+ -d "{\"url\":\"$LINK\"}"
109
+
110
+ # 例として Solana 上の USDT でブリッジ注文を作成します。
111
+ # 注文を作るだけで資金は動きません。入金のない注文はそのまま期限切れになります。
112
+ curl -s -X POST "$MPP/create-invoice" \
113
+ -H 'content-type: application/json' \
114
+ -d "{\"url\":\"$LINK\",\"source\":{\"chainId\":\"900\",\"tokenSymbol\":\"USDT\"}}"
115
+
116
+ # 入金手順(これが正)。上で得た rozoPaymentId を使います。
117
+ curl -s "$INTENTS/payments/<rozoPaymentId>"
118
+
119
+ # 履行ステータス。Coinbase の linkId を使います。
120
+ curl -s "$MPP/invoice-status?payment_id=pl_01YOURLINKID"
121
+ ```
122
+
123
+ `create-invoice` は IP 単位でレート制限されています(およそ 30 回/時)。読み取り系
124
+ エンドポイントには制限はありません。
125
+
126
+ ### 2. CLI を使う
127
+
128
+ インストールは不要です。`npx` が取得して実行します:
129
+
130
+ ```bash
131
+ npx @rozoai/checkout quote <coinbase-link>
132
+ npx @rozoai/checkout pay <coinbase-link> --with usdt-solana
133
+ npx @rozoai/checkout status <rozoPaymentId>
134
+ ```
135
+
136
+ `pay` はフロー全体を進めます。見積もりを取り、注文を作成し、マスクされた確認内容を表示し、
137
+ 承認を求め、そのうえで入金手順を出力して決済完了までポーリングします。自分のウォレット
138
+ ではなくホットウォレットから支払う場合は `--send` を、機械可読な出力が必要な場合は
139
+ `--json` を、それ以外については `--help` を追加してください。
140
+
141
+ ### 3. あるいはスクリプトを直接実行する
142
+
143
+ リポジトリをクローンすると、同じフローが個別のスクリプトとして手に入ります。エージェント
144
+ スキルが使っているのはこちらで、CLI も内部ではこれを呼び出しています。
145
+
146
+ 各スクリプトは標準出力にちょうど 1 個の JSON オブジェクトを出力します。終了コードは
147
+ `0` が成功、`1` が拒否/失敗(`error.code` を参照)、`2` が使い方の誤り、`3` が送信済みだが
148
+ 未確認です。
149
+
150
+ ```bash
151
+ # ステップ 1 — 読み取り専用の見積もり。費用はかかりません
152
+ node scripts/dist/quote.js --url "$LINK"
153
+
154
+ # ステップ 2 — 注文を作成します(資金は動きません。入金のない注文は期限切れになります)。
155
+ # この段階では完全な入金アドレスは伏せられます。確認用のマスクされた
156
+ # サマリーと `depositWithheld: true` が返ります。
157
+ node scripts/dist/create-order.js --url "$LINK" --chain 900 --token USDT
158
+
159
+ # ステップ 3 — 金額、チェーン、マスクされたアドレス、メモの要否、有効期限を確認します。
160
+ # 支払うと決めたときにだけ、同じコマンドを --confirm 付きで実行し直して
161
+ # ください。これで完全な deposit ブロックが開示され、送金スクリプトが要求する
162
+ # 確認記録が残ります。
163
+ node scripts/dist/create-order.js --url "$LINK" --chain 900 --token USDT --confirm
164
+
165
+ # ステップ 4a — モード A: deposit ブロックの内容を自分のウォレットから支払い、
166
+ # その後の決済完了を監視します
167
+ node scripts/dist/status.js --rozo-payment-id <uuid> --watch --timeout 600
168
+
169
+ # ステップ 4b — モード B: スクリプトにホットウォレットから支払わせます。--dry-run では
170
+ # 署名は一切行いません。実際に送金するにはさらに --send が必要です。
171
+ ROZO_CHECKOUT_SOL_KEY=<base58 secret key> \
172
+ node scripts/dist/send-sol.js --rozo-payment-id <uuid> --dry-run
173
+ ```
174
+
175
+ `scripts/dist/*.js` は自己完結型のバンドルです。呼び出し側で `npm install` を実行する
176
+ 必要はありません。上記の CLI は同じコードにより扱いやすい入り口を付けたもので、これらの
177
+ フローを再実装するのではなくインポートしています。したがって、以下に挙げるチェックは
178
+ どちらの入り口を使っても等しく適用されます。
179
+
180
+ ## ビルドとテスト
181
+
182
+ ```bash
183
+ npm install # 再ビルドまたはテスト実行のときだけ必要です
184
+ npm run build # esbuild -> scripts/dist/*.js (node18 ターゲット) + blacklist.json
185
+ npm test # node:test、完全オフライン
186
+ npm run check # ビルド + テスト
187
+ ```
188
+
189
+ テストは**ネットワーク通信を一切行いません**。バックエンドのレスポンスはすべて
190
+ `test/fixtures/` 内のフィクスチャです。テスト対象は、最小単位への金額変換(小数点以下
191
+ 6/18 桁と Lightning の satoshi)、有効期限マージンの計算、危殆化アドレスの正規化と
192
+ フェイルクローズ動作、注文再利用の判定、作成後の検証比較器、入金手順の完全性ルールです。
193
+
194
+ 2 つのグループは関数呼び出しではなく実際の子プロセスを起動します。単一プロセスの
195
+ テストでは検証できないものを対象とするためです。並行性スイートは複数プロセスを競合させて
196
+ 1 つの注文の獲得(勝てるのはちょうど 1 つだけ)とセッション上限の使い切りを検証し、
197
+ エントリーポイントスイートはビルド済みの送金スクリプトを実行して、`--send` がない場合、
198
+ 確認がない場合、既に他が獲得済みの場合に拒否することを証明します。
199
+
200
+ ## 安全性の設計
201
+
202
+ このリポジトリで興味深いのは、何をするかではなく、何を頑として拒否するかです。
203
+
204
+ - **二段階確認の強制。** `create-order.js` は、`--confirm` 付きで再実行されるまで完全な
205
+ 入金アドレス、メモ、BOLT11 を伏せます。再実行時には、その手順そのものの sha256 に
206
+ 紐づいた確認記録が保存されます。送金スクリプトは、`--send` と、ダイジェストが現在の
207
+ データと一致し続けている確認記録の両方がなければ実行を拒否します。したがって、誤った
208
+ 実行でも、すり替えられた入金アドレスでも、資金は動きません。
209
+ - **常にインボイス全額。** `callerPays` はインボイス金額と等しくなければならず、
210
+ `discount` は `"0"` でなければなりません。それ以外は `NO_DISCOUNT_VIOLATION` で中断します。
211
+ セキュリティ上重要なフィールド(`linkId`、`merchant`、`original`、`callerPays`、
212
+ エコーバックされた source)は、値が一致しているだけでなく存在していることも必要です。
213
+ 欠落しているフィールドは乖離 (drift) とみなします。
214
+ - **再利用ガード。** すでに期限切れでない注文があるリンクに対して注文を作成すると、その
215
+ 既存の注文が返されます。たとえその注文がすでに入金済みであってもです。そのため実行の
216
+ たびに、現在の注文が未入金であること(`payment_unpaid`、tx ハッシュなし、受領金額なし、
217
+ 確認なし)と、呼び出し側が選んだチェーンおよびトークンと一致することを必須としています。
218
+ そうでない場合は `ORDER_ALREADY_FUNDED` または `REUSED_SOURCE_MISMATCH` になります。
219
+ - **入金検知ルール、フェイルクローズ。** いったん何らかの入金が存在すると、このツールは
220
+ 単純な失敗として報告することも、再度の支払いを勧めることも、新しい注文で再試行することも
221
+ ありません。`amountReceived` が null ではないが読み取れない場合は、資金がないのではなく
222
+ 資金があるものとして扱います。バックエンドが読み取れない場合は `awaiting_deposit` では
223
+ なく `unknown` を報告します。
224
+ - **完全な入金手順。** 金額がゼロ、負数、あるいは解析不能な場合は中断します。Lightning では
225
+ BOLT11 が必須です(アドレスは空で、`source.lnInvoice` に入って届きます)。Stellar では
226
+ メモが必須で、メモの欠落は「メモ不要」と表示されることは決してなく、必ず強制中断となります。
227
+ - **有効期限マージン。** 注文の有効期限と Coinbase の有効期限のうち早いほうが、チェーン
228
+ ごとのマージン(EVM と Stellar は 10 分、Solana は 5 分)を超えて先でない限り、支払いは
229
+ 拒否されます。Lightning ではさらに BOLT11 の有効期間が最低 10 分残っている必要があります。
230
+ 期限が欠落している、または解析できない場合は中断します。
231
+ - **支払い可能性の再検証。** quote receipt があると注文作成時のライブ Coinbase チェックが
232
+ スキップされ、またリンクはいつ他人に消費されてもおかしくありません。そのため、入金
233
+ アドレスを表示する直前に支払い可能性を再確認し、さらに RPC の準備がすべて済んだ後、
234
+ ブロードキャスト直前の最終ステップとしてもう一度確認します。Coinbase の状態が不完全な
235
+ 場合は、支払い可能ではなく「支払い可能だと証明できない」として扱います。
236
+ - **危殆化アドレスリスト、フェイルクローズ。** `scripts/src/lib/blacklist.json` には、
237
+ 出所ヘッダーとアドレス全体の sha256 を伴うベンダリング済みのリストが入っています。この
238
+ ダイジェストが証明するのは、このベンダリングされたコピーが同期日以降編集されていない
239
+ ことだけであり、上流ソースによる署名ではありません。入金アドレスと送金元ウォレットの
240
+ 両方がチェックされます。ファイルが存在しない、形式が不正、空、あるいはダイジェストが
241
+ 一致しない場合は、未チェックのまま処理を続けるのではなく、すべての送金を拒否します。
242
+ - **プロセスをまたいだ一度きりの送金。** 注文の状態は
243
+ `$HOME/.rozo-checkout/state/<uuid>.json` に置かれ、アトミックに書き込まれます
244
+ (一時ファイル、fsync、rename)。状態ファイルの read-modify-write は、獲得処理、支出上限、
245
+ 注文レコード、確認記録のいずれも、単一の排他ロックファイルの内側で実行されます。
246
+ そのため、同時に起動した 2 つの実行が両方とも自分が先だと判断することも、互いの送金
247
+ 記録を上書きすることもありません。送金はブロードキャストの*前*に獲得されるため、
248
+ 曖昧な RPC エラーが二重送金になることは決してありません。トランザクションは
249
+ ブロードキャスト前に署名されるのでハッシュは事前に判明しており、結果が曖昧な場合、
250
+ スクリプトは再ブロードキャストせずにそのトランザクションそのものを照会します。
251
+ - **ホットウォレットの制御。** 鍵は環境変数からのみ取得され
252
+ (`ROZO_CHECKOUT_EVM_KEY`、`ROZO_CHECKOUT_SOL_KEY`)、決して出力されず、コマンドライン
253
+ から渡すこともできません。ライブラリと RPC のエラーは、認証情報を含むプロバイダー URL、
254
+ bearer トークン、鍵のような形の文字列も含めて、表示前に伏字化されます。作業ディレクトリの
255
+ `.env`/`.env.*` のいずれかが git で追跡されている場合、スクリプトは実行を拒否します。
256
+ git が未追跡であることを証明できない場合も同様に拒否します。署名の前に、RPC の
257
+ チェーン id(Solana ではジェネシスハッシュ)とトークンのオンチェーン小数桁数が検証されます。
258
+ 上限はトランザクションごとに $100、セッション累計で $200 です。
259
+ - **アドレスのマスキング。** 説明文では `first6...last4` の形で表示します。完全な入金
260
+ アドレス、メモ、BOLT11 文字列は機械可読な `deposit` オブジェクトの中にのみ現れるので、
261
+ コピー&ペーストできる形を保ったまま、ログのあちこちに散らばることがありません。
262
+
263
+ ## ライセンス
264
+
265
+ MIT.
package/README.md ADDED
@@ -0,0 +1,270 @@
1
+ # rozo-checkout
2
+
3
+ **English** | [简体中文](README.zh.md) | [日本語](README.ja.md) | [Español](README.es.md)
4
+
5
+ **[Quick start →](QUICKSTART.md)** — pay a link in five minutes, without reading any of this.
6
+
7
+ ```bash
8
+ npx @rozoai/checkout pay <coinbase-link> --with usdt-solana
9
+ ```
10
+
11
+ Pay an **OpenRouter Coinbase Payment Link** with a coin that link cannot take
12
+ directly — BTC over Lightning, or USDT/USDC on Solana, BNB Chain, Ethereum,
13
+ Polygon, Base or Stellar.
14
+
15
+ A Coinbase Payment Link only accepts USDC on Base. This repo is an agent skill
16
+ (plus the Node scripts behind it) that routes any of the coins above through a
17
+ bridge: you get a one-time deposit address for the coin you actually hold, and
18
+ once your deposit lands a funder wallet settles the Coinbase invoice on your
19
+ behalf.
20
+
21
+ On the invoice itself there is **no discount**: `callerPays` equals the invoice
22
+ amount. The **deposit** you send is a different number, and it is normally
23
+ larger — it includes the bridge and source-chain fees needed to deliver the
24
+ invoice amount in Base USDC. Always send exactly the `deposit.amount` the
25
+ backend returns; never assume it equals the invoice.
26
+
27
+ - `SKILL.md` — the agent-facing instructions (Claude Code skill format).
28
+ - `llms.txt` — a compact summary for agents that read one file.
29
+ - `scripts/` — the Node implementation; `src/` is the source, `dist/` holds
30
+ self-contained bundles you can run with plain `node`.
31
+ - `test/` — offline unit tests for the money-handling and safety logic.
32
+ - `PLAN.md` — the design document the implementation follows.
33
+
34
+ You do **not** need an account, an API key, or any relationship with the
35
+ operator to use this. Every endpoint it calls is public and keyless.
36
+
37
+ ## How it works
38
+
39
+ ```mermaid
40
+ flowchart TD
41
+ A["Coinbase Payment Link<br/>payments.coinbase.com/payment-links/pl_*"] --> B
42
+ B["quote-invoice<br/>(public POST)"] --> C["create-invoice<br/>(public POST)"]
43
+ C --> D["Bridge order<br/>one-time deposit address<br/>+ exact amount + expiry"]
44
+ D --> E["You send USDT/USDC/BTC<br/>on your chosen chain"]
45
+ E --> F["Bridge converts the pay-in"]
46
+ F --> G["Funder wallet settles<br/>the Coinbase invoice"]
47
+ G --> H["Merchant is paid<br/>(e.g. OpenRouter credit)"]
48
+ ```
49
+
50
+ The same thing as ASCII, for terminals without mermaid:
51
+
52
+ ```
53
+ Coinbase Payment Link (pl_* / paymentSession_*)
54
+ |
55
+ v
56
+ [ quote-invoice ] -> merchant, amount, expiry, short-lived quote receipt
57
+ |
58
+ v
59
+ [ create-invoice ] -> bridge order: rozoPaymentId + deposit address
60
+ | + exact amount + order expiry
61
+ v
62
+ you send USDT / USDC / BTC on your chain ------> deposit address
63
+ |
64
+ v
65
+ bridge converts the pay-in ------> funder wallet pays the Coinbase invoice
66
+ |
67
+ v
68
+ merchant credited; poll until the state is `settled`
69
+ ```
70
+
71
+ Three identifiers appear throughout and are never interchangeable:
72
+
73
+ | Identifier | What it is |
74
+ |---|---|
75
+ | `linkId` | the Coinbase id: `pl_*` (Payment Link) or `paymentSession_*` (v3 session) |
76
+ | `rozoPaymentId` | the bridge order's UUID — use this for deposit detail and status |
77
+ | `paymentLink` | a hosted pay page URL for the bridge order (human fallback) |
78
+
79
+ ## Supported sources
80
+
81
+ | Chain | Chain id | Tokens | Notes |
82
+ |---|---|---|---|
83
+ | Ethereum | `1` | USDC, USDT | 6 decimals |
84
+ | BNB Chain | `56` | USDC, USDT | **18 decimals** — the usual source of off-by-10^12 bugs |
85
+ | Polygon | `137` | USDC, USDT | 6 decimals |
86
+ | Base | `8453` | USDC | 6 decimals |
87
+ | Solana | `900` | USDC, USDT | 6 decimals; SPL, may require a memo. Native SOL is **not** supported |
88
+ | Stellar | `1500` | USDC | 7 decimals; requires the memo |
89
+ | Bitcoin Lightning | `lightning` | BTC | amounts are integer **satoshis**, paid via a BOLT11 invoice |
90
+
91
+ Native gas coins (SOL, BNB, ETH, MATIC) and on-chain BTC are not accepted.
92
+
93
+ ## Quickstart
94
+
95
+ ### 1. Try the public endpoints with curl
96
+
97
+ Replace `pl_01YOURLINKID` with a real Coinbase Payment Link. No auth header is
98
+ needed anywhere below.
99
+
100
+ ```bash
101
+ MPP="https://apiserver.mpprouter.dev/v1/services/rozo-agent-api"
102
+ INTENTS="https://intentapiv4.rozo.ai/functions/v1/payment-api"
103
+ LINK="https://payments.coinbase.com/payment-links/pl_01YOURLINKID"
104
+
105
+ # Quote it: merchant, amount, expiry, and a ~60-second quoteReceipt.
106
+ curl -s -X POST "$MPP/quote-invoice" \
107
+ -H 'content-type: application/json' \
108
+ -d "{\"url\":\"$LINK\"}"
109
+
110
+ # Create a bridge order for, say, USDT on Solana.
111
+ # This creates an order but moves no money; an unfunded order simply expires.
112
+ curl -s -X POST "$MPP/create-invoice" \
113
+ -H 'content-type: application/json' \
114
+ -d "{\"url\":\"$LINK\",\"source\":{\"chainId\":\"900\",\"tokenSymbol\":\"USDT\"}}"
115
+
116
+ # Deposit instructions (authoritative), using the rozoPaymentId from above.
117
+ curl -s "$INTENTS/payments/<rozoPaymentId>"
118
+
119
+ # Fulfilment status, using the Coinbase linkId.
120
+ curl -s "$MPP/invoice-status?payment_id=pl_01YOURLINKID"
121
+ ```
122
+
123
+ `create-invoice` is rate-limited per IP (about 30/hour); the read endpoints are
124
+ not.
125
+
126
+ ### 2. Use the CLI
127
+
128
+ Nothing to install — `npx` fetches and runs it:
129
+
130
+ ```bash
131
+ npx @rozoai/checkout quote <coinbase-link>
132
+ npx @rozoai/checkout pay <coinbase-link> --with usdt-solana
133
+ npx @rozoai/checkout status <rozoPaymentId>
134
+ ```
135
+
136
+ `pay` walks the whole flow: quote, create the order, show a masked review, ask
137
+ for a yes, then print the deposit instructions and poll until settlement. Add
138
+ `--send` to pay from a hot wallet instead of your own, `--json` for machine
139
+ output, and `--help` for everything else.
140
+
141
+ ### 3. Or run the scripts directly
142
+
143
+ Cloning the repo gives you the same flows as individual scripts — this is what
144
+ the agent skill uses, and what the CLI calls underneath.
145
+
146
+ Each script prints exactly one JSON object on stdout. Exit `0` success, `1`
147
+ refused/failed (read `error.code`), `2` usage, `3` submitted but unconfirmed.
148
+
149
+ ```bash
150
+ # Step 1 — read-only quote, costs nothing
151
+ node scripts/dist/quote.js --url "$LINK"
152
+
153
+ # Step 2 — create the order (no money moves; unfunded orders expire).
154
+ # The full deposit address is WITHHELD at this stage: you get the
155
+ # masked summary to review, and `depositWithheld: true`.
156
+ node scripts/dist/create-order.js --url "$LINK" --chain 900 --token USDT
157
+
158
+ # Step 3 — review the amount, chain, masked address, memo requirement and
159
+ # expiry. Only when you have decided to pay, re-run the same command
160
+ # with --confirm. This releases the full deposit block and records the
161
+ # confirmation the send scripts require.
162
+ node scripts/dist/create-order.js --url "$LINK" --chain 900 --token USDT --confirm
163
+
164
+ # Step 4a — Mode A: pay the deposit block from your own wallet,
165
+ # then watch it settle
166
+ node scripts/dist/status.js --rozo-payment-id <uuid> --watch --timeout 600
167
+
168
+ # Step 4b — Mode B: let the script pay from a hot wallet. --dry-run signs
169
+ # nothing; a real send additionally requires --send.
170
+ ROZO_CHECKOUT_SOL_KEY=<base58 secret key> \
171
+ node scripts/dist/send-sol.js --rozo-payment-id <uuid> --dry-run
172
+ ```
173
+
174
+ `scripts/dist/*.js` are self-contained bundles — no `npm install` is needed at
175
+ the call site. The CLI above is the same code with a friendlier surface: it
176
+ imports these flows rather than reimplementing them, so every check below
177
+ applies identically whichever entry point you use.
178
+
179
+ ## Build and test
180
+
181
+ ```bash
182
+ npm install # only needed to rebuild or to run the tests
183
+ npm run build # esbuild -> scripts/dist/*.js (node18 target) + blacklist.json
184
+ npm test # node:test, fully offline
185
+ npm run check # build + test
186
+ ```
187
+
188
+ Tests make **no network calls**; every backend response is a fixture in
189
+ `test/fixtures/`. They cover the atomic-amount conversion (6/18 decimals and
190
+ Lightning satoshis), the expiry-margin arithmetic, compromised-address
191
+ normalization and fail-closed behaviour, the order reuse decision, the
192
+ post-create verification comparator, and the deposit-completeness rules.
193
+
194
+ Two groups spawn real child processes rather than calling functions, because
195
+ they test things a single-process test cannot: the concurrency suite races
196
+ several processes to claim one order (exactly one may win) and to exhaust the
197
+ session cap, and the entry-point suite runs the built send scripts to prove
198
+ they refuse without `--send`, without a confirmation, and after a prior claim.
199
+
200
+ ## Safety design
201
+
202
+ The interesting part of this repo is what it refuses to do.
203
+
204
+ - **Two-phase confirmation, enforced.** `create-order.js` withholds the full
205
+ deposit address, memo and BOLT11 until it is re-run with `--confirm`, which
206
+ records a confirmation bound to a sha256 of those exact instructions. The
207
+ send scripts refuse without both `--send` and a confirmation whose digest
208
+ still matches the live data, so neither an accidental invocation nor a
209
+ swapped deposit address can move funds.
210
+ - **Full invoice, always.** `callerPays` must equal the invoice amount and
211
+ `discount` must be `"0"`; anything else aborts with `NO_DISCOUNT_VIOLATION`.
212
+ Security-critical fields (`linkId`, `merchant`, `original`, `callerPays`, the
213
+ echoed source) must be present as well as equal — a missing field is drift.
214
+ - **Reuse guard.** Creating an order for a link that already has an unexpired
215
+ order returns that existing order — even if it has already been funded. So on
216
+ every run the live order is required to be unpaid (`payment_unpaid`, no tx
217
+ hash, no amount received, no confirmation) and to match the chain and token
218
+ the caller chose. Otherwise: `ORDER_ALREADY_FUNDED` or
219
+ `REUSED_SOURCE_MISMATCH`.
220
+ - **Money-detected rule, fail closed.** Once any pay-in exists, the tooling
221
+ never reports a plain failure, never advises paying again, and never retries
222
+ into a new order. An `amountReceived` that is non-null but unreadable counts
223
+ as money, not as absence of money. An unreadable backend reports `unknown`,
224
+ never `awaiting_deposit`.
225
+ - **Complete deposit instructions.** A zero, negative or unparsable amount
226
+ aborts. Lightning requires the BOLT11 (which arrives in `source.lnInvoice`
227
+ with an empty address). Stellar requires its memo — a missing memo is a hard
228
+ abort, never rendered as "no memo required".
229
+ - **Expiry margins.** Payment is refused unless the earlier of the order expiry
230
+ and the Coinbase expiry is more than a per-chain margin away (10 min EVM and
231
+ Stellar, 5 min Solana). Lightning additionally requires at least 10 minutes
232
+ of BOLT11 validity. A missing or unparsable deadline aborts.
233
+ - **Payability revalidation.** A quote receipt makes order creation skip the
234
+ live Coinbase check, and the link can be consumed by someone else at any
235
+ moment — so payability is re-checked immediately before the deposit address
236
+ is shown, and again as the very last step before broadcast, after all the RPC
237
+ preparation. Incomplete Coinbase state is treated as "cannot prove payable",
238
+ not as payable.
239
+ - **Compromised-address list, fail closed.** `scripts/src/lib/blacklist.json`
240
+ carries a vendored list with a provenance header and a sha256 over the
241
+ addresses. The digest proves only that this vendored copy has not been edited
242
+ since its sync date; it is not a signature by the upstream source. Both the
243
+ deposit address and the sending wallet are checked. If the file is missing,
244
+ malformed, empty, or its digest does not match, every send is refused rather
245
+ than proceeding unchecked.
246
+ - **Send-once, across processes.** Order state lives in
247
+ `$HOME/.rozo-checkout/state/<uuid>.json`, written atomically (temp file,
248
+ fsync, rename). Every read-modify-write of a state file — the claim, the
249
+ spend caps, the order record and the confirmation — runs inside one exclusive
250
+ lockfile, so two concurrent invocations can neither both decide they are
251
+ first, nor overwrite each other's send record. A send is claimed *before* broadcasting, so an ambiguous RPC error can
252
+ never become a second transfer. Transactions are signed before broadcast so
253
+ the hash is known in advance; on an ambiguous result the scripts look that
254
+ exact transaction up instead of rebroadcasting.
255
+ - **Hot-wallet controls.** Keys come from the environment only
256
+ (`ROZO_CHECKOUT_EVM_KEY`, `ROZO_CHECKOUT_SOL_KEY`), are never printed and
257
+ never accepted on the command line; library and RPC errors are redacted
258
+ before display, including credential-bearing provider URLs, bearer tokens and
259
+ key-shaped strings. The scripts refuse to run when any `.env`/`.env.*` in the
260
+ working directory is git-tracked, and refuse just as hard when git cannot
261
+ prove it is untracked. The RPC's chain id (or Solana genesis hash) and the token's
262
+ on-chain decimals are verified before signing. Caps: $100 per transaction,
263
+ $200 cumulative per session.
264
+ - **Address masking.** Prose shows `first6...last4`. The full deposit address,
265
+ memo and BOLT11 string appear only inside the machine-readable `deposit`
266
+ object, so they stay copy-pastable without being scattered through logs.
267
+
268
+ ## License
269
+
270
+ MIT.