@rozoai/checkout 0.1.0 → 0.1.1

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.
@@ -10899,7 +10899,7 @@ function depositDigest(source) {
10899
10899
  });
10900
10900
  return crypto3.createHash("sha256").update(canonical, "utf8").digest("hex");
10901
10901
  }
10902
- function claimSend(rozoPaymentId, intent, { allowLarge = false, skipCaps = false } = {}) {
10902
+ function claimSend(rozoPaymentId, intent, { skipCaps = false } = {}) {
10903
10903
  return withLock(() => {
10904
10904
  const state = readState(rozoPaymentId);
10905
10905
  if (!state) {
@@ -10915,7 +10915,7 @@ function claimSend(rozoPaymentId, intent, { allowLarge = false, skipCaps = false
10915
10915
  { send: state.send }
10916
10916
  );
10917
10917
  }
10918
- const caps = skipCaps ? null : assertSpendCapsUnlocked(state.invoiceAmount, { allowLarge, excludeId: rozoPaymentId });
10918
+ const caps = skipCaps ? null : assertPaymentLimit(state.invoiceAmount);
10919
10919
  const next = {
10920
10920
  ...state,
10921
10921
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -10958,50 +10958,22 @@ function recordSendResult(rozoPaymentId, { status, txHash = null, note = null })
10958
10958
  return next;
10959
10959
  });
10960
10960
  }
10961
- function sessionSpendUsd({ excludeId = null } = {}) {
10962
- const dir = stateRoot();
10963
- let files = [];
10964
- try {
10965
- files = fs3.readdirSync(dir).filter((f) => f.endsWith(".json"));
10966
- } catch {
10967
- return 0;
10968
- }
10969
- let total = 0;
10970
- for (const f of files) {
10971
- if (excludeId && f === `${excludeId}.json`) continue;
10972
- try {
10973
- const s = JSON.parse(fs3.readFileSync(path2.join(dir, f), "utf8"));
10974
- if (s?.send && ["claimed", "submitted", "confirmed", "ambiguous"].includes(s.send.status)) {
10975
- const usd = Number(s.invoiceAmount);
10976
- total += Number.isFinite(usd) ? usd : MAX_TX_USD;
10977
- }
10978
- } catch {
10979
- total += MAX_TX_USD;
10980
- }
10961
+ var MAX_PAYMENT_USD = 1100;
10962
+ function assertPaymentLimit(invoiceUsd) {
10963
+ if (invoiceUsd === null || invoiceUsd === void 0 || String(invoiceUsd).trim() === "") {
10964
+ throw new SkillError("BAD_INVOICE_AMOUNT", "Cannot evaluate the payment limit without a USD amount.");
10981
10965
  }
10982
- return total;
10983
- }
10984
- var MAX_TX_USD = 100;
10985
- var MAX_SESSION_USD = 200;
10986
- function assertSpendCapsUnlocked(invoiceUsd, { allowLarge = false, excludeId = null } = {}) {
10987
10966
  const usd = Number(invoiceUsd);
10988
10967
  if (!Number.isFinite(usd) || usd < 0) {
10989
- throw new SkillError("BAD_INVOICE_AMOUNT", "Cannot evaluate spend caps without a USD amount.");
10968
+ throw new SkillError("BAD_INVOICE_AMOUNT", "Cannot evaluate the payment limit without a USD amount.");
10990
10969
  }
10991
- if (usd > MAX_TX_USD && !allowLarge) {
10970
+ if (usd > MAX_PAYMENT_USD) {
10992
10971
  throw new SkillError(
10993
10972
  "CAP_PER_TX",
10994
- `$${usd} exceeds the $${MAX_TX_USD} per-transaction cap. Re-run with --yes-large to override.`
10973
+ `$${usd} is above the $${MAX_PAYMENT_USD} per-payment limit for automated sending. This limit has no override. Pay this invoice from a wallet you control instead \u2014 that path needs no key and has no limit.`
10995
10974
  );
10996
10975
  }
10997
- const prior = sessionSpendUsd({ excludeId });
10998
- if (prior + usd > MAX_SESSION_USD) {
10999
- throw new SkillError(
11000
- "CAP_SESSION",
11001
- `This send would bring cumulative spend to $${(prior + usd).toFixed(2)}, past the $${MAX_SESSION_USD} session cap. Clear or archive the state directory to start a new session.`
11002
- );
11003
- }
11004
- return { priorUsd: prior, totalUsd: prior + usd };
10976
+ return { usd, limitUsd: MAX_PAYMENT_USD };
11005
10977
  }
11006
10978
 
11007
10979
  // scripts/src/lib/presend.mjs
@@ -22454,8 +22426,7 @@ async function main(argv) {
22454
22426
  memo: null,
22455
22427
  nonceBefore,
22456
22428
  expectedTxHash
22457
- },
22458
- { allowLarge: Boolean(args["yes-large"]) }
22429
+ }
22459
22430
  );
22460
22431
  let txHash = null;
22461
22432
  try {
@@ -32370,7 +32370,7 @@ function depositDigest(source) {
32370
32370
  });
32371
32371
  return crypto3.createHash("sha256").update(canonical, "utf8").digest("hex");
32372
32372
  }
32373
- function claimSend(rozoPaymentId, intent, { allowLarge = false, skipCaps = false } = {}) {
32373
+ function claimSend(rozoPaymentId, intent, { skipCaps = false } = {}) {
32374
32374
  return withLock(() => {
32375
32375
  const state = readState(rozoPaymentId);
32376
32376
  if (!state) {
@@ -32386,7 +32386,7 @@ function claimSend(rozoPaymentId, intent, { allowLarge = false, skipCaps = false
32386
32386
  { send: state.send }
32387
32387
  );
32388
32388
  }
32389
- const caps = skipCaps ? null : assertSpendCapsUnlocked(state.invoiceAmount, { allowLarge, excludeId: rozoPaymentId });
32389
+ const caps = skipCaps ? null : assertPaymentLimit(state.invoiceAmount);
32390
32390
  const next = {
32391
32391
  ...state,
32392
32392
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -32429,50 +32429,22 @@ function recordSendResult(rozoPaymentId, { status, txHash = null, note = null })
32429
32429
  return next;
32430
32430
  });
32431
32431
  }
32432
- function sessionSpendUsd({ excludeId = null } = {}) {
32433
- const dir = stateRoot();
32434
- let files = [];
32435
- try {
32436
- files = fs3.readdirSync(dir).filter((f) => f.endsWith(".json"));
32437
- } catch {
32438
- return 0;
32439
- }
32440
- let total = 0;
32441
- for (const f of files) {
32442
- if (excludeId && f === `${excludeId}.json`) continue;
32443
- try {
32444
- const s = JSON.parse(fs3.readFileSync(path2.join(dir, f), "utf8"));
32445
- if (s?.send && ["claimed", "submitted", "confirmed", "ambiguous"].includes(s.send.status)) {
32446
- const usd = Number(s.invoiceAmount);
32447
- total += Number.isFinite(usd) ? usd : MAX_TX_USD;
32448
- }
32449
- } catch {
32450
- total += MAX_TX_USD;
32451
- }
32432
+ var MAX_PAYMENT_USD = 1100;
32433
+ function assertPaymentLimit(invoiceUsd) {
32434
+ if (invoiceUsd === null || invoiceUsd === void 0 || String(invoiceUsd).trim() === "") {
32435
+ throw new SkillError("BAD_INVOICE_AMOUNT", "Cannot evaluate the payment limit without a USD amount.");
32452
32436
  }
32453
- return total;
32454
- }
32455
- var MAX_TX_USD = 100;
32456
- var MAX_SESSION_USD = 200;
32457
- function assertSpendCapsUnlocked(invoiceUsd, { allowLarge = false, excludeId = null } = {}) {
32458
32437
  const usd = Number(invoiceUsd);
32459
32438
  if (!Number.isFinite(usd) || usd < 0) {
32460
- throw new SkillError("BAD_INVOICE_AMOUNT", "Cannot evaluate spend caps without a USD amount.");
32439
+ throw new SkillError("BAD_INVOICE_AMOUNT", "Cannot evaluate the payment limit without a USD amount.");
32461
32440
  }
32462
- if (usd > MAX_TX_USD && !allowLarge) {
32441
+ if (usd > MAX_PAYMENT_USD) {
32463
32442
  throw new SkillError(
32464
32443
  "CAP_PER_TX",
32465
- `$${usd} exceeds the $${MAX_TX_USD} per-transaction cap. Re-run with --yes-large to override.`
32466
- );
32467
- }
32468
- const prior = sessionSpendUsd({ excludeId });
32469
- if (prior + usd > MAX_SESSION_USD) {
32470
- throw new SkillError(
32471
- "CAP_SESSION",
32472
- `This send would bring cumulative spend to $${(prior + usd).toFixed(2)}, past the $${MAX_SESSION_USD} session cap. Clear or archive the state directory to start a new session.`
32444
+ `$${usd} is above the $${MAX_PAYMENT_USD} per-payment limit for automated sending. This limit has no override. Pay this invoice from a wallet you control instead \u2014 that path needs no key and has no limit.`
32473
32445
  );
32474
32446
  }
32475
- return { priorUsd: prior, totalUsd: prior + usd };
32447
+ return { usd, limitUsd: MAX_PAYMENT_USD };
32476
32448
  }
32477
32449
 
32478
32450
  // scripts/src/lib/presend.mjs
@@ -34557,8 +34529,7 @@ async function main(argv) {
34557
34529
  amountAtomic: amountAtomic.toString(),
34558
34530
  memo: source.receiverMemo ?? null,
34559
34531
  expectedTxHash: signature
34560
- },
34561
- { allowLarge: Boolean(args["yes-large"]) }
34532
+ }
34562
34533
  );
34563
34534
  let sent = null;
34564
34535
  try {
package/README.es.md DELETED
@@ -1,287 +0,0 @@
1
- # rozo-checkout
2
-
3
- [English](README.md) | [简体中文](README.zh.md) | [日本語](README.ja.md) | **Español**
4
-
5
- **[Inicio rápido →](QUICKSTART.es.md)** — pagar un enlace en cinco minutos, sin leer todo esto.
6
-
7
- ```bash
8
- npx @rozoai/checkout pay <coinbase-link> --with usdt-solana
9
- ```
10
-
11
- Pagar un **enlace de pago de Coinbase (Coinbase Payment Link) de OpenRouter** con
12
- una moneda que ese enlace no puede aceptar directamente: BTC por Lightning, o
13
- USDT/USDC en Solana, BNB Chain, Ethereum, Polygon, Base o Stellar.
14
-
15
- Un enlace de pago de Coinbase solo acepta USDC en Base. Este repositorio es una
16
- habilidad de agente (agent skill) —más los scripts de Node que la respaldan— que
17
- enruta cualquiera de las monedas anteriores a través de un puente (bridge): se
18
- obtiene una dirección de depósito (deposit address) de un solo uso para la moneda
19
- que realmente se posee y, una vez que el depósito llega, una billetera fondeadora
20
- (funder wallet) liquida la factura de Coinbase en nombre del usuario.
21
-
22
- En la factura en sí **no hay descuento**: `callerPays` es igual al importe de la
23
- factura. El **depósito** que se envía es una cifra distinta y normalmente es
24
- mayor: incluye las comisiones del puente y de la cadena de origen necesarias para
25
- entregar el importe de la factura en USDC sobre Base. Enviar siempre exactamente
26
- el `deposit.amount` que devuelve el backend; nunca suponer que es igual a la
27
- factura.
28
-
29
- - `SKILL.md` — las instrucciones dirigidas al agente (formato de skill de Claude Code).
30
- - `llms.txt` — un resumen compacto para agentes que leen un solo archivo.
31
- - `scripts/` — la implementación en Node; `src/` es el código fuente y `dist/`
32
- contiene paquetes autocontenidos que se pueden ejecutar con `node` a secas.
33
- - `test/` — pruebas unitarias offline de la lógica de manejo de dinero y de seguridad.
34
- - `PLAN.md` — el documento de diseño que sigue la implementación.
35
-
36
- **No** hace falta una cuenta, una clave de API ni ninguna relación con el
37
- operador para usar esto. Todos los endpoints que invoca son públicos y sin clave.
38
-
39
- ## Cómo funciona
40
-
41
- ```mermaid
42
- flowchart TD
43
- A["Coinbase Payment Link<br/>payments.coinbase.com/payment-links/pl_*"] --> B
44
- B["quote-invoice<br/>(public POST)"] --> C["create-invoice<br/>(public POST)"]
45
- C --> D["Bridge order<br/>one-time deposit address<br/>+ exact amount + expiry"]
46
- D --> E["You send USDT/USDC/BTC<br/>on your chosen chain"]
47
- E --> F["Bridge converts the pay-in"]
48
- F --> G["Funder wallet settles<br/>the Coinbase invoice"]
49
- G --> H["Merchant is paid<br/>(e.g. OpenRouter credit)"]
50
- ```
51
-
52
- Lo mismo en ASCII, para terminales sin mermaid:
53
-
54
- ```
55
- Coinbase Payment Link (pl_* / paymentSession_*)
56
- |
57
- v
58
- [ quote-invoice ] -> merchant, amount, expiry, short-lived quote receipt
59
- |
60
- v
61
- [ create-invoice ] -> bridge order: rozoPaymentId + deposit address
62
- | + exact amount + order expiry
63
- v
64
- you send USDT / USDC / BTC on your chain ------> deposit address
65
- |
66
- v
67
- bridge converts the pay-in ------> funder wallet pays the Coinbase invoice
68
- |
69
- v
70
- merchant credited; poll until the state is `settled`
71
- ```
72
-
73
- Aparecen tres identificadores a lo largo del documento y nunca son intercambiables:
74
-
75
- | Identificador | Qué es |
76
- |---|---|
77
- | `linkId` | el id de Coinbase: `pl_*` (enlace de pago) o `paymentSession_*` (sesión v3) |
78
- | `rozoPaymentId` | el UUID de la orden del puente: se usa para el detalle del depósito y el estado |
79
- | `paymentLink` | una URL de página de pago alojada para la orden del puente (alternativa para humanos) |
80
-
81
- ## Orígenes admitidos
82
-
83
- | Cadena | Id de cadena | Tokens | Notas |
84
- |---|---|---|---|
85
- | Ethereum | `1` | USDC, USDT | 6 decimales |
86
- | BNB Chain | `56` | USDC, USDT | **18 decimales**: la causa habitual de errores por un factor de 10^12 |
87
- | Polygon | `137` | USDC, USDT | 6 decimales |
88
- | Base | `8453` | USDC | 6 decimales |
89
- | Solana | `900` | USDC, USDT | 6 decimales; SPL, puede requerir un memo. SOL nativo **no** está admitido |
90
- | Stellar | `1500` | USDC | 7 decimales; requiere el memo |
91
- | Bitcoin Lightning | `lightning` | BTC | los importes son **satoshis** enteros, pagados mediante una factura BOLT11 (BOLT11) |
92
-
93
- Las monedas de gas nativas (SOL, BNB, ETH, MATIC) y el BTC on-chain no se aceptan.
94
-
95
- ## Inicio rápido
96
-
97
- ### 1. Probar los endpoints públicos con curl
98
-
99
- Reemplazar `pl_01YOURLINKID` por un enlace de pago de Coinbase real. No se
100
- necesita ninguna cabecera de autenticación en nada de lo que sigue.
101
-
102
- ```bash
103
- MPP="https://apiserver.mpprouter.dev/v1/services/rozo-agent-api"
104
- INTENTS="https://intentapiv4.rozo.ai/functions/v1/payment-api"
105
- LINK="https://payments.coinbase.com/payment-links/pl_01YOURLINKID"
106
-
107
- # Cotizarlo: comercio, importe, expiración y un quoteReceipt de ~60 segundos.
108
- curl -s -X POST "$MPP/quote-invoice" \
109
- -H 'content-type: application/json' \
110
- -d "{\"url\":\"$LINK\"}"
111
-
112
- # Crear una orden de puente para, por ejemplo, USDT en Solana.
113
- # Esto crea una orden pero no mueve dinero; una orden sin fondear simplemente expira.
114
- curl -s -X POST "$MPP/create-invoice" \
115
- -H 'content-type: application/json' \
116
- -d "{\"url\":\"$LINK\",\"source\":{\"chainId\":\"900\",\"tokenSymbol\":\"USDT\"}}"
117
-
118
- # Instrucciones de depósito (autoritativas), usando el rozoPaymentId de arriba.
119
- curl -s "$INTENTS/payments/<rozoPaymentId>"
120
-
121
- # Estado del cumplimiento, usando el linkId de Coinbase.
122
- curl -s "$MPP/invoice-status?payment_id=pl_01YOURLINKID"
123
- ```
124
-
125
- `create-invoice` tiene límite de tasa por IP (unas 30/hora); los endpoints de
126
- lectura no.
127
-
128
- ### 2. Usar la CLI
129
-
130
- No hay nada que instalar: `npx` la descarga y la ejecuta:
131
-
132
- ```bash
133
- npx @rozoai/checkout quote <coinbase-link>
134
- npx @rozoai/checkout pay <coinbase-link> --with usdt-solana
135
- npx @rozoai/checkout status <rozoPaymentId>
136
- ```
137
-
138
- `pay` recorre todo el flujo: cotizar, crear la orden, mostrar una revisión
139
- enmascarada, pedir un sí, y luego imprimir las instrucciones de depósito y
140
- consultar hasta la liquidación. Añadir `--send` para pagar desde una billetera
141
- caliente en lugar de la propia, `--json` para salida de máquina y `--help` para
142
- todo lo demás.
143
-
144
- ### 3. O ejecutar los scripts directamente
145
-
146
- Clonar el repositorio da los mismos flujos como scripts individuales: es lo que
147
- usa la habilidad de agente, y lo que la CLI invoca por debajo.
148
-
149
- Cada script imprime exactamente un objeto JSON en stdout. Código de salida `0`
150
- éxito, `1` rechazado/fallido (leer `error.code`), `2` uso incorrecto, `3` enviado
151
- pero sin confirmar.
152
-
153
- ```bash
154
- # Paso 1 — cotización de solo lectura, no cuesta nada
155
- node scripts/dist/quote.js --url "$LINK"
156
-
157
- # Paso 2 — crear la orden (no se mueve dinero; las órdenes sin fondear expiran).
158
- # La dirección de depósito completa se RETIENE en esta etapa: se obtiene
159
- # el resumen enmascarado para revisar, y `depositWithheld: true`.
160
- node scripts/dist/create-order.js --url "$LINK" --chain 900 --token USDT
161
-
162
- # Paso 3 — revisar el importe, la cadena, la dirección enmascarada, el requisito
163
- # de memo y la expiración. Solo cuando se haya decidido pagar, volver a
164
- # ejecutar el mismo comando con --confirm. Esto libera el bloque de
165
- # depósito completo y registra la confirmación que exigen los scripts de envío.
166
- node scripts/dist/create-order.js --url "$LINK" --chain 900 --token USDT --confirm
167
-
168
- # Paso 4a — Modo A: pagar el bloque de depósito desde la propia billetera,
169
- # y luego observar la liquidación
170
- node scripts/dist/status.js --rozo-payment-id <uuid> --watch --timeout 600
171
-
172
- # Paso 4b — Modo B: dejar que el script pague desde una billetera caliente.
173
- # --dry-run no firma nada; un envío real requiere además --send.
174
- ROZO_CHECKOUT_SOL_KEY=<base58 secret key> \
175
- node scripts/dist/send-sol.js --rozo-payment-id <uuid> --dry-run
176
- ```
177
-
178
- Los `scripts/dist/*.js` son paquetes autocontenidos: no hace falta `npm install`
179
- en el lugar de la llamada. La CLI anterior es el mismo código con una superficie
180
- más amable: importa estos flujos en lugar de reimplementarlos, así que todas las
181
- comprobaciones de abajo aplican igual sea cual sea el punto de entrada que se
182
- use.
183
-
184
- ## Compilación y pruebas
185
-
186
- ```bash
187
- npm install # solo hace falta para recompilar o para ejecutar las pruebas
188
- npm run build # esbuild -> scripts/dist/*.js (objetivo node18) + blacklist.json
189
- npm test # node:test, totalmente offline
190
- npm run check # build + test
191
- ```
192
-
193
- Las pruebas **no hacen llamadas de red**; cada respuesta del backend es un
194
- fixture en `test/fixtures/`. Cubren la conversión a importes atómicos (6/18
195
- decimales y satoshis de Lightning), la aritmética del margen de expiración, la
196
- normalización de direcciones comprometidas y el comportamiento de fallo cerrado
197
- (fail-closed), la decisión de reutilización de órdenes, el comparador de
198
- verificación posterior a la creación y las reglas de completitud del depósito.
199
-
200
- Dos grupos lanzan procesos hijos reales en lugar de llamar a funciones, porque
201
- prueban cosas que una prueba de un solo proceso no puede: la suite de
202
- concurrencia hace competir a varios procesos por reclamar una misma orden
203
- (exactamente uno puede ganar) y por agotar el tope de la sesión, y la suite de
204
- puntos de entrada ejecuta los scripts de envío compilados para demostrar que
205
- rechazan sin `--send`, sin una confirmación y después de una reclamación previa.
206
-
207
- ## Diseño de seguridad
208
-
209
- Lo más interesante de este repositorio es aquello que se niega a hacer.
210
-
211
- - **Confirmación en dos fases, obligatoria.** `create-order.js` retiene la
212
- dirección de depósito completa, el memo y la factura BOLT11 hasta que se
213
- vuelve a ejecutar con `--confirm`, lo cual registra una confirmación ligada a
214
- un sha256 de esas instrucciones exactas. Los scripts de envío rechazan la
215
- operación si no están presentes tanto `--send` como una confirmación cuyo
216
- digest siga coincidiendo con los datos en vivo, de modo que ni una invocación
217
- accidental ni una dirección de depósito sustituida pueden mover fondos.
218
- - **Factura completa, siempre.** `callerPays` debe ser igual al importe de la
219
- factura y `discount` debe ser `"0"`; cualquier otra cosa aborta con
220
- `NO_DISCOUNT_VIOLATION`. Los campos críticos para la seguridad (`linkId`,
221
- `merchant`, `original`, `callerPays`, el origen reflejado) deben estar
222
- presentes además de ser iguales: un campo ausente es una desviación (drift).
223
- - **Protección contra reutilización.** Crear una orden para un enlace que ya
224
- tiene una orden no expirada devuelve esa orden existente, incluso si ya ha
225
- sido fondeada. Por eso, en cada ejecución se exige que la orden en vivo esté
226
- sin pagar (`payment_unpaid`, sin hash de transacción, sin importe recibido,
227
- sin confirmación) y que coincida con la cadena y el token elegidos por quien
228
- llama. En caso contrario: `ORDER_ALREADY_FUNDED` o `REUSED_SOURCE_MISMATCH`.
229
- - **Regla de dinero detectado, con fallo cerrado.** Una vez que existe cualquier
230
- ingreso, la herramienta nunca informa de un simple fallo, nunca aconseja pagar
231
- de nuevo y nunca reintenta con una orden nueva. Un `amountReceived` que no es
232
- nulo pero resulta ilegible cuenta como dinero, no como ausencia de dinero. Un
233
- backend ilegible se reporta como `unknown`, nunca como `awaiting_deposit`.
234
- - **Instrucciones de depósito completas.** Un importe cero, negativo o no
235
- interpretable aborta la operación. Lightning requiere la BOLT11 (que llega en
236
- `source.lnInvoice` con una dirección vacía). Stellar requiere su memo: un memo
237
- ausente es un aborto duro, nunca se representa como "no se requiere memo".
238
- - **Márgenes de expiración.** El pago se rechaza salvo que la más temprana entre
239
- la expiración de la orden y la de Coinbase esté a más de un margen por cadena
240
- (10 min en EVM y Stellar, 5 min en Solana). Lightning exige además al menos 10
241
- minutos de validez de la BOLT11. Una fecha límite ausente o no interpretable
242
- aborta la operación.
243
- - **Revalidación de la pagabilidad.** Un recibo de cotización hace que la
244
- creación de la orden omita la comprobación en vivo contra Coinbase, y el
245
- enlace puede ser consumido por otra persona en cualquier momento; por eso la
246
- pagabilidad se vuelve a comprobar inmediatamente antes de mostrar la dirección
247
- de depósito, y otra vez como último paso antes de la difusión (broadcast),
248
- después de toda la preparación de RPC. Un estado incompleto de Coinbase se
249
- trata como "no se puede demostrar que sea pagable", no como pagable.
250
- - **Lista de direcciones comprometidas, con fallo cerrado.**
251
- `scripts/src/lib/blacklist.json` incluye una lista incorporada con una
252
- cabecera de procedencia y un sha256 sobre las direcciones. El digest solo
253
- demuestra que esta copia incorporada no ha sido editada desde su fecha de
254
- sincronización; no es una firma de la fuente original. Se comprueban tanto la
255
- dirección de depósito como la billetera emisora. Si el archivo falta, está mal
256
- formado, está vacío o su digest no coincide, se rechaza todo envío en lugar de
257
- continuar sin comprobación.
258
- - **Envío único, entre procesos.** El estado de la orden vive en
259
- `$HOME/.rozo-checkout/state/<uuid>.json`, escrito de forma atómica (archivo
260
- temporal, fsync, renombrado). Cada ciclo de lectura-modificación-escritura de
261
- un archivo de estado —la reclamación, los topes de gasto, el registro de la
262
- orden y la confirmación— se ejecuta dentro de un único archivo de bloqueo
263
- exclusivo, de modo que dos invocaciones concurrentes no pueden ni decidir
264
- ambas que son la primera ni sobrescribir el registro de envío de la otra. Un
265
- envío se reclama *antes* de difundirse, así que un error ambiguo de RPC nunca
266
- puede convertirse en una segunda transferencia. Las transacciones se firman
267
- antes de la difusión para que el hash se conozca de antemano; ante un
268
- resultado ambiguo, los scripts consultan esa transacción exacta en lugar de
269
- volver a difundirla.
270
- - **Controles de la billetera caliente.** Las claves provienen únicamente del
271
- entorno (`ROZO_CHECKOUT_EVM_KEY`, `ROZO_CHECKOUT_SOL_KEY`), nunca se imprimen
272
- y nunca se aceptan por línea de comandos; los errores de bibliotecas y de RPC
273
- se redactan antes de mostrarse, incluidas las URL de proveedor que llevan
274
- credenciales, los tokens bearer y las cadenas con forma de clave. Los scripts
275
- se niegan a ejecutarse cuando cualquier `.env`/`.env.*` del directorio de
276
- trabajo está bajo seguimiento de git, y se niegan con igual firmeza cuando git
277
- no puede demostrar que no lo está. El id de cadena del RPC (o el hash génesis
278
- de Solana) y los decimales on-chain del token se verifican antes de firmar.
279
- Topes: $100 por transacción, $200 acumulados por sesión.
280
- - **Enmascaramiento de direcciones.** El texto muestra `first6...last4`. La
281
- dirección de depósito completa, el memo y la cadena BOLT11 aparecen solo
282
- dentro del objeto `deposit` legible por máquina, de modo que siguen siendo
283
- copiables sin quedar dispersos por los registros.
284
-
285
- ## Licencia
286
-
287
- MIT.