@rozoai/checkout 0.1.2 → 0.1.3

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.md CHANGED
@@ -32,7 +32,7 @@ npx @rozoai/checkout pay <coinbase-link> --with usdt-solana
32
32
  | Polygon | `usdt-polygon` `usdc-polygon` | `137` | 6 decimals |
33
33
  | Base | `usdc-base` | `8453` | 6 decimals |
34
34
  | Solana | `usdt-solana` `usdc-solana` | `900` | SPL; native SOL not supported |
35
- | Stellar | `usdc-stellar` | `1500` | memo required — shown in the deposit block |
35
+ | Stellar | `usdc-stellar` | `1500` | `MEMO_TEXT` memo required — shown in the deposit block |
36
36
  | Bitcoin Lightning | `btc-lightning` | `lightning` | BOLT11; amounts in satoshis |
37
37
 
38
38
  Native gas coins (SOL, BNB, ETH, MATIC) and on-chain BTC are not accepted.
@@ -169,7 +169,8 @@ npx @rozoai/checkout pay <coinbase-link> --with usdt-solana
169
169
  ```
170
170
 
171
171
  Wallet: any wallet, no key. `--send` signs locally using your Solana CLI
172
- keypair or an encrypted keystore.
172
+ keypair or an encrypted keystore, and supports EVM chains and Solana only —
173
+ Stellar and Lightning are Mode A only.
173
174
  </details>
174
175
 
175
176
  <details>
@@ -183,7 +184,8 @@ npx @rozoai/checkout pay <coinbase-link> --with usdt-solana
183
184
  ```
184
185
 
185
186
  Wallet: any wallet, no key. `--send` signs locally using your Solana CLI
186
- keypair or an encrypted keystore.
187
+ keypair or an encrypted keystore, and supports EVM chains and Solana only —
188
+ Stellar and Lightning are Mode A only.
187
189
  </details>
188
190
 
189
191
  <details>
@@ -197,7 +199,8 @@ npx @rozoai/checkout pay <coinbase-link> --with usdt-solana
197
199
  ```
198
200
 
199
201
  Wallet: any wallet, no key. `--send` signs locally using your Solana CLI
200
- keypair or an encrypted keystore.
202
+ keypair or an encrypted keystore, and supports EVM chains and Solana only —
203
+ Stellar and Lightning are Mode A only.
201
204
  </details>
202
205
 
203
206
  <details>
@@ -211,7 +214,8 @@ npx @rozoai/checkout pay <coinbase-link> --with usdt-solana
211
214
  ```
212
215
 
213
216
  Wallet: any wallet, no key. `--send` signs locally using your Solana CLI
214
- keypair or an encrypted keystore.
217
+ keypair or an encrypted keystore, and supports EVM chains and Solana only —
218
+ Stellar and Lightning are Mode A only.
215
219
  </details>
216
220
 
217
221
  <details>
@@ -226,7 +230,8 @@ npx @rozoai/checkout pay <coinbase-link> --with usdt-solana
226
230
  ```
227
231
 
228
232
  Wallet: any wallet, no key. `--send` signs locally using your Solana CLI
229
- keypair or an encrypted keystore.
233
+ keypair or an encrypted keystore, and supports EVM chains and Solana only —
234
+ Stellar and Lightning are Mode A only.
230
235
  </details>
231
236
 
232
237
  <details>
@@ -240,7 +245,8 @@ openclaw agent exec "Pay this OpenRouter link with USDT on Solana by running: np
240
245
  ```
241
246
 
242
247
  Wallet: any wallet, no key. `--send` signs locally using your Solana CLI
243
- keypair or an encrypted keystore.
248
+ keypair or an encrypted keystore, and supports EVM chains and Solana only —
249
+ Stellar and Lightning are Mode A only.
244
250
  </details>
245
251
 
246
252
  <details>
@@ -255,7 +261,8 @@ npx @rozoai/checkout pay <coinbase-link> --with usdt-solana
255
261
  ```
256
262
 
257
263
  Wallet: any wallet, no key. `--send` signs locally using your Solana CLI
258
- keypair or an encrypted keystore.
264
+ keypair or an encrypted keystore, and supports EVM chains and Solana only —
265
+ Stellar and Lightning are Mode A only.
259
266
  </details>
260
267
 
261
268
  <details>
@@ -304,7 +311,8 @@ If the agent has no shell but can make HTTP requests, it can drive the four
304
311
  public endpoints directly — see [how it works](docs/how-it-works.md).
305
312
 
306
313
  Wallet: any wallet, no key. `--send` signs locally using your Solana CLI
307
- keypair or an encrypted keystore.
314
+ keypair or an encrypted keystore, and supports EVM chains and Solana only —
315
+ Stellar and Lightning are Mode A only.
308
316
  </details>
309
317
 
310
318
  ## Three rules worth knowing
@@ -313,6 +321,10 @@ keypair or an encrypted keystore.
313
321
  cached response or a screenshot.
314
322
  - **Send the exact amount shown.** It is normally larger than the invoice — it
315
323
  includes the bridge and network fees.
324
+ - **Stellar deposits carry a memo, and it is `MEMO_TEXT`** — even when it looks
325
+ like a number (`65371582` is text, not an id). Sending it as `MEMO_ID`
326
+ produces a different memo and the payment will not be matched. The deposit
327
+ block states the type as `receiverMemoType`.
316
328
  - **Never pay a funded order twice.** If a payment has already been detected,
317
329
  stop and get a human to reconcile it; a second payment to a one-time address
318
330
  is not guaranteed to be credited.
@@ -331,6 +343,15 @@ The full list of what this refuses to do, and why, is in
331
343
 
332
344
  ## Changelog
333
345
 
346
+ - **0.1.3** — fixes found by the first real payment. The built bundles no
347
+ longer crash with `__filename is not defined` on Node 22+ (the esbuild banner
348
+ now shims `__filename`/`__dirname` as well as `require`, and every bundle is
349
+ now executed by the test suite). Stellar deposits state their memo type
350
+ (`MEMO_TEXT`, even when the memo looks numeric). Orders show remaining
351
+ validity as a duration ("expires in 47m") in the deposit block and in
352
+ `status`, with the exact command to make a fresh one. Reusing an existing
353
+ unpaid order, and asking for a coin that differs from it, are both explained
354
+ instead of reading as errors.
334
355
  - **0.1.2** — Mode B no longer needs a raw private key in an environment
335
356
  variable. On Solana it uses the `~/.config/solana/id.json` that
336
357
  `solana-keygen` already wrote; on EVM an encrypted V3 keystore whose
@@ -159,6 +159,7 @@ exigen los scripts de envío.
159
159
  ```
