@odatano/x402 0.2.0 → 0.3.1

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.
Files changed (62) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +5 -0
  3. package/cds-plugin.js +2 -0
  4. package/db/x402-grants.cds +49 -0
  5. package/db/x402-receipts.cds +44 -0
  6. package/package.json +4 -3
  7. package/srv/bridge.d.ts +9 -12
  8. package/srv/bridge.js +10 -13
  9. package/srv/client/axios.d.ts +1 -1
  10. package/srv/client/axios.js +45 -8
  11. package/srv/client/envelope.d.ts +1 -1
  12. package/srv/client/envelope.js +1 -1
  13. package/srv/client/errors.d.ts +86 -0
  14. package/srv/client/errors.js +107 -0
  15. package/srv/client/fetch.d.ts +2 -2
  16. package/srv/client/fetch.js +71 -11
  17. package/srv/client/pay-handlers.d.ts +4 -4
  18. package/srv/client/pay-handlers.js +3 -3
  19. package/srv/client/types.d.ts +19 -7
  20. package/srv/client/types.js +3 -3
  21. package/srv/core/asset.d.ts +1 -1
  22. package/srv/core/decode.d.ts +2 -2
  23. package/srv/core/decode.js +5 -5
  24. package/srv/core/errors.js +3 -3
  25. package/srv/core/network.d.ts +1 -1
  26. package/srv/core/network.js +1 -1
  27. package/srv/core/requirements.d.ts +37 -5
  28. package/srv/core/requirements.js +43 -4
  29. package/srv/core/types.d.ts +68 -6
  30. package/srv/core/types.js +3 -3
  31. package/srv/core/validate.d.ts +31 -7
  32. package/srv/core/validate.js +84 -9
  33. package/srv/facilitator/adapter.d.ts +8 -8
  34. package/srv/facilitator/adapter.js +5 -5
  35. package/srv/facilitator/http.d.ts +4 -4
  36. package/srv/facilitator/http.js +5 -5
  37. package/srv/facilitator/nonce.d.ts +4 -4
  38. package/srv/facilitator/nonce.js +4 -4
  39. package/srv/facilitator/server.d.ts +68 -0
  40. package/srv/facilitator/server.js +167 -0
  41. package/srv/facilitator/settle.d.ts +2 -2
  42. package/srv/facilitator/settle.js +4 -4
  43. package/srv/facilitator/verify.d.ts +5 -5
  44. package/srv/facilitator/verify.js +19 -5
  45. package/srv/helpers/build-unsigned-tx.d.ts +5 -5
  46. package/srv/helpers/build-unsigned-tx.js +3 -3
  47. package/srv/helpers/verify-confirmed.d.ts +1 -1
  48. package/srv/helpers/verify-confirmed.js +1 -1
  49. package/srv/index.d.ts +4 -2
  50. package/srv/index.js +9 -3
  51. package/srv/middleware/cap.d.ts +47 -9
  52. package/srv/middleware/cap.js +111 -43
  53. package/srv/middleware/express.d.ts +15 -10
  54. package/srv/middleware/express.js +18 -19
  55. package/srv/middleware/grants.d.ts +64 -0
  56. package/srv/middleware/grants.js +113 -0
  57. package/srv/middleware/pricing.d.ts +41 -0
  58. package/srv/middleware/pricing.js +78 -0
  59. package/srv/middleware/receipts.d.ts +38 -0
  60. package/srv/middleware/receipts.js +68 -0
  61. package/srv/plugin.d.ts +2 -2
  62. package/srv/plugin.js +2 -2
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * `x402Fetch` drop-in fetch wrapper that auto-handles 402 responses.
3
+ * `x402Fetch`, drop-in fetch wrapper that auto-handles 402 responses.
4
4
  *
5
5
  * On a 402 response the wrapper:
6
6
  * 1. Parses the body as a v2 `PaymentRequirementsBody`.
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * Non-402 responses are passed through untouched. After `maxRetries`
13
13
  * payment attempts, the last response (whether 402 or other) is
14
- * returned to the caller never an infinite loop.
14
+ * returned to the caller, never an infinite loop.
15
15
  *
16
16
  * Native fetch is used by default (Node ≥18, all modern browsers).
17
17
  * Pass `opts.fetch` to override (testing, custom agents, etc.).
@@ -19,6 +19,7 @@
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
20
  exports.x402Fetch = x402Fetch;
21
21
  const envelope_1 = require("./envelope");
