@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/QUICKSTART.zh.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# 快速开始
|
|
2
|
+
|
|
3
|
+
[English](QUICKSTART.md) | **简体中文** | [日本語](QUICKSTART.ja.md) | [Español](QUICKSTART.es.md)
|
|
4
|
+
|
|
5
|
+
用 Solana、BNB Chain、Ethereum、Polygon、Base 或 Stellar 上的 USDT/USDC,或者用走
|
|
6
|
+
Lightning 的 BTC,去支付一个 OpenRouter Coinbase 收款链接 (Payment Link)。五分钟搞定。
|
|
7
|
+
工作原理与设计理由见 [README.md](README.md)。
|
|
8
|
+
|
|
9
|
+
## 一行命令搞定
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx @rozoai/checkout pay https://payments.coinbase.com/payment-links/pl_01YOURLINKID --with usdt-solana
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
这一条命令会替你跑完下面的每一步:报价、创建订单、核对、确认、充值指令,然后一直轮询到结清。`--with` 可选的币种有:`usdt-solana`、`usdc-solana`、`usdt-bnb`、`usdc-bnb`、`usdt-ethereum`、`usdc-ethereum`、`usdt-polygon`、`usdc-polygon`、`usdc-base`、`usdc-stellar`、`btc-lightning`。
|
|
16
|
+
|
|
17
|
+
加 `--send` 就用热钱包付款而不是用你自己的钱包,加 `--json` 输出机器可读格式。
|
|
18
|
+
|
|
19
|
+
本页剩下的内容就是同一套流程,只不过一步一步来 —— 如果出了问题,或者你想自己写脚本,那才需要看它们。
|
|
20
|
+
|
|
21
|
+
## 开始之前
|
|
22
|
+
|
|
23
|
+
- **Node 18 或更高版本**(`node -v`)。别的什么都不用装 —— `npx` 会去拉取 CLI,而克隆下来的
|
|
24
|
+
`scripts/dist/*.js` 都是自包含的打包产物。
|
|
25
|
+
- **一个钱包**,在你选的链上持有你想用来付款的币种。
|
|
26
|
+
- **那个 Coinbase 链接**,例如
|
|
27
|
+
`https://payments.coinbase.com/payment-links/pl_01YOURLINKID`。
|
|
28
|
+
- 不需要账号,也不需要 API key。这里用到的每个端点都是公开的。
|
|
29
|
+
|
|
30
|
+
先设一次:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
LINK="https://payments.coinbase.com/payment-links/pl_01YOURLINKID"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
链 id:`1` Ethereum · `56` BNB Chain · `137` Polygon · `8453` Base ·
|
|
37
|
+
`900` Solana · `1500` Stellar · `lightning` Bitcoin Lightning。
|
|
38
|
+
|
|
39
|
+
## 1. 报价(只读,免费)
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
node scripts/dist/quote.js --url "$LINK"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"success": true,
|
|
48
|
+
"merchant": "OpenRouter, Inc.",
|
|
49
|
+
"invoice": { "amount": "5.00", "fiat": { "amount": "5.00", "currency": "USD" } },
|
|
50
|
+
"callerPays": "5.00",
|
|
51
|
+
"coinbaseExpiryIso": "2026-08-09T10:00:00.000Z"
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`"success": false` 且带 `LINK_NO_LONGER_PAYABLE`,说明这个链接已被付过或已过期 ——
|
|
56
|
+
去要一个新的,然后停止。
|
|
57
|
+
|
|
58
|
+
## 2. 创建订单
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
node scripts/dist/create-order.js --url "$LINK" --chain 900 --token USDT
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
这一步不动任何资金,未付款的订单会自然过期。完整充值地址 (deposit address) 在此阶段
|
|
65
|
+
会被**扣留** —— 你只拿到打码后的摘要,先用它核对。
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"success": true,
|
|
70
|
+
"rozoPaymentId": "11111111-2222-4333-8444-555555555555",
|
|
71
|
+
"invoice": { "amount": "5.000000", "currency": "USD" },
|
|
72
|
+
"deposit": null,
|
|
73
|
+
"depositWithheld": true,
|
|
74
|
+
"display": {
|
|
75
|
+
"chain": "Solana",
|
|
76
|
+
"amount": "5.021000 USDT",
|
|
77
|
+
"payToMasked": "9WzDXw...AWWM",
|
|
78
|
+
"memoRequirement": "This deposit REQUIRES the memo/tag below. ..."
|
|
79
|
+
},
|
|
80
|
+
"expiry": { "effectiveDeadlineIso": "2026-08-08T11:00:00.000Z", "minutesOfSlack": 55 }
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**继续之前务必核对 `display.amount`。** 它通常大于账单金额 —— 因为它包含了跨桥费用和
|
|
85
|
+
网络费用。记下 `rozoPaymentId`;后面每一条命令都要用它。
|
|
86
|
+
|
|
87
|
+
## 3. 确认
|
|
88
|
+
|
|
89
|
+
只有在你已经决定付款之后,才带上 `--confirm` 重新执行同一条命令:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
node scripts/dist/create-order.js --url "$LINK" --chain 900 --token USDT --confirm
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
这会释放完整的充值详情,并记录下发送脚本所需的那条确认。
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"success": true,
|
|
100
|
+
"confirmed": true,
|
|
101
|
+
"deposit": {
|
|
102
|
+
"chain": "Solana",
|
|
103
|
+
"tokenSymbol": "USDT",
|
|
104
|
+
"receiverAddress": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
|
|
105
|
+
"receiverMemo": "rozo-901",
|
|
106
|
+
"amount": "5.021000",
|
|
107
|
+
"payTo": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
对于 Lightning,`deposit.lnInvoice` 里是供扫码的 BOLT11 字符串,`deposit.amount`
|
|
113
|
+
的单位是聪 (satoshis)。
|
|
114
|
+
|
|
115
|
+
## 4. 付款
|
|
116
|
+
|
|
117
|
+
二选一。**模式 A** —— 用你自己的钱包付:在 `deposit.chain` 上,把恰好
|
|
118
|
+
`deposit.amount` 数量的 `deposit.tokenSymbol` 发送到 `deposit.receiverAddress`,
|
|
119
|
+
如果有 `deposit.receiverMemo` 就带上它。地址直接从 JSON 里复制;绝不要手打重输。
|
|
120
|
+
|
|
121
|
+
**模式 B** —— 让脚本从热钱包付款(仅限 EVM 与 Solana):
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# 预览将要签名的确切内容 —— 不会签任何名
|
|
125
|
+
ROZO_CHECKOUT_SOL_KEY=<base58 secret key> \
|
|
126
|
+
node scripts/dist/send-sol.js --rozo-payment-id <rozoPaymentId> --dry-run
|
|
127
|
+
|
|
128
|
+
# 真正发送。--send 是必需的。
|
|
129
|
+
ROZO_CHECKOUT_SOL_KEY=<base58 secret key> \
|
|
130
|
+
node scripts/dist/send-sol.js --rozo-payment-id <rozoPaymentId> --send
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Ethereum、BNB Chain、Polygon 和 Base 用 `send-evm.js` 配合 `ROZO_CHECKOUT_EVM_KEY`。
|
|
134
|
+
额度上限:单笔 $100,单会话 $200。
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"success": true,
|
|
139
|
+
"submitted": true,
|
|
140
|
+
"confirmed": true,
|
|
141
|
+
"txHash": "3Bxs4h24hBjHziQ8UJqSjqjbjWQq2sQ3yV9Fq4HrVh5c"
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## 5. 观察结清
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
node scripts/dist/status.js --rozo-payment-id <rozoPaymentId> --watch --timeout 600
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"success": true,
|
|
154
|
+
"state": "settled",
|
|
155
|
+
"terminal": true,
|
|
156
|
+
"payin": { "txHash": "3Bxs4h24...", "confirmedAt": "2026-08-08T10:05:00.000Z" }
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
状态流转依次为 `awaiting_deposit` → `payin_detected` → `payin_confirmed` →
|
|
161
|
+
`bridging` → `paying_coinbase` → `settled`。你的链上交易被确认**并不**代表结束:
|
|
162
|
+
要一直轮询到 `settled` 为止。
|
|
163
|
+
|
|
164
|
+
## 出问题时
|
|
165
|
+
|
|
166
|
+
读 `error.code`。最可能遇到的三个:
|
|
167
|
+
|
|
168
|
+
| `error.code` | 发生了什么 | 该怎么办 |
|
|
169
|
+
|---|---|---|
|
|
170
|
+
| `LINK_NO_LONGER_PAYABLE` | 这个链接已经被别人付过,或者已过期 | 找商家要一个新链接;不要付任何钱 |
|
|
171
|
+
| `EXPIRY_MARGIN` | 剩余时间太短,不足以安全完成充值、跨桥和结清 | 让订单过期,然后从第 1 步重新开始 |
|
|
172
|
+
| `ALREADY_SENT` | 这个订单已经记录过一次发送 | **不要**再发一次;先跑 `status.js` 并去链上核对 |
|
|
173
|
+
|
|
174
|
+
**只要已经有钱离开了你的钱包,就绝不要再付一次。** 保留好 `linkId`、
|
|
175
|
+
`rozoPaymentId` 和每一个交易 hash,找人来做对账。向一个一次性充值地址付第二笔款,
|
|
176
|
+
并不保证会被入账。
|
|
177
|
+
|
|
178
|
+
完整的错误码表在 [README.md](README.md),面向 agent 的指令在 [SKILL.md](SKILL.md)。
|
package/README.es.md
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
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.
|