@piprail/sdk 2.9.0 → 2.10.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 +62 -0
- package/README.md +2 -1
- package/dist/chunk-3SBTJKAG.js +92 -0
- package/dist/chunk-CQQI5IJX.cjs +92 -0
- package/dist/index.cjs +1485 -174
- package/dist/index.d.cts +717 -13
- package/dist/index.d.ts +717 -13
- package/dist/index.js +1353 -42
- package/dist/{ledger-BtzrfO-3.d.cts → ledger-OQos-tmj.d.cts} +276 -9
- package/dist/{ledger-BtzrfO-3.d.ts → ledger-OQos-tmj.d.ts} +276 -9
- package/dist/node.d.cts +2 -2
- package/dist/node.d.ts +2 -2
- package/dist/receipt-NNEID77X.js +14 -0
- package/dist/receipt-QCDPMWNF.cjs +14 -0
- package/dist/{stellar-BEMT7UYF.js → stellar-ASP2THL2.js} +1 -1
- package/dist/{stellar-SUKASK4N.cjs → stellar-CRWBSX4E.cjs} +1 -1
- package/dist/{sui-F5JQ2N6I.js → sui-FZIKZNVI.js} +13 -3
- package/dist/{sui-VE5LT7BL.cjs → sui-Y4RLKKE2.cjs} +13 -3
- package/dist/{tron-ZZZS3FNN.js → tron-BMCWN5SS.js} +7 -1
- package/dist/{tron-EMFXDFHW.cjs → tron-OMXB6EW2.cjs} +7 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,68 @@ 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
|
+
## [2.10.0] — 2026-06-20 — x402 parity: verifiable receipts · the `upto` metered rail · A2A transport
|
|
8
|
+
|
|
9
|
+
Four ratified-x402 capabilities, all **additive and opt-in** — omit the new options and the 402, the
|
|
10
|
+
200, and the pay path are byte-identical to 2.9.0.
|
|
11
|
+
|
|
12
|
+
- **feat(receipts): verifiable receipts — chain-grounded (no key) + optional EIP-712 attestation.**
|
|
13
|
+
`receipts: true` on a gate emits a self-contained `PipRailReceipt` on every settled payment, in a
|
|
14
|
+
byte-compatible `extensions['offer-receipt'].info` block. **Anyone** re-verifies it against the chain
|
|
15
|
+
with only an RPC: `PipRailClient.verifyReceipt(receipt)` re-reads the settlement tx and re-derives
|
|
16
|
+
`payTo`/`asset`/`payer`, **ignoring the receipt's claims** (never throws; `amount` is a verified lower
|
|
17
|
+
bound; `payer` genuinely re-derived). `client.lastReceipt()` captures it after a paid `fetch`. The new
|
|
18
|
+
additive wire field is `X402Receipt.nonce?` (required to re-verify the five memo-bound families:
|
|
19
|
+
Stellar/XRPL/NEAR/Algorand/TON). **Tier 2 (EVM-only):** `receipts: { attest: { wallet } }` also signs
|
|
20
|
+
the official x402 offer-receipt EIP-712 `RECEIPT_TYPES` with the merchant's existing `payTo` wallet —
|
|
21
|
+
attesting the one thing the chain can't (that the resource was *served*); verify with
|
|
22
|
+
`PipRailClient.verifyAttestation`. `includeTxHash` defaults **true** (PipRail-default verifiability — a
|
|
23
|
+
deliberate, documented divergence from the reference privacy-default; flip to `false` for the §5.3
|
|
24
|
+
empty-string privacy path). New exports: `buildReceiptExtension`, `parseReceiptExtension`,
|
|
25
|
+
`PipRailReceipt`, `SignedReceipt`, `ReceiptInput`, `ReceiptOption`, `ReceiptVerification`.
|
|
26
|
+
- **feat(upto): the ratified `upto` (metered / variable-amount) rail — EVM-Permit2, self-settle.** The
|
|
27
|
+
buyer signs a Permit2 authorization for a **maximum**; the merchant serves, meters, then self-settles
|
|
28
|
+
the **actual** (`≤ max`) from its own relayer through the on-chain `x402UptoPermit2Proxy` — backendless,
|
|
29
|
+
no fee. Opt in with `upto: { relayer, settleAmount }` and meter inside `settleAmount` on a direct
|
|
30
|
+
`gate.verify()` call (`requirePayment` throws for `upto` — it settles before the handler serves). A
|
|
31
|
+
zero charge settles nothing on-chain. New `X402UptoAcceptEntry` / `Permit2UptoAuthorization` types +
|
|
32
|
+
`parseUptoPaymentHeader` / `buildUptoSignatureHeader` codecs.
|
|
33
|
+
- **feat(transport): A2A — `gate.verifyObject()` + the A2A seller handler.** `gate.verifyObject(payload)`
|
|
34
|
+
verifies a raw-JSON payment object (not just a base64 HTTP header), and `createA2APaymentHandler(gate)`
|
|
35
|
+
maps a gate onto Google's A2A JSON-RPC Task/Message metadata — **sharing one replay set with HTTP**.
|
|
36
|
+
Backendless, zero `@a2a` dependency. The parser cores `parseSignatureObject`/`parseExactObject` are now
|
|
37
|
+
exported. *(The A2A buyer, AP2 carriage, and the live-Google-interop emit-version finalization trail.)*
|
|
38
|
+
- **feat(agent/mcp): an 8th tool — `piprail_verify_receipt`** (read-only, key-less) re-verifies a receipt
|
|
39
|
+
against the chain; `piprail_pay_request` now surfaces the `verifiableReceipt`.
|
|
40
|
+
- **fix:** a batch of correctness hardening from an adversarial multi-agent audit — require the replay
|
|
41
|
+
store's `isUsed`/`markUsed` as a pair (a lone one silently disabled double-spend protection); the read
|
|
42
|
+
methods (`planPayment`/`canAfford`/`estimateCost`) degrade instead of throwing on a malformed accept;
|
|
43
|
+
Sui coin pagination; Stellar custom-asset decimals; Tron native safe-integer; and more.
|
|
44
|
+
- **fix:** a second adversarial multi-agent conformance pass (vs the cloned x402 spec) hardened the new
|
|
45
|
+
surfaces — **upto cumulative caps are now merchant-proof:** the budget debits the authorized **MAX**
|
|
46
|
+
(a merchant that under-reports its settled `amount` can no longer loosen `maxTotal`/`maxTotalPerDenom`/
|
|
47
|
+
`windowTotal`); the metered actual is surfaced on `SpendRecord.settledBase`. The `upto` driver now
|
|
48
|
+
enforces **strict** `permitted.amount === advertised max` at verify time (an over-permit is rejected —
|
|
49
|
+
x402 `scheme_upto_evm` §Phase 3). `describe()` now projects the upto rail's mandatory
|
|
50
|
+
`extra.facilitatorAddress` (+ the exact rail's EIP-712 domain) so a discovered rail is reconstructable.
|
|
51
|
+
A2A conformance: a rejected proof re-challenges as `payment-rejected` (the spec status that pairs with
|
|
52
|
+
the retryable `input-required`, not the terminal `payment-failed`) carrying a failure receipt with
|
|
53
|
+
`network` + `transaction:''`. The agent guide + `paymentTools` JSDoc now document the upto rail and the
|
|
54
|
+
8th tool.
|
|
55
|
+
- **fix:** a third verification pass (regression-focused) caught two **doc copy-paste bugs** that would
|
|
56
|
+
brick a user's code — the `upto` seller + Tier-2 receipt examples used the pre-v2 `{ privateKey }`
|
|
57
|
+
wallet field (which throws `WrongFamilyError`) instead of the unified `{ key }`. Also: the A2A
|
|
58
|
+
terminal-`failed` receipt now carries the attempted `network`; the A2A `fulfill` doc/example returns a
|
|
59
|
+
structurally valid artifact (`{ name, parts }`); the MCP config docs + registry `server.json` now list
|
|
60
|
+
`upto`; and a **regression test** locks the merchant-proof cumulative leash (a sequence of
|
|
61
|
+
under-reporting `upto` payments is refused by `maxTotal`/`maxTotalPerDenom`/`windowTotal`).
|
|
62
|
+
- **fix:** a fourth, end-to-end pass (every flow traced hop-by-hop + a clean-room standards sign-off,
|
|
63
|
+
which came back **CONFORMANT on all four wire formats**, and a live MCP-pays-the-`upto`-rail proof)
|
|
64
|
+
found only doc-accuracy gaps + one low edge case: A2A failure receipts now attribute `network` from
|
|
65
|
+
the buyer's submitted payload (covering multi-network gates + v1-flat exact payloads), and the whole
|
|
66
|
+
docs-site is updated to the **8-tool** surface (the new `piprail_verify_receipt`) with the 2.10.0
|
|
67
|
+
receipts/upto/A2A APIs documented in the reference. No SDK behavior change beyond the A2A edge fix.
|
|
68
|
+
|
|
7
69
|
## [2.9.0] — 2026-06-19 — Cross-token grand total · payment-count caps · durable budget · richer spend observability
|
|
8
70
|
|
|
9
71
|
Spend controls grow up — a single budget across every token and chain, caps on the *number* of
|
package/README.md
CHANGED
|
@@ -74,8 +74,9 @@ The same app can **take** payments and **make** them. → [Making payments](http
|
|
|
74
74
|
| | |
|
|
75
75
|
|---|---|
|
|
76
76
|
| **[Getting started](https://docs.piprail.com/getting-started/introduction/)** | Install · quickstart · how it works |
|
|
77
|
-
| **[Accepting payments](https://docs.piprail.com/accepting-payments/require-payment-and-gate/)** | `requirePayment` · `createPaymentGate` · the `exact` rail |
|
|
77
|
+
| **[Accepting payments](https://docs.piprail.com/accepting-payments/require-payment-and-gate/)** | `requirePayment` · `createPaymentGate` · the `exact` rail · the `upto` metered rail |
|
|
78
78
|
| **[Making payments](https://docs.piprail.com/making-payments/piprail-client/)** | `PipRailClient` · `quote` · `estimateCost` · `planPayment` · auto-route · `MultiChainPayer` |
|
|
79
|
+
| **[Verifiable receipts](https://docs.piprail.com/accepting-payments/verifiable-receipts/)** | Chain-grounded, anyone-verifiable receipts (no key) · optional EIP-712 attestation |
|
|
79
80
|
| **[Spend controls](https://docs.piprail.com/spend-controls/payment-policy/)** | Per-token + cross-token grand total · payment-count caps · time envelope · durable budget · the spend ledger |
|
|
80
81
|
| **[Agent toolkit](https://docs.piprail.com/agent-toolkit/payment-tools/)** | `paymentTools` · the agent guide · NL renderers |
|
|
81
82
|
| **[Discovery](https://docs.piprail.com/discovery/discover-and-register/)** | Find & be found on the open x402 indexes ($0, no backend) |
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// src/drivers/evm/receipt.ts
|
|
2
|
+
import {
|
|
3
|
+
recoverTypedDataAddress,
|
|
4
|
+
getAddress
|
|
5
|
+
} from "viem";
|
|
6
|
+
var RECEIPT_DOMAIN = Object.freeze({
|
|
7
|
+
name: "x402 receipt",
|
|
8
|
+
version: "1",
|
|
9
|
+
chainId: 1
|
|
10
|
+
});
|
|
11
|
+
var RECEIPT_TYPES = Object.freeze({
|
|
12
|
+
Receipt: [
|
|
13
|
+
{ name: "version", type: "uint256" },
|
|
14
|
+
{ name: "network", type: "string" },
|
|
15
|
+
{ name: "resourceUrl", type: "string" },
|
|
16
|
+
{ name: "payer", type: "string" },
|
|
17
|
+
{ name: "issuedAt", type: "uint256" },
|
|
18
|
+
{ name: "transaction", type: "string" }
|
|
19
|
+
]
|
|
20
|
+
});
|
|
21
|
+
var RECEIPT_PRIMARY_TYPE = "Receipt";
|
|
22
|
+
function receiptMessage(input) {
|
|
23
|
+
return {
|
|
24
|
+
version: 1n,
|
|
25
|
+
network: input.network,
|
|
26
|
+
resourceUrl: input.resourceUrl,
|
|
27
|
+
payer: input.payer,
|
|
28
|
+
issuedAt: BigInt(input.issuedAt),
|
|
29
|
+
// §5.3: unused optional fields are the empty string in the SIGNED message, never omitted.
|
|
30
|
+
transaction: input.transaction ?? ""
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
async function signReceiptEvm(wallet, input) {
|
|
34
|
+
const adapter = wallet._native;
|
|
35
|
+
const account = adapter.account;
|
|
36
|
+
const walletClient = adapter.walletClient;
|
|
37
|
+
const signature = await walletClient.signTypedData({
|
|
38
|
+
account,
|
|
39
|
+
domain: RECEIPT_DOMAIN,
|
|
40
|
+
types: RECEIPT_TYPES,
|
|
41
|
+
primaryType: RECEIPT_PRIMARY_TYPE,
|
|
42
|
+
message: receiptMessage(input)
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
format: "eip712",
|
|
46
|
+
signature,
|
|
47
|
+
// The address that actually produced the signature (the bound wallet). A
|
|
48
|
+
// verifier re-recovers + checks it equals the trusted `payTo`.
|
|
49
|
+
signer: account.address,
|
|
50
|
+
payload: {
|
|
51
|
+
version: 1,
|
|
52
|
+
network: input.network,
|
|
53
|
+
resourceUrl: input.resourceUrl,
|
|
54
|
+
payer: input.payer,
|
|
55
|
+
issuedAt: input.issuedAt,
|
|
56
|
+
transaction: input.transaction ?? ""
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
async function verifyReceiptAttestationEvm(input) {
|
|
61
|
+
try {
|
|
62
|
+
const recovered = await recoverTypedDataAddress({
|
|
63
|
+
domain: RECEIPT_DOMAIN,
|
|
64
|
+
types: RECEIPT_TYPES,
|
|
65
|
+
primaryType: RECEIPT_PRIMARY_TYPE,
|
|
66
|
+
message: receiptMessage(input),
|
|
67
|
+
signature: input.signature
|
|
68
|
+
});
|
|
69
|
+
let expected;
|
|
70
|
+
let actual;
|
|
71
|
+
try {
|
|
72
|
+
expected = getAddress(input.payTo);
|
|
73
|
+
actual = getAddress(recovered);
|
|
74
|
+
} catch {
|
|
75
|
+
return { ok: false, signer: recovered, reason: "bad-address" };
|
|
76
|
+
}
|
|
77
|
+
if (actual !== expected) {
|
|
78
|
+
return { ok: false, signer: recovered, reason: "signer-mismatch" };
|
|
79
|
+
}
|
|
80
|
+
return { ok: true, signer: recovered };
|
|
81
|
+
} catch {
|
|
82
|
+
return { ok: false, reason: "invalid-signature" };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export {
|
|
87
|
+
RECEIPT_DOMAIN,
|
|
88
|
+
RECEIPT_TYPES,
|
|
89
|
+
RECEIPT_PRIMARY_TYPE,
|
|
90
|
+
signReceiptEvm,
|
|
91
|
+
verifyReceiptAttestationEvm
|
|
92
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }// src/drivers/evm/receipt.ts
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
var _viem = require('viem');
|
|
6
|
+
var RECEIPT_DOMAIN = Object.freeze({
|
|
7
|
+
name: "x402 receipt",
|
|
8
|
+
version: "1",
|
|
9
|
+
chainId: 1
|
|
10
|
+
});
|
|
11
|
+
var RECEIPT_TYPES = Object.freeze({
|
|
12
|
+
Receipt: [
|
|
13
|
+
{ name: "version", type: "uint256" },
|
|
14
|
+
{ name: "network", type: "string" },
|
|
15
|
+
{ name: "resourceUrl", type: "string" },
|
|
16
|
+
{ name: "payer", type: "string" },
|
|
17
|
+
{ name: "issuedAt", type: "uint256" },
|
|
18
|
+
{ name: "transaction", type: "string" }
|
|
19
|
+
]
|
|
20
|
+
});
|
|
21
|
+
var RECEIPT_PRIMARY_TYPE = "Receipt";
|
|
22
|
+
function receiptMessage(input) {
|
|
23
|
+
return {
|
|
24
|
+
version: 1n,
|
|
25
|
+
network: input.network,
|
|
26
|
+
resourceUrl: input.resourceUrl,
|
|
27
|
+
payer: input.payer,
|
|
28
|
+
issuedAt: BigInt(input.issuedAt),
|
|
29
|
+
// §5.3: unused optional fields are the empty string in the SIGNED message, never omitted.
|
|
30
|
+
transaction: _nullishCoalesce(input.transaction, () => ( ""))
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
async function signReceiptEvm(wallet, input) {
|
|
34
|
+
const adapter = wallet._native;
|
|
35
|
+
const account = adapter.account;
|
|
36
|
+
const walletClient = adapter.walletClient;
|
|
37
|
+
const signature = await walletClient.signTypedData({
|
|
38
|
+
account,
|
|
39
|
+
domain: RECEIPT_DOMAIN,
|
|
40
|
+
types: RECEIPT_TYPES,
|
|
41
|
+
primaryType: RECEIPT_PRIMARY_TYPE,
|
|
42
|
+
message: receiptMessage(input)
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
format: "eip712",
|
|
46
|
+
signature,
|
|
47
|
+
// The address that actually produced the signature (the bound wallet). A
|
|
48
|
+
// verifier re-recovers + checks it equals the trusted `payTo`.
|
|
49
|
+
signer: account.address,
|
|
50
|
+
payload: {
|
|
51
|
+
version: 1,
|
|
52
|
+
network: input.network,
|
|
53
|
+
resourceUrl: input.resourceUrl,
|
|
54
|
+
payer: input.payer,
|
|
55
|
+
issuedAt: input.issuedAt,
|
|
56
|
+
transaction: _nullishCoalesce(input.transaction, () => ( ""))
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
async function verifyReceiptAttestationEvm(input) {
|
|
61
|
+
try {
|
|
62
|
+
const recovered = await _viem.recoverTypedDataAddress.call(void 0, {
|
|
63
|
+
domain: RECEIPT_DOMAIN,
|
|
64
|
+
types: RECEIPT_TYPES,
|
|
65
|
+
primaryType: RECEIPT_PRIMARY_TYPE,
|
|
66
|
+
message: receiptMessage(input),
|
|
67
|
+
signature: input.signature
|
|
68
|
+
});
|
|
69
|
+
let expected;
|
|
70
|
+
let actual;
|
|
71
|
+
try {
|
|
72
|
+
expected = _viem.getAddress.call(void 0, input.payTo);
|
|
73
|
+
actual = _viem.getAddress.call(void 0, recovered);
|
|
74
|
+
} catch (e) {
|
|
75
|
+
return { ok: false, signer: recovered, reason: "bad-address" };
|
|
76
|
+
}
|
|
77
|
+
if (actual !== expected) {
|
|
78
|
+
return { ok: false, signer: recovered, reason: "signer-mismatch" };
|
|
79
|
+
}
|
|
80
|
+
return { ok: true, signer: recovered };
|
|
81
|
+
} catch (e2) {
|
|
82
|
+
return { ok: false, reason: "invalid-signature" };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
exports.RECEIPT_DOMAIN = RECEIPT_DOMAIN; exports.RECEIPT_TYPES = RECEIPT_TYPES; exports.RECEIPT_PRIMARY_TYPE = RECEIPT_PRIMARY_TYPE; exports.signReceiptEvm = signReceiptEvm; exports.verifyReceiptAttestationEvm = verifyReceiptAttestationEvm;
|