22
+ const errors_1 = require("./errors");
22
23
  /**
23
24
  * Wrap `fetch` with 402-handling. The returned function has the same
24
25
  * signature as native fetch, so it's a drop-in replacement.
@@ -37,34 +38,93 @@ function x402Fetch(opts) {
37
38
  return async function paidFetch(input, init) {
38
39
  let attemptsLeft = maxRetries;
39
40
  // Contextual typing from FetchFn means we don't need to spell out
40
- // RequestInfo / RequestInit explicitly those are DOM-only globals.
41
+ // RequestInfo / RequestInit explicitly, those are DOM-only globals.
41
42
  let nextInit = init;
43
+ // Remember the last parsed 402 body so retries_exhausted carries
44
+ // the same diagnostic shape as a fresh server_rejected.
45
+ let lastBody;
42
46
  // Loop: original request + up-to-maxRetries payment retries.
43
47
  // eslint-disable-next-line no-constant-condition
44
48
  while (true) {
45
49
  const res = await baseFetch(input, nextInit);
46
- if (res.status !== 402 || attemptsLeft <= 0)
50
+ if (res.status !== 402)
47
51
  return res;
52
+ if (attemptsLeft <= 0) {
53
+ if (opts.errorOnFailure) {
54
+ // Use the last successfully-parsed 402 body if we have one
55
+ // (we typically do, since the prior attempt parsed it). If
56
+ // not, parse one more time to give the caller a useful error.
57
+ let body = lastBody;
58
+ if (!body) {
59
+ try {
60
+ body = await res.clone().json();
61
+ }
62
+ catch { /* fall through */ }
63
+ }
64
+ if (body) {
65
+ throw (0, errors_1.paymentErrorFromBody)(body, { kind: 'retries_exhausted', httpStatus: res.status });
66
+ }
67
+ throw new errors_1.X402PaymentError({
68
+ message: 'x402Fetch: retries exhausted with no parsable 402 body',
69
+ kind: 'retries_exhausted',
70
+ httpStatus: res.status,
71
+ });
72
+ }
73
+ return res;
74
+ }
48
75
  // Parse 402 body. If it's not a v2 PaymentRequirementsBody we
49
- // bail with the original response (caller's problem to handle).
76
+ // bail with the original response (or throw under errorOnFailure).
50
77
  let body;
51
78
  try {
52
- body = (await res.clone().json());
79
+ body = await res.clone().json();
53
80
  }
54
81
  catch {
82
+ if (opts.errorOnFailure) {
83
+ throw new errors_1.X402PaymentError({
84
+ message: 'x402Fetch: 402 body was not JSON',
85
+ kind: 'invalid_402_body',
86
+ httpStatus: res.status,
87
+ });
88
+ }
55
89
  return res;
56
90
  }