160
160
 
161
161
  Enviar exactamente los campos que entrega este bloque. Para Lightning,
162
+ En Stellar el memo es **`MEMO_TEXT`** aunque parezca numérico: enviarlo como `MEMO_ID` no coincidirá. `deposit.expiresIn` indica cuánto sigue siendo válida la orden.
162
163
  `deposit.lnInvoice` contiene la cadena BOLT11 que hay que escanear, y
163
164
  `deposit.amount` está en satoshis.
164
165
 
@@ -182,8 +183,9 @@ Eso es todo el Modo A. Continuar en el paso 5.
182
183
 
183
184
  ### La forma opcional: dejar que el script pague (Modo B)
184
185
 
185
- Solo si se quiere que esta máquina firme, y únicamente en cadenas EVM y Solana.
186
- Esta es la única parte que necesita una clave.
186
+ Solo si se quiere que esta máquina firme, y únicamente en **cadenas EVM y
187
+ Solana**: no hay `--send` para Stellar ni Lightning, que se pagan desde la
188
+ propia billetera. Esta es la única parte que necesita una clave.
187
189
 
188
190
  **Solana: usar el par de claves que ya se tiene.** Si alguna vez se ejecutó
189
191
  `solana-keygen`, `~/.config/solana/id.json` ya existe y se usa automáticamente:
@@ -141,6 +141,7 @@ node scripts/dist/create-order.js --url "$LINK" --chain 900 --token USDT --confi
141
141
  ```
142
142
 
143
143
  このブロックが示すフィールドを、そのとおりに送金してください。Lightning の場合、
144
+ Stellar の memo は数字に見えても**常に `MEMO_TEXT`** です — `MEMO_ID` として送ると一致しません。`deposit.expiresIn` に残りの有効時間が出ます。
144
145
  `deposit.lnInvoice` にスキャン用の BOLT11 文字列が入り、`deposit.amount` は
145
146
  satoshi 単位になります。
146
147
 
@@ -163,8 +164,8 @@ satoshi 単位になります。
163
164
 
164
165
  ### 任意の方法: スクリプトに支払わせる(モード B)
165
166
 
166
- このマシンに署名させたい場合のみで、対象は EVM チェーンと Solana だけです。鍵が
167
- 必要なのはこの部分だけです。
167
+ このマシンに署名させたい場合のみで、対象は **EVM チェーンと Solana だけ**です。Stellar と
168
+ Lightning に `--send` はなく、自分のウォレットから支払います。鍵が必要なのはこの部分だけです。
168
169
 
169
170
  **Solana — すでにある鍵ペアをそのまま使います。** `solana-keygen` を一度でも実行して
170
171
  いれば `~/.config/solana/id.json` が既に存在し、自動的に使われます:
@@ -151,7 +151,10 @@ scripts require.
151
151
  }
152
152
  ```
153
153
 
154
- Send exactly the fields this block gives you. For Lightning,
154
+ Send exactly the fields this block gives you. On Stellar the memo is
155
+ **`MEMO_TEXT`** even when it looks numeric — sending it as `MEMO_ID` will not
156
+ match. `deposit.expiresIn` tells you how long the order stays valid. For
157
+ Lightning,
155
158
  `deposit.lnInvoice` holds the BOLT11 string to scan, and `deposit.amount` is
156
159
  in satoshis.
157
160
 
@@ -174,8 +177,9 @@ That is the whole of Mode A. Skip to step 5.
174
177
 
175
178
  ### The optional way: let the script pay (Mode B)
176
179
 
177
- Only if you want this machine to sign for you, and only on EVM chains and
178
- Solana. This is the only part that needs a key.
180
+ Only if you want this machine to sign for you, and only on **EVM chains and
181
+ Solana** — there is no `--send` for Stellar or Lightning, which are paid from
182
+ your own wallet. This is the only part that needs a key.
179
183
 
180
184
  **Solana — use the keypair you already have.** If you have ever run
181
185
  `solana-keygen`, `~/.config/solana/id.json` already exists and is used
@@ -129,7 +129,7 @@ node scripts/dist/create-order.js --url "$LINK" --chain 900 --token USDT --confi
129
129
  }
130
130
  ```
131
131
 
132
- 严格按这个信息块给出的字段来发送。对于 Lightning,`deposit.lnInvoice` 里是供扫码的
132
+ 严格按这个信息块给出的字段来发送。Stellar 上的 memo 即使看起来是纯数字,也**始终是 `MEMO_TEXT`** —— 按 `MEMO_ID` 发送不会被匹配上。`deposit.expiresIn` 会告诉你订单还有多久过期。对于 Lightning,`deposit.lnInvoice` 里是供扫码的
133
133
  BOLT11 字符串,`deposit.amount` 的单位是聪 (satoshis)。
134
134
 
135
135
  ## 4. 付款
@@ -150,8 +150,8 @@ BOLT11 字符串,`deposit.amount` 的单位是聪 (satoshis)。
150
150
 
151
151
  ### 可选的方式:让脚本替你付(模式 B)
152
152
 
153
- 只有当你想让这台机器替你签名时才需要,而且只支持 EVM 链和 Solana。这是整个流程里
154
- 唯一需要私钥的部分。
153
+ 只有当你想让这台机器替你签名时才需要,而且**只支持 EVM 链和 Solana** —— Stellar 和
154
+ 闪电网络没有 `--send`,只能用你自己的钱包付。这是整个流程里唯一需要私钥的部分。
155
155
 
156
156
  **Solana —— 直接用你已有的密钥对。** 只要你跑过 `solana-keygen`,
157
157
  `~/.config/solana/id.json` 就已经存在,脚本会自动使用它:
package/docs/README.es.md CHANGED
@@ -34,7 +34,7 @@ npx @rozoai/checkout pay <coinbase-link> --with usdt-solana
34
34
  | Polygon | `usdt-polygon` `usdc-polygon` | `137` | 6 decimales |
35
35
  | Base | `usdc-base` | `8453` | 6 decimales |
36
36
  | Solana | `usdt-solana` `usdc-solana` | `900` | SPL; SOL nativo no admitido |
37
- | Stellar | `usdc-stellar` | `1500` | memo obligatorio — se muestra en el bloque de depósito |
37
+ | Stellar | `usdc-stellar` | `1500` | memo `MEMO_TEXT` obligatorio — se muestra en el bloque de depósito |
38
38
  | Bitcoin Lightning | `btc-lightning` | `lightning` | BOLT11; importes en satoshis |
39
39
 
40
40
  Las monedas de gas nativas (SOL, BNB, ETH, MATIC) y el BTC on-chain no se aceptan.
@@ -103,7 +103,7 @@ Para pagar un enlace de pago de OpenRouter / Coinbase, ejecuta:
103
103
  npx @rozoai/checkout pay <coinbase-link> --with usdt-solana
104
104
  ```
