@rozoai/checkout 0.1.0 → 0.1.2

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.
@@ -0,0 +1,43 @@
1
+ # How it works
2
+
3
+ [← back to the README](../README.md) · [Quick start](QUICKSTART.md) · [Safety design](safety.md)
4
+
5
+ ```mermaid
6
+ flowchart TD
7
+ A["Coinbase Payment Link<br/>payments.coinbase.com/payment-links/pl_*"] --> B
8
+ B["quote-invoice<br/>(public POST)"] --> C["create-invoice<br/>(public POST)"]
9
+ C --> D["Bridge order<br/>one-time deposit address<br/>+ exact amount + expiry"]
10
+ D --> E["You send USDT/USDC/BTC<br/>on your chosen chain"]
11
+ E --> F["Bridge converts the pay-in"]
12
+ F --> G["Funder wallet settles<br/>the Coinbase invoice"]
13
+ G --> H["Merchant is paid<br/>(e.g. OpenRouter credit)"]
14
+ ```
15
+
16
+ The same thing as ASCII, for terminals without mermaid:
17
+
18
+ ```
19
+ Coinbase Payment Link (pl_* / paymentSession_*)
20
+ |
21
+ v
22
+ [ quote-invoice ] -> merchant, amount, expiry, short-lived quote receipt
23
+ |
24
+ v
25
+ [ create-invoice ] -> bridge order: rozoPaymentId + deposit address
26
+ | + exact amount + order expiry
27
+ v
28
+ you send USDT / USDC / BTC on your chain ------> deposit address
29
+ |
30
+ v
31
+ bridge converts the pay-in ------> funder wallet pays the Coinbase invoice
32
+ |
33
+ v
34
+ merchant credited; poll until the state is `settled`
35
+ ```
36
+
37
+ Three identifiers appear throughout and are never interchangeable:
38
+
39
+ | Identifier | What it is |
40
+ |---|---|
41
+ | `linkId` | the Coinbase id: `pl_*` (Payment Link) or `paymentSession_*` (v3 session) |
42
+ | `rozoPaymentId` | the bridge order's UUID — use this for deposit detail and status |
43
+ | `paymentLink` | a hosted pay page URL for the bridge order (human fallback) |
package/docs/safety.md ADDED
@@ -0,0 +1,78 @@
1
+ # Safety design
2
+
3
+ [← back to the README](../README.md) · [Quick start](QUICKSTART.md) · [How it works](how-it-works.md)
4
+
5
+ The interesting part of this project is what it refuses to do. The README keeps
6
+ the three rules a payer needs; everything below is the full list, for anyone
7
+ reviewing or extending the code.
8
+
9
+ - **Two-phase confirmation, enforced.** `create-order.js` withholds the full
10
+ deposit address, memo and BOLT11 until it is re-run with `--confirm`, which
11
+ records a confirmation bound to a sha256 of those exact instructions. The
12
+ send scripts refuse without both `--send` and a confirmation whose digest
13
+ still matches the live data, so neither an accidental invocation nor a
14
+ swapped deposit address can move funds.
15
+ - **Full invoice, always.** `callerPays` must equal the invoice amount and
16
+ `discount` must be `"0"`; anything else aborts with `NO_DISCOUNT_VIOLATION`.
17
+ Security-critical fields (`linkId`, `merchant`, `original`, `callerPays`, the
18
+ echoed source) must be present as well as equal — a missing field is drift.
19
+ - **Reuse guard.** Creating an order for a link that already has an unexpired
20
+ order returns that existing order — even if it has already been funded. So on
21
+ every run the live order is required to be unpaid (`payment_unpaid`, no tx
22
+ hash, no amount received, no confirmation) and to match the chain and token
23
+ the caller chose. Otherwise: `ORDER_ALREADY_FUNDED` or
24
+ `REUSED_SOURCE_MISMATCH`.
25
+ - **Money-detected rule, fail closed.** Once any pay-in exists, the tooling
26
+ never reports a plain failure, never advises paying again, and never retries
27
+ into a new order. An `amountReceived` that is non-null but unreadable counts
28
+ as money, not as absence of money. An unreadable backend reports `unknown`,
29
+ never `awaiting_deposit`.
30
+ - **Complete deposit instructions.** A zero, negative or unparsable amount
31
+ aborts. Lightning requires the BOLT11 (which arrives in `source.lnInvoice`
32
+ with an empty address). A Stellar deposit is a shared hub address plus a
33
+ per-order memo, so the memo is part of the destination: an order that
34
+ arrives without one is a hard abort, never rendered as "no memo required".
35
+ - **Expiry margins.** Payment is refused unless the earlier of the order expiry
36
+ and the Coinbase expiry is more than a per-chain margin away (10 min EVM and
37
+ Stellar, 5 min Solana). Lightning additionally requires at least 10 minutes
38
+ of BOLT11 validity. A missing or unparsable deadline aborts.
39
+ - **Payability revalidation.** A quote receipt makes order creation skip the
40
+ live Coinbase check, and the link can be consumed by someone else at any
41
+ moment — so payability is re-checked immediately before the deposit address
42
+ is shown, and again as the very last step before broadcast, after all the RPC
43
+ preparation. Incomplete Coinbase state is treated as "cannot prove payable",
44
+ not as payable.
45
+ - **Compromised-address list, fail closed.** `scripts/src/lib/blacklist.json`
46
+ carries a vendored list with a provenance header and a sha256 over the
47
+ addresses. The digest proves only that this vendored copy has not been edited
48
+ since its sync date; it is not a signature by the upstream source. Both the
49
+ deposit address and the sending wallet are checked. If the file is missing,
50
+ malformed, empty, or its digest does not match, every send is refused rather
51
+ than proceeding unchecked.
52
+ - **Send-once, across processes.** Order state lives in
53
+ `$HOME/.rozo-checkout/state/<uuid>.json`, written atomically (temp file,
54
+ fsync, rename). Every read-modify-write of a state file — the claim, the
55
+ spend caps, the order record and the confirmation — runs inside one exclusive
56
+ lockfile, so two concurrent invocations can neither both decide they are
57
+ first, nor overwrite each other's send record. A send is claimed *before* broadcasting, so an ambiguous RPC error can
58
+ never become a second transfer. Transactions are signed before broadcast so
59
+ the hash is known in advance; on an ambiguous result the scripts look that
60
+ exact transaction up instead of rebroadcasting.
61
+ - **Hot-wallet controls.** A signing key is read from a local key file (a
62
+ solana-keygen keypair, or an encrypted V3 keystore whose passphrase is
63
+ prompted) or from the environment for unattended runs. A key file that is
64
+ readable by other users, or tracked by git, is refused — as is a `.env`
65
+ carrying the same settings, which is parsed as plain text and never
66
+ evaluated by a shell. Keys and passphrases
67
+ are never accepted on the command line, and are never printed; library and RPC errors are redacted
68
+ before display, including credential-bearing provider URLs, bearer tokens and
69
+ key-shaped strings. The scripts refuse to run when any `.env`/`.env.*` in the
70
+ working directory is git-tracked, and refuse just as hard when git cannot
71
+ prove it is untracked. The RPC's chain id (or Solana genesis hash) and the token's
72
+ on-chain decimals are verified before signing. One limit applies: a single
73
+ payment may not exceed $1,100. There is no override flag and no cumulative
74
+ session cap — a larger invoice is paid from your own wallet, which needs no
75
+ key and has no limit.
76
+ - **Address masking.** Prose shows `first6...last4`. The full deposit address,
77
+ memo and BOLT11 string appear only inside the machine-readable `deposit`
78
+ object, so they stay copy-pastable without being scattered through logs.
package/llms.txt CHANGED
@@ -8,7 +8,7 @@
8
8
  > deposit normally carries a small bridge/source-chain fee on top.