57
- if (body?.x402Version !== 2 || !Array.isArray(body.accepts) || body.accepts.length === 0) {
91
+ if (!body || body.x402Version !== 2 || !Array.isArray(body.accepts) || body.accepts.length === 0) {
92
+ if (opts.errorOnFailure) {
93
+ throw new errors_1.X402PaymentError({
94
+ message: 'x402Fetch: 402 body is not a v2 PaymentRequirementsBody',
95
+ kind: 'invalid_402_body',
96
+ httpStatus: res.status,
97
+ });
98
+ }
58
99
  return res;
59
100
  }
101
+ lastBody = body;
60
102
  const chosen = select(body.accepts);
61
- if (!chosen)
103
+ if (!chosen) {
104
+ if (opts.errorOnFailure) {
105
+ throw (0, errors_1.paymentErrorFromBody)(body, { kind: 'server_rejected', httpStatus: res.status });
106
+ }
62
107
  return res;
63
- const { signedTxCborHex, nonceRef } = await opts.pay(chosen);
108
+ }
109
+ // Invoke user's pay handler. Wallet rejection / signer errors /
110
+ // network blips here all surface as X402PaymentError(pay_handler_failed)
111
+ // with the original error on `.cause`, regardless of errorOnFailure.
112
+ let payResult;
113
+ try {
114
+ payResult = await opts.pay(chosen);
115
+ }
116
+ catch (err) {
117
+ throw new errors_1.X402PaymentError({
118
+ message: `x402Fetch: pay handler failed: ${err?.message ?? String(err)}`,
119
+ kind: 'pay_handler_failed',
120
+ accepts: body.accepts,
121
+ cause: err,
122
+ });
123
+ }
64
124
  const header = (0, envelope_1.encodePaymentEnvelope)({
65
125
  network: chosen.network,
66
- signedTxCborHex,
67
- nonceRef,
126
+ signedTxCborHex: payResult.signedTxCborHex,
127
+ nonceRef: payResult.nonceRef,
68
128
  });
69
129
  // Merge PAYMENT-SIGNATURE into headers without mutating caller's init.
70
130
  const mergedHeaders = new Headers(nextInit?.headers ?? init?.headers);
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * `createBridgePayHandler` is the default for server-to-server flows
5
5
  * (one CAP service calling another, AI-agent payments). It uses the
6
- * existing `buildUnsignedPaymentTx` helper which requires
7
- * `@odatano/core` bridge access at runtime and delegates signing
6
+ * existing `buildUnsignedPaymentTx` helper, which requires
7
+ * `@odatano/core` bridge access at runtime, and delegates signing
8
8
  * to a caller-supplied `signTx` callback.
9
9
  *
10
10
  * For browser CIP-30 wallets, write your own PayHandler: get UTxOs
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import type { PayHandler } from './types';
16
16
  export interface BridgePayHandlerOptions {
17
- /** Buyer bech32 used for UTxO lookup and change. */
17
+ /** Buyer bech32, used for UTxO lookup and change. */
18
18
  buyerBech32: string;
19
19
  /**
20
20
  * Sign the unsigned tx CBOR. Returns the SIGNED tx CBOR hex
@@ -34,7 +34,7 @@ export interface BridgePayHandlerOptions {
34
34
  * 2. caller-supplied `signTx` (signs the unsigned CBOR)
35
35
  * 3. returns `{ signedTxCborHex, nonceRef }`
36
36
  *
37
- * The signed tx is NOT submitted here the x402 server submits it
37
+ * The signed tx is NOT submitted here, the x402 server submits it
38
38
  * after validating the envelope (per Cardano-x402-v2 facilitator flow).
39
39
  */
40
40
  export declare function createBridgePayHandler(opts: BridgePayHandlerOptions): PayHandler;
@@ -4,8 +4,8 @@
4
4
  *
5
5
  * `createBridgePayHandler` is the default for server-to-server flows
6
6
  * (one CAP service calling another, AI-agent payments). It uses the
7
- * existing `buildUnsignedPaymentTx` helper which requires
8
- * `@odatano/core` bridge access at runtime and delegates signing
7
+ * existing `buildUnsignedPaymentTx` helper, which requires
8
+ * `@odatano/core` bridge access at runtime, and delegates signing
9
9
  * to a caller-supplied `signTx` callback.
10
10
  *
11
11
  * For browser CIP-30 wallets, write your own PayHandler: get UTxOs
@@ -22,7 +22,7 @@ const build_unsigned_tx_1 = require("../helpers/build-unsigned-tx");
22
22
  * 2. caller-supplied `signTx` (signs the unsigned CBOR)
23
23
  * 3. returns `{ signedTxCborHex, nonceRef }`
24
24
  *
25
- * The signed tx is NOT submitted here the x402 server submits it
25
+ * The signed tx is NOT submitted here, the x402 server submits it
26
26
  * after validating the envelope (per Cardano-x402-v2 facilitator flow).
27
27
  */
28
28
  function createBridgePayHandler(opts) {
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Client-side helper types symmetric to the server-side facilitator.
2
+ * Client-side helper types, symmetric to the server-side facilitator.
3
3
  *
4
4
  * A `PayHandler` is the one extension point: given the `accepts[]` entry
5
5
  * the user chose, it must produce a signed payment tx (CBOR hex) + the
6
- * v2 nonce reference. Everything else 402-detection, retry loop,
7
- * envelope encoding is generic and lives in `fetch.ts` / `axios.ts`.
6
+ * v2 nonce reference. Everything else, 402-detection, retry loop,
7
+ * envelope encoding, is generic and lives in `fetch.ts` / `axios.ts`.
8
8
  */
9
9
  import type { PaymentRequirementEntry } from '../core/types';
10
10
  /**
@@ -19,7 +19,7 @@ export interface PayHandlerResult {
19
19
  /** Hex of the **signed** payment-tx CBOR (vkey witness set populated). */
20
20
  signedTxCborHex: string;
21
21
  /**
22
- * v2 nonce reference `<txHash>#<outputIndex>` must point to an
22
+ * v2 nonce reference `<txHash>#<outputIndex>`, must point to an
23
23
  * unspent UTxO that ALSO appears as an input of the signed tx.
24
24
  * (Server enforces both at validate time.)
25
25
  */
@@ -28,17 +28,29 @@ export interface PayHandlerResult {
28
28
  /** Pick which `accepts[]` entry to satisfy. Default picks the first. */
29
29
  export type AcceptsSelector = (accepts: PaymentRequirementEntry[]) => PaymentRequirementEntry | undefined;
30
30
  export interface X402ClientOptions {
31
- /** Required how to produce the signed payment tx. */
31
+ /** Required, how to produce the signed payment tx. */
32
32
  pay: PayHandler;
33
33
  /**
34
- * Optional choose one of the `accepts[]` entries when the server
34
+ * Optional, choose one of the `accepts[]` entries when the server
35
35
  * offers multiple. Defaults to `accepts[0]`.
36
36
  */
37
37
  selectAccepts?: AcceptsSelector;
38
38
  /**
39
39
  * Maximum number of 402-driven payment retries per request. Default 1
40
- * i.e. one payment attempt per request, no infinite loops.
40
+ *, i.e. one payment attempt per request, no infinite loops.
41
41
  */
42
42
  maxRetries?: number;
43
+ /**
44
+ * When `true`, throw an `X402PaymentError` after retries are
45
+ * exhausted (or when the 402 body is malformed). Default `false`:
46
+ *
47
+ * - `x402Fetch` returns the last `Response` (the 402 itself).
48
+ * - `x402Axios` re-throws the original AxiosError.
49
+ *
50
+ * Pay-handler throws are ALWAYS wrapped in `X402PaymentError` (kind:
51
+ * `'pay_handler_failed'`) regardless of this flag, with the original
52
+ * error preserved on `.cause`.
53
+ */
54
+ errorOnFailure?: boolean;
43
55
  }
44
56
  //# sourceMappingURL=types.d.ts.map
@@ -1,10 +1,10 @@
1
1
  "use strict";
2
2
  /**
3
- * Client-side helper types symmetric to the server-side facilitator.
3
+ * Client-side helper types, symmetric to the server-side facilitator.
4
4
  *
5
5
  * A `PayHandler` is the one extension point: given the `accepts[]` entry
6
6
  * the user chose, it must produce a signed payment tx (CBOR hex) + the
7
- * v2 nonce reference. Everything else 402-detection, retry loop,
8
- * envelope encoding is generic and lives in `fetch.ts` / `axios.ts`.
7
+ * v2 nonce reference. Everything else, 402-detection, retry loop,
8
+ * envelope encoding, is generic and lives in `fetch.ts` / `axios.ts`.
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -21,7 +21,7 @@ export interface ParsedAsset {
21
21
  assetNameHex: string;
22
22
  /**
23
23
  * Concatenation used as the canonical UTxO `unit` key by Blockfrost /
24
- * Koios / ODATANO (`policyId + assetNameHex`). Empty for lovelace
24
+ * Koios / ODATANO (`policyId + assetNameHex`). Empty for lovelace ,
25
25
  * comparisons against UTxO assets short-circuit via `isLovelace`.
26
26
  */
27
27
  unit: string;
@@ -12,14 +12,14 @@
12
12
  * }
13
13
  * }))