105
105
 
106
- Billetera: cualquier billetera, **sin clave**. Solo `--send` la necesita: firma localmente con el par de claves de la CLI de Solana o un keystore cifrado.
106
+ Billetera: cualquier billetera, **sin clave**. Solo `--send` la necesita: firma localmente con el par de claves de la CLI de Solana o un keystore cifrado, y solo admite cadenas EVM y Solana (Stellar y Lightning son solo Modo A).
107
107
  </details>
108
108
 
109
109
  <details>
@@ -329,6 +329,14 @@ La lista completa de lo que esta herramienta se niega a hacer, y por qué, está
329
329
 
330
330
  ## Registro de cambios
331
331
 
332
+ - **0.1.3** — Correcciones surgidas del primer pago real. Los bundles ya no fallan
333
+ con `__filename is not defined` en Node 22+ (el banner de esbuild ahora define
334
+ `__filename`/`__dirname` además de `require`, y las pruebas ejecutan cada bundle
335
+ de verdad). Los depósitos de Stellar indican el tipo de memo (`MEMO_TEXT`, aunque
336
+ el memo parezca numérico). Las órdenes muestran la validez restante como duración
337
+ ("expires in 47m") en el bloque de depósito y en `status`, con el comando exacto
338
+ para crear una nueva. Reutilizar una orden impaga existente, y pedir una moneda
339
+ distinta a la suya, ahora se explican en lugar de parecer errores.
332
340
  - **0.1.2** — El Modo B ya no necesita una clave privada en bruto en una variable
333
341
  de entorno. En Solana usa el `~/.config/solana/id.json` que `solana-keygen` ya
334
342
  creó; en EVM un keystore V3 cifrado cuya frase de contraseña se solicita.
package/docs/README.ja.md CHANGED
@@ -33,7 +33,7 @@ npx @rozoai/checkout pay <coinbase-link> --with usdt-solana
33
33
  | Polygon | `usdt-polygon` `usdc-polygon` | `137` | 小数点以下 6 桁 |
34
34
  | Base | `usdc-base` | `8453` | 小数点以下 6 桁 |
35
35
  | Solana | `usdt-solana` `usdc-solana` | `900` | SPL。ネイティブの SOL は非対応 |
36
- | Stellar | `usdc-stellar` | `1500` | メモが必須 — 入金ブロックに表示されます |
36
+ | Stellar | `usdc-stellar` | `1500` | `MEMO_TEXT` のメモが必須 — 入金ブロックに表示されます |
37
37
  | Bitcoin Lightning | `btc-lightning` | `lightning` | BOLT11。金額は satoshi 単位 |
38
38
 
39
39
  ネイティブのガス通貨(SOL、BNB、ETH、MATIC)およびオンチェーンの BTC は受け付けません。
@@ -89,7 +89,7 @@ OpenRouter / Coinbase の決済リンクを支払うには、次を実行して
89
89
  npx @rozoai/checkout pay <coinbase-link> --with usdt-solana
90
90
  ```
91
91
 
92
- ウォレット: どのウォレットでも構いません。**鍵は不要です。** Solana CLI の鍵ペアまたは暗号化された keystore を使う `--send` の場合だけ
92
+ ウォレット: どのウォレットでも構いません。**鍵は不要です。** Solana CLI の鍵ペアまたは暗号化された keystore を使う `--send`(EVM チェーンと Solana のみ、Stellar と Lightning はモード A 専用)の場合だけ
93
93
  環境変数の鍵が必要です。
94
94
  </details>
95
95
 
@@ -320,6 +320,13 @@ WalletConnect 専用のモバイルウォレット、取引所アカウント。
320
320
 
321
321
  ## 変更履歴
322
322
 
323
+ - **0.1.3** — 最初の実際の支払いで見つかった不具合の修正。ビルド済みバンドルが Node 22 以降で
324
+ `__filename is not defined` により停止しなくなりました(esbuild の banner が `require` に加えて
325
+ `__filename`/`__dirname` も定義し、テストが各バンドルを実際に実行するようになりました)。Stellar の
326
+ 入金では memo の種類(`MEMO_TEXT`。memo が数字に見える場合も同様)を明示します。注文の残り有効時間を
327
+ 入金ブロックと `status` の両方で継続時間として表示し("expires in 47m")、期限切れ時には新しい注文を
328
+ 作るコマンドをそのまま示します。既存の未払い注文の再利用と、それと異なる通貨を指定した場合の双方を、
329
+ エラーではなく説明として提示します。
323
330
  - **0.1.2** — モード B で環境変数に生の秘密鍵を置く必要がなくなりました。Solana では
324
331
  `solana-keygen` が既に作成した `~/.config/solana/id.json` を、EVM では暗号化された
325
332
  V3 keystore(パスフレーズは対話的に入力)を使います。`--keyfile` でどちらも明示でき、
package/docs/README.zh.md CHANGED
@@ -30,7 +30,7 @@ npx @rozoai/checkout pay <coinbase-link> --with usdt-solana
30
30
  | Polygon | `usdt-polygon` `usdc-polygon` | `137` | 6 位小数 |
31
31
  | Base | `usdc-base` | `8453` | 6 位小数 |
32
32
  | Solana | `usdt-solana` `usdc-solana` | `900` | SPL;不支持原生 SOL |
33
- | Stellar | `usdc-stellar` | `1500` | 必须带 memo —— 会在充值信息块里给出 |
33
+ | Stellar | `usdc-stellar` | `1500` | 必须带 `MEMO_TEXT` memo —— 会在充值信息块里给出 |
34
34
  | Bitcoin Lightning | `btc-lightning` | `lightning` | BOLT11;金额单位为聪 |
35
35
 
36
36
  原生 gas 币(SOL、BNB、ETH、MATIC)以及链上 BTC 均不接受。
@@ -82,7 +82,7 @@ Codex 会读取项目根目录的 `AGENTS.md`。加一条常驻指令,它就
82
82
  npx @rozoai/checkout pay <coinbase-link> --with usdt-solana
83
83
  ```