9
9
 
10
10
  This file teaches the whole flow to any agent or script. Repo (scripts, safety rails,
11
- full docs): https://github.com/RozoAI/rozo-checkout-skill — see QUICKSTART.md.
11
+ full docs): https://github.com/RozoAI/rozo-checkout-skill — see docs/QUICKSTART.md.
12
12
 
13
13
  ## Fastest path: the CLI (npm @rozoai/checkout)
14
14
 
@@ -21,11 +21,49 @@ calls with every safety rule already enforced:
21
21
 
22
22
  --with accepts usdt-solana, usdc-solana, usdt-bnb, usdc-bnb, usdt-ethereum,
23
23
  usdc-ethereum, usdt-polygon, usdc-polygon, usdc-base, usdc-stellar, btc-lightning
24
- (or raw: --chain 900 --token USDT). Flags: --send (pay from a hot wallet, keys from
25
- ROZO_CHECKOUT_EVM_KEY / ROZO_CHECKOUT_SOL_KEY, never from argv), --yes (skip the
26
- interactive confirm; required when stdin is not a TTY), --dry-run, --json (exactly
27
- one JSON object on stdout), --no-watch, --timeout <s>. Exit codes: 0 success,
28
- 1 refused/failed (read error.code), 2 usage, 3 submitted but unconfirmed.
24
+ (or raw: --chain 900 --token USDT).
25
+
26
+ DEFAULT PATH NEEDS NO KEY: `pay` prints a deposit address for the user to pay from
27
+ any wallet. No private key, no environment variable, no configuration. Only the
28
+ optional --send flag signs locally. Its key comes from the first source that
29
+ exists: --keyfile <path>; then ~/.config/solana/id.json (the keypair
30
+ solana-keygen already wrote) for Solana or ROZO_CHECKOUT_EVM_KEYSTORE (an
31
+ encrypted V3 keystore, passphrase prompted, never argv) for EVM; then a raw key
32
+ in ROZO_CHECKOUT_SOL_KEY / ROZO_CHECKOUT_EVM_KEY, which is the unattended-
33
+ automation option. Those settings may also come from a .env in the working
34
+ directory (or --env-file <path>): only ROZO_CHECKOUT_* keys are read from it, it
35
+ is parsed as text and never shell-evaluated, and the real environment wins.
36
+ Key files and .env must be chmod 600 and untracked by git.
37
+
38
+ ## Local wallet setup for --send (not needed for the default keyless path)
39
+
40
+ .env keys (only ROZO_CHECKOUT_* are read; real env wins; chmod 600; gitignore):
41
+ ROZO_CHECKOUT_SOL_KEY base58 string OR JSON byte array
42
+ ROZO_CHECKOUT_EVM_KEY 64 hex chars, 0x prefix optional
43
+ ROZO_CHECKOUT_EVM_KEYSTORE path to an encrypted V3 keystore
44
+ ROZO_CHECKOUT_KEYSTORE_PASSPHRASE unattended runs only; else prompted
45
+ ROZO_CHECKOUT_RPC_<chainId> e.g. ROZO_CHECKOUT_RPC_8453 (Base),
46
+ ROZO_CHECKOUT_RPC_900 (Solana)
47
+
48
+ Where the material comes from:
49
+ - Solana: `solana-keygen new` -> ~/.config/solana/id.json, found automatically
50
+ (preferred). Phantom exports base58; Solflare exports base58 (current) or a
51
+ JSON byte array (older). Both accepted.
52
+ - EVM: MetaMask/Rabby export 64 hex chars with no 0x prefix; paste as-is, the
53
+ prefix is optional. For an encrypted keystore, Foundry's
54
+ `cast wallet import <name> --interactive` writes a V3 keystore to
55
+ ~/.foundry/keystores/<name>; `geth account import` also writes one. Browser
56
+ wallets do not export keystores, only raw keys.
57
+ - Hardware wallets, WalletConnect-only wallets and exchange accounts CANNOT be
58
+ used with --send. Those users take the default keyless path, which works with
59
+ any wallet.
60
+ With --send, one limit applies: a single payment may not exceed $1,100, with no
61
+ override; larger invoices go through the keyless path.
62
+
63
+ Other flags: --yes (skip the interactive confirm; required when stdin is not a
64
+ TTY), --dry-run, --json (exactly one JSON object on stdout), --no-watch,
65
+ --timeout <s>. Exit codes: 0 success, 1 refused/failed (read error.code),
66
+ 2 usage, 3 submitted but unconfirmed.
29
67
 
