@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 +84 -0
- package/dist/index.cjs +689 -112
- package/dist/index.d.cts +325 -39
- package/dist/index.d.ts +325 -39
- package/dist/index.js +636 -59
- package/package.json +1 -1
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.
|