84
84
 
85
- 钱包:任意钱包,**无需私钥**。只有 `--send` 需要,它用你的 Solana CLI 密钥对或加密 keystore 在本地签名。
85
+ 钱包:任意钱包,**无需私钥**。只有 `--send` 需要,它用你的 Solana CLI 密钥对或加密 keystore 在本地签名;仅支持 EVM 链和 Solana —— Stellar 与闪电网络只能用模式 A。
86
86
  </details>
87
87
 
88
88
  <details>
@@ -292,6 +292,12 @@ echo '.env' >> .gitignore
292
292
 
293
293
  ## 更新日志
294
294
 
295
+ - **0.1.3** —— 第一笔真实付款暴露出来的问题修复。构建产物在 Node 22+ 上不再因
296
+ `__filename is not defined` 崩溃(esbuild banner 现在同时注入 `__filename`/`__dirname`
297
+ 和 `require`,并且测试会真正执行每一个构建产物)。Stellar 充值现在会明确标出 memo
298
+ 类型(`MEMO_TEXT`,即使 memo 看起来是纯数字)。订单在充值信息块和 `status` 里都会以
299
+ 时长显示剩余有效期("expires in 47m"),过期时给出重新下单的完整命令。复用已有的未付
300
+ 订单、以及所选币种与已有订单不一致这两种情况,现在都会被解释清楚,而不是看起来像报错。
295
301
  - **0.1.2** —— 模式 B 不再需要把原始私钥放进环境变量。Solana 直接用 `solana-keygen`
296
302
  已经写好的 `~/.config/solana/id.json`;EVM 用加密的 V3 keystore,口令交互式提示输入。
297
303
  `--keyfile` 可以显式指定其中任一种,相关设置也可以放进已 gitignore 的 `.env`。
package/llms.txt CHANGED
@@ -54,6 +54,8 @@ Where the material comes from:
54
54
  `cast wallet import <name> --interactive` writes a V3 keystore to
55
55
  ~/.foundry/keystores/<name>; `geth account import` also writes one. Browser
56
56
  wallets do not export keystores, only raw keys.
57
+ - --send supports EVM chains and Solana ONLY. Stellar and Lightning are paid
58
+ from the user's own wallet (Mode A); there is no local signing for them.
57
59
  - Hardware wallets, WalletConnect-only wallets and exchange accounts CANNOT be
58
60
  used with --send. Those users take the default keyless path, which works with
59
61
  any wallet.
@@ -108,7 +110,11 @@ private key (EVM and Solana only); every other path is keyless.
108
110
  Pay EXACTLY source.amount of the requested token on the requested chain to
109
111
  source.receiverAddress, including every other field the block carries (Stellar
110
112
  routes on a shared hub address plus source.receiverMemo, so omitting the memo
111
- loses the payment).
113
+ loses the payment). The Stellar memo is ALWAYS MEMO_TEXT, even when it is all
114
+ digits (e.g. 65371582) — sending it as MEMO_ID produces a different memo and
115
+ the payment will not match. The deposit block reports this as
116
+ receiverMemoType. Orders expire: deposit.expiresIn / status expiry.expiresIn
117
+ give the remaining validity as a duration.
112
118
  For Lightning, pay the BOLT11 in source.lnInvoice (receiverAddress is empty).
113
119
 
114
120
  4) Poll until settled:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rozoai/checkout",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "description": "Pay an OpenRouter Coinbase Payment Link with BTC Lightning, or USDT/USDC on Solana, BNB Chain, Ethereum, Polygon, Base or Stellar.",
6
6
  "license": "MIT",
@@ -1,6 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as __rozoCreateRequire } from 'node:module';
3
+ import { fileURLToPath as __rozoFileURLToPath } from 'node:url';
4
+ import { dirname as __rozoDirname } from 'node:path';
3
5
  const require = __rozoCreateRequire(import.meta.url);
6
+ const __filename = __rozoFileURLToPath(import.meta.url);
7
+ const __dirname = __rozoDirname(__filename);
4
8
  var __create = Object.create;
5
9
  var __defProp = Object.defineProperty;