14
14
  *
15
- * The decoder is **pure** no chain calls, no DB. It produces a
15
+ * The decoder is **pure**, no chain calls, no DB. It produces a
16
16
  * `DecodedPayment` that downstream `validate.ts` checks against
17
17
  * `PaymentRequirementEntry` (the 6 mandatory checks).
18
18
  */
19
19
  import type { DecodedPayment } from './types';
20
20
  /**
21
21
  * Decode a `PAYMENT-SIGNATURE` header value end-to-end. Throws X402Error
22
- * with a precise `code` on any malformed input the caller catches and
22
+ * with a precise `code` on any malformed input, the caller catches and
23
23
  * surfaces the code in the 402 response body.
24
24
  */
25
25
  export declare function decode(paymentHeader: string | undefined | null): DecodedPayment;
@@ -13,7 +13,7 @@
13
13
  * }
14
14
  * }))
15
15
  *
16
- * The decoder is **pure** no chain calls, no DB. It produces a
16
+ * The decoder is **pure**, no chain calls, no DB. It produces a
17
17
  * `DecodedPayment` that downstream `validate.ts` checks against
18
18
  * `PaymentRequirementEntry` (the 6 mandatory checks).
19
19
  */
@@ -59,7 +59,7 @@ const SUPPORTED_SCHEME = 'exact';
59
59
  const NONCE_RE = /^([0-9a-f]{64})#(\d+)$/i;