30
68
  `pay` withholds the full deposit address until you confirm, then prints it as a
31
69
  copy-pastable block. Creating an order moves no money; an unfunded order expires.
@@ -38,6 +76,12 @@ chainId "1" Ethereum (USDC, USDT) · "56" BNB Chain (USDC, USDT; 18-decimals) ·
38
76
  amounts in integer satoshis via source.amountUnit=="sats").
39
77
  Native SOL/ETH/BNB are NOT supported — stablecoins (or Lightning BTC) only.
40
78
 
79
+ WALLET: one wallet on one of these chains is enough — not one per chain. Any
80
+ wallet, or an exchange withdrawal, can fund the deposit address; nothing
81
+ connects to a site. Stellar is the exception that must be able to set a memo,
82
+ and Lightning pays a BOLT11 invoice rather than an address. Only --send needs a
83
+ private key (EVM and Solana only); every other path is keyless.
84
+
41
85
  ## Flow (4 HTTP calls, all public)
42
86
 
43
87
  1) Quote (read-only, safe):
@@ -62,7 +106,9 @@ Native SOL/ETH/BNB are NOT supported — stablecoins (or Lightning BTC) only.
62
106
  -> { status, expiresAt, source:{ receiverAddress, receiverMemo, amount, amountUnit,
63
107
  lnInvoice, txHash, amountReceived, confirmedAt, ... }, destination:{...} }
64
108
  Pay EXACTLY source.amount of the requested token on the requested chain to
65
- source.receiverAddress (include receiverMemo when present — Stellar requires it).
109
+ source.receiverAddress, including every other field the block carries (Stellar
110
+ routes on a shared hub address plus source.receiverMemo, so omitting the memo
111
+ loses the payment).
66
112
  For Lightning, pay the BOLT11 in source.lnInvoice (receiverAddress is empty).
67
113
 
68
114
  4) Poll until settled:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rozoai/checkout",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
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",
@@ -28,15 +28,10 @@
28
28
  },
29
29
  "files": [
30
30
  "scripts/dist",
31
+ "docs/*.md",
32
+ "!docs/reviews",
31
33
  "llms.txt",
32
34
  "README.md",
33
- "README.zh.md",
34
- "README.ja.md",
35
- "README.es.md",
36
- "QUICKSTART.md",
37
- "QUICKSTART.zh.md",
38
- "QUICKSTART.ja.md",
39
- "QUICKSTART.es.md",
40
35
  "LICENSE"
41
36
  ],
42
37
  "engines": {