6
10
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -41655,6 +41659,7 @@ function formatAmount(source) {
41655
41659
  function chainName(chainId) {
41656
41660
  return CHAIN_NAMES[String(chainId)] || CHAIN_NAMES[chainId] || `chain ${chainId}`;
41657
41661
  }
41662
+ var STELLAR_MEMO_TYPE = "MEMO_TEXT";
41658
41663
  function chainFamily(chainId) {
41659
41664
  return CHAIN_FAMILY[String(chainId)] || CHAIN_FAMILY[chainId] || null;
41660
41665
  }
@@ -42701,6 +42706,16 @@ var MARGINS_MS = {
42701
42706
  };
42702
42707
  var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
42703
42708
  var DEFAULT_MARGIN_MS = 10 * MINUTE;
42709
+ function formatRemaining(ms) {
42710
+ if (!Number.isFinite(ms)) return "unknown";
42711
+ if (ms <= 0) return "expired";
42712
+ const totalMinutes = Math.floor(ms / 6e4);
42713
+ if (totalMinutes < 1) return `${Math.floor(ms / 1e3)}s`;
42714
+ if (totalMinutes < 60) return `${totalMinutes}m`;
42715
+ const h = Math.floor(totalMinutes / 60);
42716
+ const m = totalMinutes % 60;
42717
+ return m ? `${h}h ${m}m` : `${h}h`;
42718
+ }
42704
42719
  function marginFor(chainId) {
42705
42720
  const key = String(chainId);
42706
42721
  return MARGINS_MS[key] ?? MARGINS_MS[chainId] ?? DEFAULT_MARGIN_MS;
@@ -43342,6 +43357,33 @@ async function main2(argv) {
43342
43357
  const rozoPaymentId = assertRozoPaymentId(created.rozoPaymentId);
43343
43358
  const verify = verifyCreateAgainstQuote({ snapshot: snapshot2, created, requested });
43344
43359
  if (!verify.ok) {
43360
+ const onlySourceDrift = verify.drift.length > 0 && verify.drift.every((d) => d.field.startsWith("source."));
43361
+ if (created.reused && onlySourceDrift) {
43362
+ const existing = created.source || {};
43363
+ const remainingMs = created.expiresAt ? Date.parse(created.expiresAt) - Date.now() : NaN;
43364
+ emit(
43365
+ {
43366
+ success: false,
43367
+ step: "reuse-source-mismatch",
43368
+ error: {
43369
+ code: "REUSED_SOURCE_MISMATCH",
43370
+ message: `This link already has an unpaid order, created for ${existing.tokenSymbol ?? "?"} on chain ${existing.chainId ?? "?"}. You asked for ${tokenSymbol} on chain ${chainId}. Only one order exists per link at a time, so nothing new was created.`
43371
+ },
43372
+ linkId: created.linkId ?? parsedLinkId,
43373
+ rozoPaymentId,
43374
+ paymentLink: created.paymentLink ?? null,
43375
+ existingOrder: {
43376
+ rozoPaymentId,
43377
+ chainId: existing.chainId ?? null,
43378
+ tokenSymbol: existing.tokenSymbol ?? null,
43379
+ expiresAt: created.expiresAt ?? null,
43380
+ expiresIn: Number.isFinite(remainingMs) ? formatRemaining(remainingMs) : null
43381
+ },
43382
+ guidance: `Either pay the existing order with ${existing.tokenSymbol ?? "its own coin"} (re-run with --chain ${existing.chainId} --token ${existing.tokenSymbol}), or wait ${Number.isFinite(remainingMs) ? formatRemaining(remainingMs) : "for it to expire"} for it to expire and then create a new one. Unpaid orders cost nothing.`
43383
+ },
43384
+ EXIT_ERROR
43385
+ );
43386
+ }
43345
43387
  emit(
43346
43388
  {
43347
43389
  success: false,
@@ -43463,7 +43505,8 @@ async function main2(argv) {
43463
43505
  step: "create-order",
43464
43506
  confirmed,
43465
43507
  reused: Boolean(created.reused),
43466
- reusedNote: created.reused ? "An existing unfunded order for this link was reused; it passed the funded-check above." : null,
43508
+ reusedNote: created.reused ? `An existing unpaid order for this link was reused (${rozoPaymentId}), valid for another ${formatRemaining(expiry.msRemaining)}. Nothing new was created.` : null,
43509
+ orderCost: "Creating an order moves no money. An order you never fund simply expires and costs nothing.",
43467
43510
  linkId: created.linkId ?? parsedLinkId,
43468
43511
  rozoPaymentId,
43469
43512
  paymentLink: created.paymentLink ?? null,
@@ -43481,12 +43524,16 @@ async function main2(argv) {
43481
43524
  // Lightning has no deposit address: the BOLT11 IS the instruction.
43482
43525
  receiverAddress: lightning ? null : source.receiverAddress,
43483
43526
  receiverMemo: source.receiverMemo ?? null,
43527
+ // Stellar memos are TEXT even when they look numeric. Sending one as
43528
+ // MEMO_ID produces a different memo and the payment will not match.
43529
+ receiverMemoType: source.receiverMemo ? STELLAR_MEMO_TYPE : null,
43484
43530
  amount: source.amount,
43485
43531
  amountUnit: source.amountUnit ?? null,
43486
43532
  isSats: isSatsUnit(source.amountUnit),
43487
43533
  lnInvoice: bolt11 || null,
43488
43534
  payTo: depositInfo.payTo,
43489
- expiresAt: payment?.expiresAt ?? null
43535
+ expiresAt: payment?.expiresAt ?? null,
43536
+ expiresIn: formatRemaining(expiry.msRemaining)
43490
43537
  } : null,
43491
43538
  depositWithheld: !confirmed,
43492
43539
  // Safe for prose / chat.
@@ -43498,12 +43545,16 @@ async function main2(argv) {
43498
43545
  payToMasked: maskAddress2(depositInfo.payTo),
43499
43546
  receiverMemoMasked: maskMemo(source.receiverMemo),
43500
43547
  hasMemo: Boolean(source.receiverMemo),
43548
+ memoType: source.receiverMemo ? STELLAR_MEMO_TYPE : null,
43501
43549
  memoRequirement
43502
43550
  },
43503
43551
  expiry: {
43504
43552
  intentExpiresAt: payment?.expiresAt ?? null,
43505
43553
  coinbaseExpiry: snapshot2?.coinbase?.preApprovalExpiry ?? null,
43506
43554
  effectiveDeadlineIso: new Date(expiry.effectiveDeadlineMs).toISOString(),
43555
+ // A duration, not just a timestamp: this is what a payer actually needs.
43556
+ expiresIn: formatRemaining(expiry.msRemaining),
43557
+ msRemaining: expiry.msRemaining,
43507
43558
  marginMinutes: Math.round(expiry.marginMs / 6e4),
43508
43559
  minutesOfSlack: Math.floor(expiry.msOfSlack / 6e4)
43509
43560
  },
@@ -43599,6 +43650,16 @@ async function snapshot({ rozoPaymentId, linkId }) {
43599
43650
  senderAddressMasked: source.senderAddress ? maskAddress2(source.senderAddress) : null,
43600
43651
  chain: source.chainId ? chainName(source.chainId) : null
43601
43652
  },
43653
+ expiry: (() => {
43654
+ const iso = payment?.expiresAt ?? status?.rozoPayment?.expiresAt ?? null;
43655
+ if (!iso) return { expiresAt: null, expiresIn: null, msRemaining: null };
43656
+ const ms = Date.parse(iso) - Date.now();
43657
+ return {
43658
+ expiresAt: iso,
43659
+ expiresIn: formatRemaining(ms),
43660
+ msRemaining: Number.isFinite(ms) ? ms : null
43661
+ };
43662
+ })(),
43602
43663
  payout: {
43603
43664
  txHash: payment?.destination?.txHash ?? status?.rozoPayment?.destination?.txHash ?? null,
43604
43665
  confirmedAt: payment?.destination?.confirmedAt ?? status?.rozoPayment?.destination?.confirmedAt ?? null
@@ -43624,7 +43685,7 @@ async function main3(argv) {
43624
43685
  if (next.state !== result.state) history.push({ at: (/* @__PURE__ */ new Date()).toISOString(), state: next.state });
43625
43686
  result = next;
43626
43687
  }
43627
- const guidance = result.escalate ? "MONEY DETECTED and the order is not on a healthy path. Do NOT pay again and do NOT create a new order for this link. Preserve linkId, rozoPaymentId and every tx hash, then escalate to the operator for manual reconciliation." : result.unknown ? "The order state could not be established. This is NOT evidence that nothing was paid \u2014 do not create a new order and do not send again on the strength of it. Retry, or pass --rozo-payment-id so the authoritative pay-in view can be read." : !result.authoritativeView ? "Only the fulfilment view was readable; the pay-in view is unavailable, so the money-detected rule cannot be enforced. Pass --rozo-payment-id for a complete answer." : result.state === "expired_unfunded" ? "Nothing was funded. It is safe to start over with a fresh link." : result.terminal ? "Done." : "Still in flight. Poll again in ~10s.";
43688
+ const guidance = result.escalate ? "MONEY DETECTED and the order is not on a healthy path. Do NOT pay again and do NOT create a new order for this link. Preserve linkId, rozoPaymentId and every tx hash, then escalate to the operator for manual reconciliation." : result.unknown ? "The order state could not be established. This is NOT evidence that nothing was paid \u2014 do not create a new order and do not send again on the strength of it. Retry, or pass --rozo-payment-id so the authoritative pay-in view can be read." : !result.authoritativeView ? "Only the fulfilment view was readable; the pay-in view is unavailable, so the money-detected rule cannot be enforced. Pass --rozo-payment-id for a complete answer." : result.state === "expired_unfunded" ? "Nothing was funded, so nothing was lost. Start a fresh order with: rozo-checkout pay <coinbase-link> --with <coin> (or create-order.js --url <link> --chain <id> --token <SYMBOL>)" : result.terminal ? "Done." : "Still in flight. Poll again in ~10s.";
43628
43689
  const unresolved = watch && !result.terminal && !result.escalate && !result.unknown;
43629
43690
  const failed = result.escalate || result.unknown || !result.authoritativeView;
43630
43691
  emit(
@@ -57910,6 +57971,9 @@ async function cmdStatus(opts) {
57910
57971
  out();
57911
57972
  out(` State ${stateLabel(payload.state)}`);
57912
57973
  if (payload.detail) out(` ${dim(payload.detail)}`);
57974
+ if (payload.expiry?.expiresIn && !payload.terminal) {
57975
+ out(` Expires in ${payload.expiry.expiresIn}`);
57976
+ }
57913
57977
  if (payload.payin?.txHash) out(` Pay-in ${payload.payin.txHash}`);
57914
57978
  if (payload.guidance) out(` ${payload.escalate ? red(payload.guidance) : dim(payload.guidance)}`);
57915
57979
  printMoneyWarning(payload);
@@ -57996,9 +58060,14 @@ async function cmdPay(opts) {
57996
58060
  out(` Invoice ${bold(`${p.invoice?.amount} ${p.invoice?.currency ?? "USD"}`)}`);
57997
58061
  out(` You send ${bold(p.display?.amount)} on ${bold(p.display?.chain)}`);
57998
58062
  out(` To ${p.display?.payToMasked} ${dim("(full address shown after you confirm)")}`);
57999
- if (p.display?.hasMemo) out(` Memo ${p.display.receiverMemoMasked}`);
58000
- out(` Expires ${p.expiry?.effectiveDeadlineIso} ${dim(`(${p.expiry?.minutesOfSlack} min of slack)`)}`);
58001
- if (p.reused) out(` ${dim("Reused an existing unfunded order for this link.")}`);
58063
+ if (p.display?.hasMemo) {
58064
+ out(` Memo ${p.display.receiverMemoMasked} ${dim(`(${p.display.memoType})`)}`);
58065
+ }
58066
+ out(` Expires in ${bold(p.expiry?.expiresIn ?? "?")} ${dim(`(${p.expiry?.effectiveDeadlineIso})`)}`);
58067
+ if (p.reused) {
58068
+ out(` ${dim(`Reusing the existing unpaid order ${p.rozoPaymentId} \u2014 nothing new was created.`)}`);
58069
+ }
58070
+ out(` ${dim("An order you never fund simply expires and costs nothing.")}`);
58002
58071
  out();
58003
58072
  out(dim(` The amount you send includes bridge and network fees, so it is`));
58004
58073
  out(dim(` normally larger than the invoice.`));
@@ -58127,7 +58196,12 @@ function printDeposit(deposit, opts) {
58127
58196
  out(` Amount ${bold(`${deposit.amount} ${deposit.tokenSymbol}`)}`);
58128
58197
  out(` Chain ${bold(deposit.chain)}`);
58129
58198
  out(` Address ${bold(deposit.receiverAddress)}`);
58130
- if (deposit.receiverMemo) out(` Memo ${bold(deposit.receiverMemo)} ${red("(required)")}`);
58199
+ if (deposit.receiverMemo) {
58200
+ out(
58201
+ ` Memo ${bold(deposit.receiverMemo)} ${red(`(required, ${deposit.receiverMemoType})`)}`
58202
+ );
58203
+ }
58204
+ if (deposit.expiresIn) out(` Expires in ${bold(deposit.expiresIn)}`);
58131
58205
  }
58132
58206
  out();
58133
58207
  const unit = isSatsUnit(deposit.amountUnit) ? "sats" : deposit.tokenSymbol;
@@ -1,6 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as __rozoCreateRequire } from 'node:module';
3
+ import { fileURLToPath as __rozoFileURLToPath } from 'node:url';
4
+ import { dirname as __rozoDirname } from 'node:path';
3
5
  const require = __rozoCreateRequire(import.meta.url);
6
+ const __filename = __rozoFileURLToPath(import.meta.url);
7
+ const __dirname = __rozoDirname(__filename);
4
8
 
5
9
  // scripts/src/lib/output.mjs
6
10
  var EXIT_OK = 0;
@@ -396,6 +400,7 @@ function formatAmount(source) {
396
400
  function chainName(chainId) {
397
401
  return CHAIN_NAMES[String(chainId)] || CHAIN_NAMES[chainId] || `chain ${chainId}`;
398
402
  }
403
+ var STELLAR_MEMO_TYPE = "MEMO_TEXT";
399
404
  function chainFamily(chainId) {
400
405
  return CHAIN_FAMILY[String(chainId)] || CHAIN_FAMILY[chainId] || null;
401
406
  }
@@ -418,6 +423,16 @@ var MARGINS_MS = {
418
423
  };
419
424
  var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
420
425
  var DEFAULT_MARGIN_MS = 10 * MINUTE;
426
+ function formatRemaining(ms) {
427
+ if (!Number.isFinite(ms)) return "unknown";
428
+ if (ms <= 0) return "expired";
429
+ const totalMinutes = Math.floor(ms / 6e4);
430
+ if (totalMinutes < 1) return `${Math.floor(ms / 1e3)}s`;
431
+ if (totalMinutes < 60) return `${totalMinutes}m`;
432
+ const h = Math.floor(totalMinutes / 60);
433
+ const m = totalMinutes % 60;
434
+ return m ? `${h}h ${m}m` : `${h}h`;
435
+ }
421
436
  function marginFor(chainId) {
422
437
  const key = String(chainId);
423
438
  return MARGINS_MS[key] ?? MARGINS_MS[chainId] ?? DEFAULT_MARGIN_MS;
@@ -1135,6 +1150,33 @@ async function main(argv) {
1135
1150
  const rozoPaymentId = assertRozoPaymentId(created.rozoPaymentId);
1136
1151
  const verify = verifyCreateAgainstQuote({ snapshot, created, requested });
1137
1152
  if (!verify.ok) {
1153
+ const onlySourceDrift = verify.drift.length > 0 && verify.drift.every((d) => d.field.startsWith("source."));
1154
+ if (created.reused && onlySourceDrift) {
1155
+ const existing = created.source || {};
1156
+ const remainingMs = created.expiresAt ? Date.parse(created.expiresAt) - Date.now() : NaN;
1157
+ emit(
1158
+ {
1159
+ success: false,
1160
+ step: "reuse-source-mismatch",
1161
+ error: {
1162
+ code: "REUSED_SOURCE_MISMATCH",
1163
+ message: `This link already has an unpaid order, created for ${existing.tokenSymbol ?? "?"} on chain ${existing.chainId ?? "?"}. You asked for ${tokenSymbol} on chain ${chainId}. Only one order exists per link at a time, so nothing new was created.`
1164
+ },
1165
+ linkId: created.linkId ?? parsedLinkId,
1166
+ rozoPaymentId,
1167
+ paymentLink: created.paymentLink ?? null,
1168
+ existingOrder: {
1169
+ rozoPaymentId,
1170
+ chainId: existing.chainId ?? null,
1171
+ tokenSymbol: existing.tokenSymbol ?? null,
1172
+ expiresAt: created.expiresAt ?? null,
1173
+ expiresIn: Number.isFinite(remainingMs) ? formatRemaining(remainingMs) : null
1174
+ },
1175
+ guidance: `Either pay the existing order with ${existing.tokenSymbol ?? "its own coin"} (re-run with --chain ${existing.chainId} --token ${existing.tokenSymbol}), or wait ${Number.isFinite(remainingMs) ? formatRemaining(remainingMs) : "for it to expire"} for it to expire and then create a new one. Unpaid orders cost nothing.`
1176
+ },
1177
+ EXIT_ERROR
1178
+ );
1179
+ }
1138
1180
  emit(
1139
1181
  {
1140
1182
  success: false,
@@ -1256,7 +1298,8 @@ async function main(argv) {
1256
1298
  step: "create-order",
1257
1299
  confirmed,
1258
1300
  reused: Boolean(created.reused),
1259
- reusedNote: created.reused ? "An existing unfunded order for this link was reused; it passed the funded-check above." : null,
1301
+ reusedNote: created.reused ? `An existing unpaid order for this link was reused (${rozoPaymentId}), valid for another ${formatRemaining(expiry.msRemaining)}. Nothing new was created.` : null,
1302
+ orderCost: "Creating an order moves no money. An order you never fund simply expires and costs nothing.",
1260
1303
  linkId: created.linkId ?? parsedLinkId,
1261
1304
  rozoPaymentId,
1262
1305
  paymentLink: created.paymentLink ?? null,
@@ -1274,12 +1317,16 @@ async function main(argv) {
1274
1317
  // Lightning has no deposit address: the BOLT11 IS the instruction.
1275
1318
  receiverAddress: lightning ? null : source.receiverAddress,
1276
1319
  receiverMemo: source.receiverMemo ?? null,
1320
+ // Stellar memos are TEXT even when they look numeric. Sending one as
1321
+ // MEMO_ID produces a different memo and the payment will not match.
1322
+ receiverMemoType: source.receiverMemo ? STELLAR_MEMO_TYPE : null,
1277
1323
  amount: source.amount,
1278
1324
  amountUnit: source.amountUnit ?? null,
1279
1325
  isSats: isSatsUnit(source.amountUnit),
1280
1326
  lnInvoice: bolt11 || null,
1281
1327
  payTo: depositInfo.payTo,
1282
- expiresAt: payment?.expiresAt ?? null
1328
+ expiresAt: payment?.expiresAt ?? null,
1329
+ expiresIn: formatRemaining(expiry.msRemaining)
1283
1330
  } : null,
1284
1331
  depositWithheld: !confirmed,
1285
1332
  // Safe for prose / chat.
@@ -1291,12 +1338,16 @@ async function main(argv) {
1291
1338
  payToMasked: maskAddress(depositInfo.payTo),
1292
1339
  receiverMemoMasked: maskMemo(source.receiverMemo),
1293
1340
  hasMemo: Boolean(source.receiverMemo),
1341
+ memoType: source.receiverMemo ? STELLAR_MEMO_TYPE : null,
1294
1342
  memoRequirement
1295
1343
  },
1296
1344
  expiry: {
1297
1345
  intentExpiresAt: payment?.expiresAt ?? null,
1298
1346
  coinbaseExpiry: snapshot?.coinbase?.preApprovalExpiry ?? null,
1299
1347
  effectiveDeadlineIso: new Date(expiry.effectiveDeadlineMs).toISOString(),
1348
+ // A duration, not just a timestamp: this is what a payer actually needs.
1349
+ expiresIn: formatRemaining(expiry.msRemaining),
1350
+ msRemaining: expiry.msRemaining,
1300
1351
  marginMinutes: Math.round(expiry.marginMs / 6e4),
1301
1352
  minutesOfSlack: Math.floor(expiry.msOfSlack / 6e4)
1302
1353
  },
@@ -1,6 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as __rozoCreateRequire } from 'node:module';
3
+ import { fileURLToPath as __rozoFileURLToPath } from 'node:url';
4
+ import { dirname as __rozoDirname } from 'node:path';
3
5
  const require = __rozoCreateRequire(import.meta.url);
6
+ const __filename = __rozoFileURLToPath(import.meta.url);
7
+ const __dirname = __rozoDirname(__filename);
4
8
 
5
9
  // scripts/src/lib/output.mjs
6
10
  var EXIT_OK = 0;
@@ -1,6 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as __rozoCreateRequire } from 'node:module';
3
+ import { fileURLToPath as __rozoFileURLToPath } from 'node:url';
4
+ import { dirname as __rozoDirname } from 'node:path';
3
5
  const require = __rozoCreateRequire(import.meta.url);
6
+ const __filename = __rozoFileURLToPath(import.meta.url);
7
+ const __dirname = __rozoDirname(__filename);
4
8
  var __defProp = Object.defineProperty;
5
9
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
10
  var __esm = (fn, res) => function __init() {
@@ -1,6 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as __rozoCreateRequire } from 'node:module';
3
+ import { fileURLToPath as __rozoFileURLToPath } from 'node:url';
4
+ import { dirname as __rozoDirname } from 'node:path';
3
5
  const require = __rozoCreateRequire(import.meta.url);
6
+ const __filename = __rozoFileURLToPath(import.meta.url);
7
+ const __dirname = __rozoDirname(__filename);
4
8
  var __create = Object.create;
5
9
  var __defProp = Object.defineProperty;
6
10
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -1,6 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as __rozoCreateRequire } from 'node:module';
3
+ import { fileURLToPath as __rozoFileURLToPath } from 'node:url';
4
+ import { dirname as __rozoDirname } from 'node:path';
3
5
  const require = __rozoCreateRequire(import.meta.url);
6
+ const __filename = __rozoFileURLToPath(import.meta.url);
7
+ const __dirname = __rozoDirname(__filename);
4
8
 
5
9
  // scripts/src/lib/output.mjs
6
10
  var EXIT_OK = 0;
@@ -428,6 +432,30 @@ function classifyStatus({ payment, routerState, coinbase, now = Date.now(), view
428
432
  );
429
433
  }
430
434
 
435
+ // scripts/src/lib/expiry.mjs
436
+ var MINUTE = 6e4;
437
+ var MARGINS_MS = {
438
+ 1: 10 * MINUTE,
439
+ 56: 10 * MINUTE,
440
+ 137: 10 * MINUTE,
441
+ 8453: 10 * MINUTE,
442
+ 900: 5 * MINUTE,
443
+ 1500: 10 * MINUTE,
444
+ lightning: 10 * MINUTE
445
+ };
446
+ var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
447
+ var DEFAULT_MARGIN_MS = 10 * MINUTE;
448
+ function formatRemaining(ms) {
449
+ if (!Number.isFinite(ms)) return "unknown";
450
+ if (ms <= 0) return "expired";
451
+ const totalMinutes = Math.floor(ms / 6e4);
452
+ if (totalMinutes < 1) return `${Math.floor(ms / 1e3)}s`;
453
+ if (totalMinutes < 60) return `${totalMinutes}m`;
454
+ const h = Math.floor(totalMinutes / 60);
455
+ const m = totalMinutes % 60;
456
+ return m ? `${h}h ${m}m` : `${h}h`;
457
+ }
458
+
431
459
  // scripts/src/lib/state.mjs
432
460
  import fs from "node:fs";
433
461
  import path from "node:path";
@@ -519,6 +547,16 @@ async function snapshot({ rozoPaymentId, linkId }) {
519
547
  senderAddressMasked: source.senderAddress ? maskAddress(source.senderAddress) : null,
520
548
  chain: source.chainId ? chainName(source.chainId) : null
521
549
  },
550
+ expiry: (() => {
551
+ const iso = payment?.expiresAt ?? status?.rozoPayment?.expiresAt ?? null;
552
+ if (!iso) return { expiresAt: null, expiresIn: null, msRemaining: null };
553
+ const ms = Date.parse(iso) - Date.now();
554
+ return {
555
+ expiresAt: iso,
556
+ expiresIn: formatRemaining(ms),
557
+ msRemaining: Number.isFinite(ms) ? ms : null
558
+ };
559
+ })(),
522
560
  payout: {
523
561
  txHash: payment?.destination?.txHash ?? status?.rozoPayment?.destination?.txHash ?? null,
524
562
  confirmedAt: payment?.destination?.confirmedAt ?? status?.rozoPayment?.destination?.confirmedAt ?? null
@@ -544,7 +582,7 @@ async function main(argv) {
544
582
  if (next.state !== result.state) history.push({ at: (/* @__PURE__ */ new Date()).toISOString(), state: next.state });
545
583
  result = next;
546
584
  }
547
- const guidance = result.escalate ? "MONEY DETECTED and the order is not on a healthy path. Do NOT pay again and do NOT create a new order for this link. Preserve linkId, rozoPaymentId and every tx hash, then escalate to the operator for manual reconciliation." : result.unknown ? "The order state could not be established. This is NOT evidence that nothing was paid \u2014 do not create a new order and do not send again on the strength of it. Retry, or pass --rozo-payment-id so the authoritative pay-in view can be read." : !result.authoritativeView ? "Only the fulfilment view was readable; the pay-in view is unavailable, so the money-detected rule cannot be enforced. Pass --rozo-payment-id for a complete answer." : result.state === "expired_unfunded" ? "Nothing was funded. It is safe to start over with a fresh link." : result.terminal ? "Done." : "Still in flight. Poll again in ~10s.";
585
+ const guidance = result.escalate ? "MONEY DETECTED and the order is not on a healthy path. Do NOT pay again and do NOT create a new order for this link. Preserve linkId, rozoPaymentId and every tx hash, then escalate to the operator for manual reconciliation." : result.unknown ? "The order state could not be established. This is NOT evidence that nothing was paid \u2014 do not create a new order and do not send again on the strength of it. Retry, or pass --rozo-payment-id so the authoritative pay-in view can be read." : !result.authoritativeView ? "Only the fulfilment view was readable; the pay-in view is unavailable, so the money-detected rule cannot be enforced. Pass --rozo-payment-id for a complete answer." : result.state === "expired_unfunded" ? "Nothing was funded, so nothing was lost. Start a fresh order with: rozo-checkout pay <coinbase-link> --with <coin> (or create-order.js --url <link> --chain <id> --token <SYMBOL>)" : result.terminal ? "Done." : "Still in flight. Poll again in ~10s.";
548
586
  const unresolved = watch && !result.terminal && !result.escalate && !result.unknown;
549
587
  const failed = result.escalate || result.unknown || !result.authoritativeView;
550
588
  emit(