60
60
  function decodeBase64ToBuffer(s, errCode) {
61
61
  // Node's Buffer.from is lenient (silently drops bad chars). Re-encode
62
- // and compare modulo padding to catch malformed input early otherwise
62
+ // and compare modulo padding to catch malformed input early, otherwise
63
63
  // garbage in `transaction` would only fail at CBOR parse time with a
64
64
  // confusing error.
65
65
  const buf = Buffer.from(s, 'base64');
@@ -121,7 +121,7 @@ function extractInputs(txBody) {
121
121
  /**
122
122
  * Pull validity range bounds. CSL exposes `ttl()` (upper) since Shelley
123
123
  * and `validity_start_interval_bignum()` (lower) since Allegra. Both can
124
- * be absent in which case we return null and the TTL check is skipped
124
+ * be absent, in which case we return null and the TTL check is skipped
125
125
  * (per v2 spec: only validate TTL if buyer set one).
126
126
  */
127
127
  function extractValidityRange(txBody) {
@@ -166,7 +166,7 @@ function parseNonceRef(nonce) {
166
166
  }
167
167
  /**
168
168
  * Decode a `PAYMENT-SIGNATURE` header value end-to-end. Throws X402Error
169
- * with a precise `code` on any malformed input the caller catches and
169
+ * with a precise `code` on any malformed input, the caller catches and
170
170
  * surfaces the code in the 402 response body.
171
171
  */
172
172
  function decode(paymentHeader) {
@@ -201,7 +201,7 @@ function decode(paymentHeader) {
201
201
  throw new errors_1.X402Error(errors_1.Codes.MISSING_FIELD, 'payload.nonce is required (v2 UTxO-ref)');
202
202
  }
203
203
  // 3. Tx CBOR → CSL Transaction (parses both `transaction` and `fixed`
204
- // representation; we need both Transaction for body access,
204
+ // representation; we need both, Transaction for body access,
205
205
  // FixedTransaction for byte-stable hash).
206
206
  const txBuf = decodeBase64ToBuffer(payload.transaction, errors_1.Codes.INVALID_CBOR);
207
207
  let tx;
@@ -39,9 +39,9 @@ exports.Codes = Object.freeze({
39
39
  WRONG_RECIPIENT: 'wrong_recipient', // check 2
40
40
  INSUFFICIENT_AMOUNT: 'insufficient_amount', // check 3
41
41
  WRONG_ASSET: 'wrong_asset', // check 4
42
- REPLAY: 'replay_detected', // check 5 UTxO already spent
43
- NONCE_NOT_REFERENCED: 'nonce_not_referenced', // check 5 UTxO not in tx inputs
44
- EXPIRED_TTL: 'expired_ttl', // check 6 validity range upper bound passed
42
+ REPLAY: 'replay_detected', // check 5, UTxO already spent
43
+ NONCE_NOT_REFERENCED: 'nonce_not_referenced', // check 5, UTxO not in tx inputs
44
+ EXPIRED_TTL: 'expired_ttl', // check 6, validity range upper bound passed
45
45
  // ---- supporting ----
46
46
  UNSIGNED_TRANSACTION: 'unsigned_transaction', // sanity: no vkey witnesses
47
47
  // ---- settle ----
@@ -10,7 +10,7 @@ export type Network = 'cardano:mainnet' | 'cardano:preprod' | 'cardano:preview';
10
10
  export declare function isNetwork(s: unknown): s is Network;
11
11
  /**
12
12
  * Validate a network string and return it typed. Throws X402Error on
13
- * malformed input including v1-style hyphen variants so the caller's
13
+ * malformed input, including v1-style hyphen variants, so the caller's
14
14
  * 402 body carries a precise diagnostic.
15
15
  */
16
16
  export declare function parseNetwork(s: string): Network;
@@ -18,7 +18,7 @@ function isNetwork(s) {
18
18
  }
19
19
  /**
20
20
  * Validate a network string and return it typed. Throws X402Error on
21
- * malformed input including v1-style hyphen variants so the caller's
21
+ * malformed input, including v1-style hyphen variants, so the caller's
22
22
  * 402 body carries a precise diagnostic.
23
23
  */
24
24
  function parseNetwork(s) {
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Build the canonical Cardano-x402-v2 PaymentRequirements body.
3
3
  *
4
- * Asset-agnostic by design the consumer passes a v2 asset string
4
+ * Asset-agnostic by design, the consumer passes a v2 asset string
5
5
  * (`<policy>.<nameHex>` or `'lovelace'`), a payTo bech32, a network, and
6
6
  * a resource descriptor. There is NO USDM default and no decimals
7
- * assumption both belong to the consumer's product config.
7
+ * assumption, both belong to the consumer's product config.
8
8
  *
9
9
  * The v2 shape diverges from v1 in five places (see `docs/spec-v2-summary.md`
10
10
  * once written):
@@ -17,7 +17,7 @@
17
17
  * 5. `accepts[].assetTransferMethod`(new field)
18
18
  */
19
19
  import { type Network } from './network';
20
- import type { PaymentRequirementEntry, PaymentRequirementsBody, ResourceDescriptor, AssetTransferMethod } from './types';
20
+ import type { PaymentRequirementEntry, PaymentRequirementsBody, ResourceDescriptor, AssetTransferMethod, RouteOption } from './types';
21
21
  export interface BuildPaymentRequirementsArgs {
22
22
  /** Asset amount in raw units (BigInt-safe). */
23
23
  amount: string | number | bigint;
@@ -41,11 +41,43 @@ export interface BuildPaymentRequirementsArgs {
41
41
  }
42
42
  /**
43
43
  * Construct a single `accepts[]` entry. Most callers want
44
- * `buildPaymentRequirements()` which wraps this in the 402 envelope
44
+ * `buildPaymentRequirements()` which wraps this in the 402 envelope ,
45
45
  * use `buildEntry()` directly when composing multi-asset accept lists.
46
46
  */
47
47
  export declare function buildEntry(args: BuildPaymentRequirementsArgs): PaymentRequirementEntry;
48
48
  export declare function buildPaymentRequirements(args: BuildPaymentRequirementsArgs): PaymentRequirementsBody;
49
- /** Pick the first `accepts[]` entry what the validator inspects. */
49
+ /** Pick the first `accepts[]` entry, what the validator inspects. */
50
50
  export declare function flatRequirements(body: PaymentRequirementsBody): PaymentRequirementEntry;
51
+ export interface BuildMultiArgs {
52
+ /**
53
+ * Payment options the seller advertises. Buyer picks one implicitly by
54
+ * which (payTo, asset) the payment tx actually credits. MUST be non-empty.
55
+ */
56
+ options: RouteOption[];
57
+ /** Default recipient if a `RouteOption` omits `payTo`. */
58
+ payTo: string;
59
+ /** Default network if a `RouteOption` omits `network`. */
60
+ network: Network | string;
61
+ /** Default asset if a `RouteOption` omits `asset`. */
62
+ asset?: string;
63
+ resource: ResourceDescriptor | string;
64
+ description?: string;
65
+ mimeType?: string;
66
+ outputSchema?: unknown;
67
+ assetTransferMethod?: AssetTransferMethod;
68
+ maxTimeoutSeconds?: number;
69
+ extra?: Record<string, unknown>;
70
+ /** Same as the single-entry builder's flag. See `BuildPaymentRequirementsArgs`. */
71
+ withMissingHeaderError?: boolean;
72
+ }
73
+ /**
74
+ * Build a multi-entry `accepts[]` body. Each option inherits the
75
+ * top-level `payTo` / `network` / `asset` / etc. defaults unless it sets
76
+ * its own. The `resource` block is shared across all entries (a 402 is
77
+ * for one route, even when it offers many payment options).
78
+ *
79
+ * Single-entry callers should keep using `buildPaymentRequirements()`;
80
+ * this function is the path for the multi-accept feature.
81
+ */
82
+ export declare function buildPaymentRequirementsMulti(args: BuildMultiArgs): PaymentRequirementsBody;
51
83
  //# sourceMappingURL=requirements.d.ts.map
@@ -2,10 +2,10 @@
2
2
  /**
3
3
  * Build the canonical Cardano-x402-v2 PaymentRequirements body.
4
4
  *
5
- * Asset-agnostic by design the consumer passes a v2 asset string
5
+ * Asset-agnostic by design, the consumer passes a v2 asset string
6
6
  * (`<policy>.<nameHex>` or `'lovelace'`), a payTo bech32, a network, and
7
7
  * a resource descriptor. There is NO USDM default and no decimals
8
- * assumption both belong to the consumer's product config.
8
+ * assumption, both belong to the consumer's product config.
9
9
  *
10
10
  * The v2 shape diverges from v1 in five places (see `docs/spec-v2-summary.md`
11
11
  * once written):
@@ -21,6 +21,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
21
21
  exports.buildEntry = buildEntry;
22
22
  exports.buildPaymentRequirements = buildPaymentRequirements;
23
23
  exports.flatRequirements = flatRequirements;
24
+ exports.buildPaymentRequirementsMulti = buildPaymentRequirementsMulti;
24
25
  const network_1 = require("./network");
25
26
  const asset_1 = require("./asset");
26
27
  function normalizeResource(resource, description, mimeType, outputSchema) {
@@ -44,7 +45,7 @@ function normalizeResource(resource, description, mimeType, outputSchema) {
44
45
  }
45
46
  /**
46
47
  * Construct a single `accepts[]` entry. Most callers want
47
- * `buildPaymentRequirements()` which wraps this in the 402 envelope
48
+ * `buildPaymentRequirements()` which wraps this in the 402 envelope ,
48
49
  * use `buildEntry()` directly when composing multi-asset accept lists.
49
50
  */
50
51
  function buildEntry(args) {
@@ -77,10 +78,48 @@ function buildPaymentRequirements(args) {
77
78
  accepts,
78
79
  };
79
80
  }
80
- /** Pick the first `accepts[]` entry what the validator inspects. */
81
+ /** Pick the first `accepts[]` entry, what the validator inspects. */
81
82
  function flatRequirements(body) {
82
83
  if (!body.accepts || body.accepts.length === 0) {
83
84
  throw new Error('flatRequirements: PaymentRequirementsBody.accepts is empty');
84
85
  }
85
86
  return body.accepts[0];
86
87
  }
88
+ /**
89
+ * Build a multi-entry `accepts[]` body. Each option inherits the
90
+ * top-level `payTo` / `network` / `asset` / etc. defaults unless it sets
91
+ * its own. The `resource` block is shared across all entries (a 402 is
92
+ * for one route, even when it offers many payment options).
93
+ *
94
+ * Single-entry callers should keep using `buildPaymentRequirements()`;
95
+ * this function is the path for the multi-accept feature.
96
+ */
97
+ function buildPaymentRequirementsMulti(args) {
98
+ if (!args.options || args.options.length === 0) {
99
+ throw new Error('buildPaymentRequirementsMulti: options must be non-empty');
100
+ }
101
+ const accepts = args.options.map((opt) => {
102
+ const asset = opt.asset ?? args.asset;
103
+ if (!asset) {
104
+ throw new Error('buildPaymentRequirementsMulti: option is missing `asset` and no top-level default was set');
105
+ }
106
+ return buildEntry({
107
+ amount: opt.amount,
108
+ asset,
109
+ payTo: opt.payTo ?? args.payTo,
110
+ network: opt.network ?? args.network,
111
+ resource: args.resource,
112
+ description: opt.description ?? args.description,
113
+ mimeType: opt.mimeType ?? args.mimeType,
114
+ outputSchema: args.outputSchema,
115
+ assetTransferMethod: opt.assetTransferMethod ?? args.assetTransferMethod,
116
+ maxTimeoutSeconds: opt.maxTimeoutSeconds ?? args.maxTimeoutSeconds,
117
+ extra: opt.extra ?? args.extra,
118
+ });
119
+ });
120
+ return {
121
+ x402Version: 2,
122
+ ...(args.withMissingHeaderError ? { error: 'PAYMENT-SIGNATURE header is required' } : {}),
123
+ accepts,
124
+ };
125
+ }
@@ -1,15 +1,15 @@
1
1
  /**
2
2
  * Public type surface for x402 Cardano-v2.
3
3
  *
4
- * The shapes here match the v2 spec excerpt in
5
- * `docs/x402-cardano-integration.md` (envelope, requirements, payload).
6
- * Keep them as the single source of truth middleware, facilitator and
4
+ * The shapes here match the Cardano-x402-v2 spec (envelope, requirements,
5
+ * payload). See `docs/protocol.md` for the wire-level reference. Keep
6
+ * this file as the single source of truth: middleware, facilitator and
7
7
  * helpers all import from here.
8
8
  */
9
9
  import type { Network } from './network';
10
10
  /** Asset-transfer method. v2 spec. MVP supports only `default`. */
11
11
  export type AssetTransferMethod = 'default' | 'masumi' | 'script';
12
- /** v2 resource descriptor was a bare string in v1. */
12
+ /** v2 resource descriptor, was a bare string in v1. */
13
13
  export interface ResourceDescriptor {
14
14
  url: string;
15
15
  description: string;
@@ -45,7 +45,7 @@ export interface PaymentRequirementsBody {
45
45
  /**
46
46
  * Decoded `PAYMENT-SIGNATURE` envelope. The envelope payload references
47
47
  * a buyer-funded UTxO (the **nonce**) which must also appear in the
48
- * payment tx's inputs this is the v2 replay defense, on-chain.
48
+ * payment tx's inputs, this is the v2 replay defense, on-chain.
49
49
  */
50
50
  export interface PaymentEnvelope {
51
51
  x402Version: 2;
@@ -54,7 +54,7 @@ export interface PaymentEnvelope {
54
54
  payload: {
55
55
  /** base64 CBOR of the signed payment tx */
56
56
  transaction: string;
57
- /** `<txHash>#<outputIndex>` UTxO acting as replay nonce */
57
+ /** `<txHash>#<outputIndex>`, UTxO acting as replay nonce */
58
58
  nonce: string;
59
59
  };
60
60
  }
@@ -69,6 +69,8 @@ export interface PaymentClaim {
69
69
  unit: string;
70
70
  /** The v2 asset string from requirements (passed-through for audit). */
71
71
  asset: string;
72
+ /** Bech32 recipient the matched requirements entry credited. */
73
+ payTo: string;
72
74
  /** The route / resource URL the buyer paid for. */
73
75
  resourceUrl: string;
74
76
  /** UTxO-ref nonce as `<txHash>#<index>`. */
@@ -113,4 +115,64 @@ export interface DecodedInput {
113
115
  outputIndex: number;
114
116
  }
115
117
  export type { Network };
118
+ /**
119
+ * One payment option inside a multi-accept route. Lets a seller advertise
120
+ * e.g. "0.5 ADA or 0.1 USDM" for the same route. Top-level middleware
121
+ * options (`payTo`, `network`, `asset`, `description`, ...) fill any
122
+ * field the option leaves unset.
123
+ *
124
+ * The buyer's tx picks ONE option implicitly by which (payTo, asset) it
125
+ * actually pays; the facilitator selects the matching entry via
126
+ * `pickRequirement()` before running the six validation checks against it.
127
+ */
128
+ export interface RouteOption {
129
+ amount: string | number | bigint;
130
+ asset?: string;
131
+ payTo?: string;
132
+ network?: Network | string;
133
+ description?: string;
134
+ mimeType?: string;
135
+ assetTransferMethod?: AssetTransferMethod;
136
+ maxTimeoutSeconds?: number;
137
+ extra?: Record<string, unknown>;
138
+ }
139
+ /**
140
+ * What a `routePricing` entry or a `PriceResolver` may return.
141
+ *
142
+ * - scalar , single price in the route's default asset
143
+ * - `RouteOption` , single option with field overrides
144
+ * - `RouteOption[]` , multi-accept, buyer picks one on-chain
145
+ *
146
+ * Returning `null` from a resolver means "no gate, pass through" , the
147
+ * non-null type is what gates a request.
148
+ */
149
+ export type PriceSpec = string | number | bigint | RouteOption | RouteOption[];
150
+ /**
151
+ * Context passed to a `PriceResolver`. Intentionally minimal so we don't
152
+ * leak express/CAP-specific shapes through the public API. Both
153
+ * middlewares fill the common fields; CAP-only fields are optional.
154
+ */
155
+ export interface PricingContext {
156
+ /**
157
+ * CAP: req.event ('READ'|'CREATE'|action-name).
158
+ * Express: last URL segment with OData function args stripped.
159
+ */
160
+ event: string;
161
+ /** CAP target name (e.g. 'PricesService.Quotes'). Undefined in Express. */
162
+ target?: string;
163
+ /** Express request path. Undefined in CAP. */
164
+ path?: string;
165
+ /** HTTP method. Express only. */
166
+ method?: string;
167
+ /** Lower-cased header map. Array values for multi-valued headers. */
168
+ headers: Record<string, string | string[] | undefined>;
169
+ /** Parsed query params (Express). */
170
+ query?: Record<string, string | string[] | undefined>;
171
+ }
172
+ /**
173
+ * Dynamic pricing function. Sync or async. Return `null` to skip the
174
+ * gate, a scalar / option / option-array to charge. Errors thrown here
175
+ * are treated as middleware failures and surface as 500 to the buyer.
176
+ */
177
+ export type PriceResolver = (ctx: PricingContext) => PriceSpec | null | Promise<PriceSpec | null>;
116
178
  //# sourceMappingURL=types.d.ts.map
package/srv/core/types.js CHANGED
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * Public type surface for x402 Cardano-v2.
4
4
  *
5
- * The shapes here match the v2 spec excerpt in
6
- * `docs/x402-cardano-integration.md` (envelope, requirements, payload).
7
- * Keep them as the single source of truth middleware, facilitator and
5
+ * The shapes here match the Cardano-x402-v2 spec (envelope, requirements,
6
+ * payload). See `docs/protocol.md` for the wire-level reference. Keep
7
+ * this file as the single source of truth: middleware, facilitator and
8
8
  * helpers all import from here.
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });