@piprail/sdk 1.15.1 → 1.17.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/CHANGELOG.md CHANGED
@@ -4,6 +4,90 @@ All notable changes to `@piprail/sdk` are documented here. The format
4
4
  follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the
5
5
  versions follow [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [1.17.0] — 2026-06-11 — `onPaid` hardening: enriched, isolated, durable receipts
8
+
9
+ A minor, fully additive release — defaults byte-identical (fire-and-forget stays the default,
10
+ the wire `X402Receipt` is unchanged), the protocol layer stays viem-free, and the EVM bundle
11
+ pulls in **no new dependency** (`deliverReceipt` is global `fetch` + Web Crypto).
12
+
13
+ ### Fixed — an `async onPaid` could crash the process
14
+ - The gate's `onPaid` isolation only caught **synchronous** throws; an `async` handler that
15
+ rejected (the common case — a DB/queue/webhook write) escaped as an `unhandledRejection` that
16
+ could crash the process. `fireOnPaid` now isolates a **rejected promise as well as a sync
17
+ throw**, routing both to the new `onPaidError` seam. The "a hook can never break the request"
18
+ guarantee is now true for async handlers too.
19
+
20
+ ### Added — the enriched `PaidReceipt` (what `onPaid` now receives)
21
+ - `onPaid` (and the new `onPaidError`) receive a `PaidReceipt`: every `X402Receipt` field **plus**
22
+ `decimals`, `symbol`, `amountFormatted` (formatted from the *settled* amount), and a stable
23
+ `idempotencyKey` (= the settled tx id) — so a receipt handler never needs a second lookup. The
24
+ wire receipt (`result.receipt`, the response header) stays the lean settlement record.
25
+
26
+ ### Added — receipt-hook options on `requirePayment` / `createPaymentGate`
27
+ - `onPaid?: (receipt: PaidReceipt) => void | Promise<void>` — now explicitly **sync or async**.
28
+ - `onPaidError?: (err, receipt) => void` — observe a failing hook instead of swallowing it
29
+ silently (its own throws are isolated too).
30
+ - `awaitOnPaid?: boolean` (default `false`) — await the hook before serving the resource, so
31
+ "receipt recorded" is guaranteed on the happy path. A rejection is still isolated; it never
32
+ turns a settled payment into a 402.
33
+
34
+ ### Added — `deliverReceipt()`, a reliable self-hosted webhook primitive
35
+ - `deliverReceipt(receipt, { url, secret, retries, timeoutMs, backoff, headers, onAttempt })`
36
+ POSTs a `PaidReceipt` to **your own** endpoint with retries + exponential backoff, an
37
+ **HMAC-SHA256** signature (`piprail-signature: sha256=…`), and an `idempotency-key` header. It
38
+ **never throws** (failure → `{ delivered: false, … }`), retries `408`/`429`/`5xx`/transport
39
+ errors, and stops on a permanent `4xx`. Isomorphic (global `fetch` + Web Crypto), zero new deps.
40
+ PipRail hosts nothing — the URL is yours. New exports: `deliverReceipt`, `DeliverReceiptOptions`,
41
+ `DeliverAttempt`, `DeliverResult`, and the `PaidReceipt` type.
42
+
43
+ ### Delivery contract (documented)
44
+ - `onPaid` is **at-least-once**: exactly once per proof on a single in-memory replay store, but
45
+ across instances sharing a custom `isUsed`/`markUsed` store a race can deliver twice — **dedupe
46
+ on `idempotencyKey`**. Covered on the Receipts & onPaid docs page with queue + webhook patterns.
47
+
48
+ ### Tests
49
+ - +22: `test/server-onpaid.test.ts` (enrichment, sync+async isolation, no-`unhandledRejection`,
50
+ `awaitOnPaid` ordering, fire-once-per-settlement) and `test/receipts.test.ts` (retries, backoff,
51
+ permanent-vs-retryable status, HMAC signature verification, idempotency + header precedence,
52
+ timeout/abort, never-throws).
53
+
54
+ ## [1.16.0] — 2026-06-11 — x402 `exact` Permit2 method: BNB Chain is a first-class exact rail
55
+
56
+ A minor, fully additive feature — defaults byte-identical (`exact` stays opt-in), the protocol
57
+ layer stays viem-free, and the EVM bundle pulls in **no new dependency** (Permit2 is EIP-712
58
+ signing + one `approve` on the existing `viem` peer; the lazy-chunk invariant still holds).
59
+
60
+ ### Added — the `permit2` asset-transfer method of the x402 `exact` scheme (EVM)
61
+ - The `exact` scheme now settles tokens **without** EIP-3009 — most importantly **Binance-Peg
62
+ USDC/USDT on BNB Chain** (no native Circle USDC exists on BNB). Per the x402 spec
63
+ (`specs/schemes/exact/scheme_exact_evm.md`): the buyer signs a Permit2 `PermitWitnessTransferFrom`
64
+ whose `spender` is the canonical **x402ExactPermit2Proxy** (`0x402085…20001`) and whose
65
+ `witness.to` binds the recipient; the merchant/relayer self-settles via the proxy's `settle`.
66
+ - **Buyer** — `PipRailClient({ schemes: ['exact'] })` auto-detects `extra.assetTransferMethod` and
67
+ signs EIP-3009 or Permit2 accordingly. The one-time `approve(Permit2)` is done lazily on first use
68
+ (the only on-chain action the buyer takes; gas-free thereafter); `estimateCost` notes it.
69
+ - **Seller** — `requirePayment({ exact: { settle: 'self', relayer } })` **auto-selects** the method:
70
+ EIP-3009 when the token supports it, else Permit2 (any ERC-20). New `exact.method?: 'eip3009' |
71
+ 'permit2' | 'auto'` (default `'auto'`) pins it. The advertised rail carries
72
+ `extra.assetTransferMethod`; Permit2 replay uses the Permit2 nonce bitmap.
73
+ - New public exports: `PERMIT2_ADDRESS`, `X402_EXACT_PERMIT2_PROXY`, `PERMIT2_WITNESS_TYPES`, and the
74
+ wire types `Permit2Authorization` / `Permit2PaymentPayload` / `ExactPaymentPayloadAny`.
75
+ `ParsedExactPayment` is now a discriminated union on `method` (`'eip3009' | 'permit2'`). BNB slugs
76
+ added to `EXACT_NETWORK_SLUGS`.
77
+ - **FDUSD + USD1 are now default BNB tokens.** Both **are EIP-3009** (unlike Binance-Peg USDC/USDT),
78
+ so the `exact` rail uses the **gasless `transferWithAuthorization` path — no Permit2 approve**.
79
+ They hardcode their EIP-712 domain version (`"1"`) without a `version()` function, so
80
+ `readExactDomain` now **derives the version from the on-chain `DOMAIN_SEPARATOR`** (generalizes to
81
+ any `version()`-less EIP-3009 token). 18-decimal; verified on-chain.
82
+
83
+ ### Verified
84
+ - **Live-proven on BNB mainnet** (real USDC, both rails — 402 → pay → 200, balance moved, replay
85
+ rejected): onchain-proof tx `0x4bf044b554e5d1390b5c0fb225bad7501c4fa1e3538005aed144ad153d30eb14`;
86
+ exact/Permit2 self-settle tx `0x6e3ecc3f3230d6e1627db5c233a102dd1878e46bab676302a84f78f30be61589`.
87
+ - **FDUSD + USD1 live-proven on BNB mainnet** via the gasless EIP-3009 `exact` rail (domain version
88
+ derived on-chain, replay rejected): FDUSD tx `0xfaec2e82a294790322a24db65458abbe4913a493e81dd66accfcf7a8be5dbfda`;
89
+ USD1 tx `0x10e68722375943a183edd749b67acf05a75baa98680a31b06af804d56a160c28`.
90
+
7
91
  ## [1.15.1] — 2026-06-10 — docs consolidation: the README is now a signpost to docs.piprail.com
8
92
 
9
93
  Docs-only. No code, no API, no behaviour change — `dist` is byte-identical.