@nevermined-io/payments 1.9.0 → 1.11.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.
Files changed (98) hide show
  1. package/dist/api/base-payments.d.ts +11 -0
  2. package/dist/api/base-payments.d.ts.map +1 -1
  3. package/dist/api/base-payments.js +28 -3
  4. package/dist/api/base-payments.js.map +1 -1
  5. package/dist/api/nvm-api.d.ts +4 -0
  6. package/dist/api/nvm-api.d.ts.map +1 -1
  7. package/dist/api/nvm-api.js +7 -0
  8. package/dist/api/nvm-api.js.map +1 -1
  9. package/dist/api/organizations-api/organizations-api.d.ts +19 -1
  10. package/dist/api/organizations-api/organizations-api.d.ts.map +1 -1
  11. package/dist/api/organizations-api/organizations-api.js +49 -0
  12. package/dist/api/organizations-api/organizations-api.js.map +1 -1
  13. package/dist/api/organizations-api/types.d.ts +27 -0
  14. package/dist/api/organizations-api/types.d.ts.map +1 -1
  15. package/dist/api/organizations-api/types.js.map +1 -1
  16. package/dist/common/api-version.d.ts +1 -1
  17. package/dist/common/api-version.js +1 -1
  18. package/dist/common/api-version.js.map +1 -1
  19. package/dist/common/types.d.ts +9 -4
  20. package/dist/common/types.d.ts.map +1 -1
  21. package/dist/common/types.js.map +1 -1
  22. package/dist/environments.d.ts +12 -0
  23. package/dist/environments.d.ts.map +1 -1
  24. package/dist/environments.js +31 -0
  25. package/dist/environments.js.map +1 -1
  26. package/dist/index.d.ts +1 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +2 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/mcp/core/server-manager.d.ts.map +1 -1
  31. package/dist/mcp/core/server-manager.js +3 -0
  32. package/dist/mcp/core/server-manager.js.map +1 -1
  33. package/dist/mcp/http/mcp-handler.d.ts +7 -0
  34. package/dist/mcp/http/mcp-handler.d.ts.map +1 -1
  35. package/dist/mcp/http/mcp-handler.js +8 -1
  36. package/dist/mcp/http/mcp-handler.js.map +1 -1
  37. package/dist/mcp/http/oauth-metadata.d.ts.map +1 -1
  38. package/dist/mcp/http/oauth-metadata.js +31 -6
  39. package/dist/mcp/http/oauth-metadata.js.map +1 -1
  40. package/dist/mcp/http/oauth-router.d.ts +17 -16
  41. package/dist/mcp/http/oauth-router.d.ts.map +1 -1
  42. package/dist/mcp/http/oauth-router.js +56 -28
  43. package/dist/mcp/http/oauth-router.js.map +1 -1
  44. package/dist/mpp/codec.d.ts +92 -0
  45. package/dist/mpp/codec.d.ts.map +1 -0
  46. package/dist/mpp/codec.js +405 -0
  47. package/dist/mpp/codec.js.map +1 -0
  48. package/dist/mpp/errors.d.ts +165 -0
  49. package/dist/mpp/errors.d.ts.map +1 -0
  50. package/dist/mpp/errors.js +177 -0
  51. package/dist/mpp/errors.js.map +1 -0
  52. package/dist/mpp/fetch.d.ts +196 -0
  53. package/dist/mpp/fetch.d.ts.map +1 -0
  54. package/dist/mpp/fetch.js +493 -0
  55. package/dist/mpp/fetch.js.map +1 -0
  56. package/dist/mpp/index.d.ts +11 -0
  57. package/dist/mpp/index.d.ts.map +1 -0
  58. package/dist/mpp/index.js +20 -0
  59. package/dist/mpp/index.js.map +1 -0
  60. package/dist/mpp/mpp-api.d.ts +153 -0
  61. package/dist/mpp/mpp-api.d.ts.map +1 -0
  62. package/dist/mpp/mpp-api.js +298 -0
  63. package/dist/mpp/mpp-api.js.map +1 -0
  64. package/dist/mpp/types.d.ts +43 -0
  65. package/dist/mpp/types.d.ts.map +1 -0
  66. package/dist/mpp/types.js +9 -0
  67. package/dist/mpp/types.js.map +1 -0
  68. package/dist/payments.d.ts +13 -0
  69. package/dist/payments.d.ts.map +1 -1
  70. package/dist/payments.js +3 -0
  71. package/dist/payments.js.map +1 -1
  72. package/dist/x402/delegation-api.d.ts +3 -1
  73. package/dist/x402/delegation-api.d.ts.map +1 -1
  74. package/dist/x402/delegation-api.js.map +1 -1
  75. package/dist/x402/express/index.d.ts +2 -0
  76. package/dist/x402/express/index.d.ts.map +1 -1
  77. package/dist/x402/express/index.js +2 -0
  78. package/dist/x402/express/index.js.map +1 -1
  79. package/dist/x402/express/middleware.d.ts +33 -1
  80. package/dist/x402/express/middleware.d.ts.map +1 -1
  81. package/dist/x402/express/middleware.js +831 -1
  82. package/dist/x402/express/middleware.js.map +1 -1
  83. package/dist/x402/express/mpp-support.d.ts +58 -0
  84. package/dist/x402/express/mpp-support.d.ts.map +1 -0
  85. package/dist/x402/express/mpp-support.js +67 -0
  86. package/dist/x402/express/mpp-support.js.map +1 -0
  87. package/dist/x402/express/raw-body.d.ts +24 -0
  88. package/dist/x402/express/raw-body.d.ts.map +1 -0
  89. package/dist/x402/express/raw-body.js +34 -0
  90. package/dist/x402/express/raw-body.js.map +1 -0
  91. package/dist/x402/token-request.d.ts +15 -0
  92. package/dist/x402/token-request.d.ts.map +1 -0
  93. package/dist/x402/token-request.js +66 -0
  94. package/dist/x402/token-request.js.map +1 -0
  95. package/dist/x402/token.d.ts.map +1 -1
  96. package/dist/x402/token.js +7 -51
  97. package/dist/x402/token.js.map +1 -1
  98. package/package.json +3 -1
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Typed errors for the MPP surface.
3
+ *
4
+ * The backend deliberately collapses every rejection reason into one code so
5
+ * the endpoint cannot be used as a forgery oracle. The SDK mirrors that: it
6
+ * does not try to reconstruct why a credential was refused.
7
+ */
8
+ /**
9
+ * Reads the spend accounting off any error raised by `payments.mpp.fetch`.
10
+ *
11
+ * Exported so a caller never has to hand-cast: the field is declared on
12
+ * {@link MppError} but is also attached to the {@link PaymentsError} a
13
+ * caller-constraint guard throws on the re-challenge turn, and those two do
14
+ * not share a base class.
15
+ *
16
+ * A report is attached **only** when at least one credential was presented, so
17
+ * a truthy result always means money may have left, and `undefined` always
18
+ * means nothing was spent. That is what makes `if (mppSpendOf(err))` a usable
19
+ * test rather than one that fires on plain argument validation too.
20
+ */
21
+ export function mppSpendOf(error) {
22
+ if (typeof error !== 'object' || error === null)
23
+ return undefined;
24
+ const spend = error.spend;
25
+ if (typeof spend !== 'object' || spend === null)
26
+ return undefined;
27
+ return typeof spend.credentialsPresented === 'number'
28
+ ? spend
29
+ : undefined;
30
+ }
31
+ export class MppError extends Error {
32
+ constructor(message, code) {
33
+ super(message);
34
+ this.code = code;
35
+ this.name = 'MppError';
36
+ }
37
+ }
38
+ /** `BCK.MPP.0002` — the deployment has no MPP secret, so MPP routes are off. */
39
+ export class MppNotConfiguredError extends MppError {
40
+ constructor(message = 'MPP is not configured on this environment') {
41
+ super(message, 'BCK.MPP.0002');
42
+ this.name = 'MppNotConfiguredError';
43
+ }
44
+ }
45
+ /** `BCK.MPP.0003` — the credential was refused (replay, forgery, plan, balance). */
46
+ export class MppCredentialRejectedError extends MppError {
47
+ constructor(message = 'MPP credential rejected') {
48
+ super(message, 'BCK.MPP.0003');
49
+ this.name = 'MppCredentialRejectedError';
50
+ }
51
+ }
52
+ /** `BCK.MPP.0004` — the challenge expired. Fetch a fresh one; do not retry blindly. */
53
+ export class MppChallengeExpiredError extends MppError {
54
+ constructor(message = 'MPP challenge expired') {
55
+ super(message, 'BCK.MPP.0004');
56
+ this.name = 'MppChallengeExpiredError';
57
+ }
58
+ }
59
+ /** `BCK.MPP.0005` — the body sent does not match the digest sealed in the challenge. */
60
+ export class MppBodyDigestMismatchError extends MppError {
61
+ constructor(message = 'MPP body digest mismatch') {
62
+ super(message, 'BCK.MPP.0005');
63
+ this.name = 'MppBodyDigestMismatchError';
64
+ }
65
+ }
66
+ /**
67
+ * Stable `code` on {@link MppSettlementOutcomeUnknownError}. Not a backend
68
+ * code — no `BCK.MPP.*` prefix, so it can never collide with one.
69
+ */
70
+ export const MPP_SETTLEMENT_OUTCOME_UNKNOWN_CODE = 'settlement_outcome_unknown';
71
+ /**
72
+ * Raised when settlement's outcome cannot be determined: `settleCredential`'s
73
+ * own outbound deadline firing before any response, or a 2xx whose body could
74
+ * not be read. Settlement is the one MPP call that burns, so in both cases the
75
+ * burn may have already happened even though the caller received nothing
76
+ * usable. Collapsing that into the same `network_error` `MppError` used for
77
+ * "nothing happened" failures (connection refused, DNS failure, a hung
78
+ * challenge/verify call) would let a real burn get logged and treated exactly
79
+ * like one that never occurred — silently corrupting the seller's own
80
+ * accounting on the call that is not safe to shrug off.
81
+ */
82
+ export class MppSettlementOutcomeUnknownError extends MppError {
83
+ constructor(message = 'MPP settlement outcome unknown: the request timed out before the backend responded, so the credits may or may not have been burned') {
84
+ // No backend issues this code — the condition is detected here — but it
85
+ // still carries one, following the same convention as the other
86
+ // SDK-invented codes (`network_error`, `http_${status}`). `code` is the
87
+ // only DATA-level discriminant on this hierarchy, and this is the branch
88
+ // whose whole point is that misclassifying it corrupts the seller's
89
+ // accounting. Two copies of this package in one dependency tree make
90
+ // `instanceof` false for a genuinely-MPP error, and the check would
91
+ // degrade silently to the "nothing happened" path — which the
92
+ // integration guide tells sellers to rely on. Same across a process or
93
+ // serialization boundary.
94
+ super(message, MPP_SETTLEMENT_OUTCOME_UNKNOWN_CODE);
95
+ this.name = 'MppSettlementOutcomeUnknownError';
96
+ }
97
+ }
98
+ /**
99
+ * Stable `code` on {@link MppSpendOutcomeUnknownError}. Not a backend code, for
100
+ * the same reason as {@link MPP_SETTLEMENT_OUTCOME_UNKNOWN_CODE}.
101
+ */
102
+ export const MPP_SPEND_OUTCOME_UNKNOWN_CODE = 'spend_outcome_unknown';
103
+ /**
104
+ * The buyer-side mirror of {@link MppSettlementOutcomeUnknownError}: raised
105
+ * when the retry that carries a credential fails at the transport level — a
106
+ * disconnect, a DNS failure, or the caller's own `AbortSignal` firing between
107
+ * the mint and the retry (`init.signal` is re-sent with it).
108
+ *
109
+ * The credential is on the wire by then, so the seller may already have
110
+ * verified and burned it. Left as the raw `TypeError` that `fetch` rejects
111
+ * with, the failure would escape the `catch (e) { if (e instanceof MppError) }`
112
+ * pattern this module and `markdown/mpp-integration.md` both prescribe, and
113
+ * carry no accounting at all: spent, invisible, unrecoverable. Wrapped, it
114
+ * reaches the documented handler with {@link MppSpendReport} attached and the
115
+ * original error preserved on `cause`.
116
+ *
117
+ * A transport failure BEFORE any credential exists is not this error — it stays
118
+ * exactly as `fetch` threw it, because nothing was spent and wrapping it would
119
+ * only obscure a plain network fault.
120
+ */
121
+ export class MppSpendOutcomeUnknownError extends MppError {
122
+ constructor(message, cause, spend) {
123
+ super(message, MPP_SPEND_OUTCOME_UNKNOWN_CODE);
124
+ this.name = 'MppSpendOutcomeUnknownError';
125
+ this.cause = cause;
126
+ this.spend = spend;
127
+ }
128
+ }
129
+ /**
130
+ * `BCK.MPP.*` codes a buyer can retry by minting a fresh credential against
131
+ * the NEW challenge the same 402 carries alongside them — as opposed to
132
+ * `BCK.MPP.0003`, which means the credential itself was refused and paying
133
+ * again cannot help.
134
+ *
135
+ * `0004` (expired) is obviously retryable: fetch a fresh challenge, pay it.
136
+ * `0005` (body digest mismatch) is less obvious but equally retryable: the
137
+ * fresh challenge is sealed to the digest of the request that just arrived,
138
+ * so a credential minted against it — presented with the SAME body — would
139
+ * succeed. A buyer gate that only excepts `0004` from a bare
140
+ * `code.startsWith('BCK.MPP.')` check wrongly treats `0005` as terminal and
141
+ * gives up on a request that would have worked on the next attempt.
142
+ *
143
+ * Grouped here, once, so a buyer checks {@link isRetryableMppCode} instead of
144
+ * hardcoding the exception list — and so a future retryable code only needs
145
+ * to be added to this one set.
146
+ *
147
+ * Exported so the buyer's own tests derive their table from this set instead of
148
+ * repeating its members, which is what makes "a code added here is honoured by
149
+ * `payments.mpp.fetch`" an enforced property rather than a convention. It is
150
+ * deliberately NOT re-exported from `./index.js`: a mutable-looking membership
151
+ * list is not something to make public API.
152
+ */
153
+ export const RETRYABLE_BCK_MPP_CODES = new Set([
154
+ 'BCK.MPP.0004',
155
+ 'BCK.MPP.0005',
156
+ ]);
157
+ /** Whether a `BCK.MPP.*` code means "mint a fresh credential and try again"
158
+ * rather than "this credential was refused; a new one changes nothing". */
159
+ export function isRetryableMppCode(code) {
160
+ return code !== undefined && RETRYABLE_BCK_MPP_CODES.has(code);
161
+ }
162
+ /** Maps a backend error payload onto the typed error hierarchy. */
163
+ export function toMppError(code, message) {
164
+ switch (code) {
165
+ case 'BCK.MPP.0002':
166
+ return new MppNotConfiguredError(message);
167
+ case 'BCK.MPP.0003':
168
+ return new MppCredentialRejectedError(message);
169
+ case 'BCK.MPP.0004':
170
+ return new MppChallengeExpiredError(message);
171
+ case 'BCK.MPP.0005':
172
+ return new MppBodyDigestMismatchError(message);
173
+ default:
174
+ return new MppError(message, code);
175
+ }
176
+ }
177
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/mpp/errors.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Typed errors for the MPP surface.\n *\n * The backend deliberately collapses every rejection reason into one code so\n * the endpoint cannot be used as a forgery oracle. The SDK mirrors that: it\n * does not try to reconstruct why a credential was refused.\n */\n\n/**\n * What a buyer-side failure reports about money already committed.\n *\n * `payments.mpp.fetch` returns this accounting on its success path\n * (`credentialsPresented` / `creditsPresented` on `MppFetchResult`). It is\n * repeated on the ERROR path because that is where a caller needs it most: the\n * credential and its access token are function-local and gone once the helper\n * throws, so an error without these numbers leaves the caller unable to tell\n * whether money left — the one outcome a buyer helper must never produce.\n *\n * Read it with {@link mppSpendOf} rather than casting: it rides on\n * `PaymentsError` too (a `maxCredits` or `planId` guard can fire on the\n * re-challenge turn, after a credential has already been presented).\n */\nexport interface MppSpendReport {\n /** How many credentials were minted and sent before the failure (0, 1 or 2). */\n credentialsPresented: number\n /**\n * Total credits named by the challenges those credentials were minted\n * against, as a decimal string. Present whenever `credentialsPresented > 0`.\n */\n creditsPresented?: string\n /** `id` of the challenge the last credential was minted against, so the caller can correlate it with the seller's side. */\n challengeId?: string\n}\n\n/**\n * Reads the spend accounting off any error raised by `payments.mpp.fetch`.\n *\n * Exported so a caller never has to hand-cast: the field is declared on\n * {@link MppError} but is also attached to the {@link PaymentsError} a\n * caller-constraint guard throws on the re-challenge turn, and those two do\n * not share a base class.\n *\n * A report is attached **only** when at least one credential was presented, so\n * a truthy result always means money may have left, and `undefined` always\n * means nothing was spent. That is what makes `if (mppSpendOf(err))` a usable\n * test rather than one that fires on plain argument validation too.\n */\nexport function mppSpendOf(error: unknown): MppSpendReport | undefined {\n if (typeof error !== 'object' || error === null) return undefined\n const spend = (error as { spend?: unknown }).spend\n if (typeof spend !== 'object' || spend === null) return undefined\n return typeof (spend as MppSpendReport).credentialsPresented === 'number'\n ? (spend as MppSpendReport)\n : undefined\n}\n\nexport class MppError extends Error {\n /**\n * Spend accounting, set when this error was raised after a credential had\n * been minted and presented. Absent means nothing was spent. Prefer\n * {@link mppSpendOf}, which also reads it off a `PaymentsError`.\n */\n spend?: MppSpendReport\n\n constructor(\n message: string,\n readonly code?: string,\n ) {\n super(message)\n this.name = 'MppError'\n }\n}\n\n/** `BCK.MPP.0002` — the deployment has no MPP secret, so MPP routes are off. */\nexport class MppNotConfiguredError extends MppError {\n constructor(message = 'MPP is not configured on this environment') {\n super(message, 'BCK.MPP.0002')\n this.name = 'MppNotConfiguredError'\n }\n}\n\n/** `BCK.MPP.0003` — the credential was refused (replay, forgery, plan, balance). */\nexport class MppCredentialRejectedError extends MppError {\n constructor(message = 'MPP credential rejected') {\n super(message, 'BCK.MPP.0003')\n this.name = 'MppCredentialRejectedError'\n }\n}\n\n/** `BCK.MPP.0004` — the challenge expired. Fetch a fresh one; do not retry blindly. */\nexport class MppChallengeExpiredError extends MppError {\n constructor(message = 'MPP challenge expired') {\n super(message, 'BCK.MPP.0004')\n this.name = 'MppChallengeExpiredError'\n }\n}\n\n/** `BCK.MPP.0005` — the body sent does not match the digest sealed in the challenge. */\nexport class MppBodyDigestMismatchError extends MppError {\n constructor(message = 'MPP body digest mismatch') {\n super(message, 'BCK.MPP.0005')\n this.name = 'MppBodyDigestMismatchError'\n }\n}\n\n/**\n * Stable `code` on {@link MppSettlementOutcomeUnknownError}. Not a backend\n * code — no `BCK.MPP.*` prefix, so it can never collide with one.\n */\nexport const MPP_SETTLEMENT_OUTCOME_UNKNOWN_CODE = 'settlement_outcome_unknown'\n\n/**\n * Raised when settlement's outcome cannot be determined: `settleCredential`'s\n * own outbound deadline firing before any response, or a 2xx whose body could\n * not be read. Settlement is the one MPP call that burns, so in both cases the\n * burn may have already happened even though the caller received nothing\n * usable. Collapsing that into the same `network_error` `MppError` used for\n * \"nothing happened\" failures (connection refused, DNS failure, a hung\n * challenge/verify call) would let a real burn get logged and treated exactly\n * like one that never occurred — silently corrupting the seller's own\n * accounting on the call that is not safe to shrug off.\n */\nexport class MppSettlementOutcomeUnknownError extends MppError {\n constructor(\n message = 'MPP settlement outcome unknown: the request timed out before the backend responded, so the credits may or may not have been burned',\n ) {\n // No backend issues this code — the condition is detected here — but it\n // still carries one, following the same convention as the other\n // SDK-invented codes (`network_error`, `http_${status}`). `code` is the\n // only DATA-level discriminant on this hierarchy, and this is the branch\n // whose whole point is that misclassifying it corrupts the seller's\n // accounting. Two copies of this package in one dependency tree make\n // `instanceof` false for a genuinely-MPP error, and the check would\n // degrade silently to the \"nothing happened\" path — which the\n // integration guide tells sellers to rely on. Same across a process or\n // serialization boundary.\n super(message, MPP_SETTLEMENT_OUTCOME_UNKNOWN_CODE)\n this.name = 'MppSettlementOutcomeUnknownError'\n }\n}\n\n/**\n * Stable `code` on {@link MppSpendOutcomeUnknownError}. Not a backend code, for\n * the same reason as {@link MPP_SETTLEMENT_OUTCOME_UNKNOWN_CODE}.\n */\nexport const MPP_SPEND_OUTCOME_UNKNOWN_CODE = 'spend_outcome_unknown'\n\n/**\n * The buyer-side mirror of {@link MppSettlementOutcomeUnknownError}: raised\n * when the retry that carries a credential fails at the transport level — a\n * disconnect, a DNS failure, or the caller's own `AbortSignal` firing between\n * the mint and the retry (`init.signal` is re-sent with it).\n *\n * The credential is on the wire by then, so the seller may already have\n * verified and burned it. Left as the raw `TypeError` that `fetch` rejects\n * with, the failure would escape the `catch (e) { if (e instanceof MppError) }`\n * pattern this module and `markdown/mpp-integration.md` both prescribe, and\n * carry no accounting at all: spent, invisible, unrecoverable. Wrapped, it\n * reaches the documented handler with {@link MppSpendReport} attached and the\n * original error preserved on `cause`.\n *\n * A transport failure BEFORE any credential exists is not this error — it stays\n * exactly as `fetch` threw it, because nothing was spent and wrapping it would\n * only obscure a plain network fault.\n */\nexport class MppSpendOutcomeUnknownError extends MppError {\n /** The original error `fetch` (or the mint) rejected with. */\n readonly cause?: unknown\n\n constructor(message: string, cause?: unknown, spend?: MppSpendReport) {\n super(message, MPP_SPEND_OUTCOME_UNKNOWN_CODE)\n this.name = 'MppSpendOutcomeUnknownError'\n this.cause = cause\n this.spend = spend\n }\n}\n\n/**\n * What `paymentMiddleware`'s `onAfterSettle` hook receives as its third\n * argument when settlement raised {@link MppSettlementOutcomeUnknownError}.\n * That parameter's declared type is `unknown` (shared with the x402 hook of\n * the same name — out of scope to narrow here), so nothing stops a consumer\n * from casting straight to `MppSettleResult` and silently reading\n * `undefined` for `creditsRedeemed` on this branch. Exporting this shape\n * gives a consumer something concrete to check for instead — e.g.\n * `if ((result as MppSettlementOutcomeUnknown).outcome === 'unknown')` —\n * and documents, at the type level, that the branch exists at all.\n */\nexport interface MppSettlementOutcomeUnknown {\n outcome: 'unknown'\n reason: string\n}\n\n/**\n * `BCK.MPP.*` codes a buyer can retry by minting a fresh credential against\n * the NEW challenge the same 402 carries alongside them — as opposed to\n * `BCK.MPP.0003`, which means the credential itself was refused and paying\n * again cannot help.\n *\n * `0004` (expired) is obviously retryable: fetch a fresh challenge, pay it.\n * `0005` (body digest mismatch) is less obvious but equally retryable: the\n * fresh challenge is sealed to the digest of the request that just arrived,\n * so a credential minted against it — presented with the SAME body — would\n * succeed. A buyer gate that only excepts `0004` from a bare\n * `code.startsWith('BCK.MPP.')` check wrongly treats `0005` as terminal and\n * gives up on a request that would have worked on the next attempt.\n *\n * Grouped here, once, so a buyer checks {@link isRetryableMppCode} instead of\n * hardcoding the exception list — and so a future retryable code only needs\n * to be added to this one set.\n *\n * Exported so the buyer's own tests derive their table from this set instead of\n * repeating its members, which is what makes \"a code added here is honoured by\n * `payments.mpp.fetch`\" an enforced property rather than a convention. It is\n * deliberately NOT re-exported from `./index.js`: a mutable-looking membership\n * list is not something to make public API.\n */\nexport const RETRYABLE_BCK_MPP_CODES: ReadonlySet<string> = new Set([\n 'BCK.MPP.0004',\n 'BCK.MPP.0005',\n])\n\n/** Whether a `BCK.MPP.*` code means \"mint a fresh credential and try again\"\n * rather than \"this credential was refused; a new one changes nothing\". */\nexport function isRetryableMppCode(code: string | undefined): boolean {\n return code !== undefined && RETRYABLE_BCK_MPP_CODES.has(code)\n}\n\n/** Maps a backend error payload onto the typed error hierarchy. */\nexport function toMppError(code: string | undefined, message: string): MppError {\n switch (code) {\n case 'BCK.MPP.0002':\n return new MppNotConfiguredError(message)\n case 'BCK.MPP.0003':\n return new MppCredentialRejectedError(message)\n case 'BCK.MPP.0004':\n return new MppChallengeExpiredError(message)\n case 'BCK.MPP.0005':\n return new MppBodyDigestMismatchError(message)\n default:\n return new MppError(message, code)\n }\n}\n"]}
@@ -0,0 +1,196 @@
1
+ /**
2
+ * The buyer half of MPP: pay a challenged endpoint with an existing Nevermined
3
+ * delegation.
4
+ *
5
+ * The buyer learns nothing about MPP beyond calling this instead of `fetch`.
6
+ * The plan comes out of the challenge, the credential is built from the
7
+ * challenge plus an MPP-domain access token, and the request is retried once.
8
+ *
9
+ * Two error families are used deliberately:
10
+ * - {@link PaymentsError} (`code: 'validation'`) for guards this call refuses
11
+ * to even attempt — a bad argument, a challenge that violates a
12
+ * caller-supplied constraint (`planId`, `maxCredits`), or a body that
13
+ * cannot be replayed. None of these mean a payment failed; nothing was ever
14
+ * attempted.
15
+ * - {@link MppError} (and its typed subclasses) for what the wire actually
16
+ * said: a rejected credential, a malformed challenge, an MPP-disabled
17
+ * environment. A caller branching on `instanceof MppError` to mean
18
+ * "the payment failed" gets exactly that, and no more.
19
+ */
20
+ import type { DelegationConfig, X402TokenOptions } from '../common/types.js';
21
+ import type { MppReceipt } from './types.js';
22
+ /**
23
+ * Options for {@link mppFetch} / `MppAPI.fetch`.
24
+ *
25
+ * @experimental The MPP buyer surface may change in a minor release.
26
+ *
27
+ * The request's `init.body`, if any, must be replayable **if the endpoint may
28
+ * challenge the request**: a `ReadableStream` body throws a typed
29
+ * {@link PaymentsError} once a 402 challenge actually requires a retry, since
30
+ * the stream cannot be resent. A request that is never challenged sends a
31
+ * stream body exactly once, exactly like plain `fetch`.
32
+ *
33
+ * This helper mints `nvm:erc4337` access tokens only in this release — a
34
+ * buyer holding an `nvm:card-delegation` delegation cannot use it yet.
35
+ */
36
+ export interface MppFetchOptions {
37
+ /** The delegation that backs the payment — the same one x402 uses. */
38
+ delegationConfig: DelegationConfig;
39
+ /**
40
+ * Overrides the agent id the seller's challenge names — the minted token is
41
+ * addressed to this agent id instead of whatever the challenge carries.
42
+ * Unlike `planId`, this is not a guard: a mismatch is not checked or
43
+ * refused, it simply replaces what the seller asked for. Leave unset to
44
+ * honor the challenge as issued.
45
+ */
46
+ agentId?: string;
47
+ /** Fail before minting if the challenge names a different plan than this. */
48
+ planId?: string;
49
+ /**
50
+ * Budget for the WHOLE call, not for one challenge: the helper refuses to
51
+ * mint whenever the credits named so far plus the credits this challenge
52
+ * asks for would exceed it. A seller unilaterally names the price, and a
53
+ * re-challenge names it again — a per-turn cap would therefore bound each
54
+ * turn at `maxCredits` and the call at twice it, which is not what "cap"
55
+ * reads as. `creditsPresented` on the result is the same running total, so
56
+ * the two always speak about the same number.
57
+ *
58
+ * Must be a non-negative integer (a decimal string, a safe integer, or a
59
+ * `bigint`); anything else is refused with a {@link PaymentsError} at entry,
60
+ * before the first request, rather than mid-flight on the first 402.
61
+ */
62
+ maxCredits?: string | number | bigint;
63
+ }
64
+ /**
65
+ * What {@link mppFetch} / `MppAPI.fetch` resolves to.
66
+ *
67
+ * @experimental Fields may be added or change meaning in a minor release —
68
+ * `settled` and `creditsPresented` both did during review.
69
+ */
70
+ export interface MppFetchResult {
71
+ /** The final response — the paid one when a payment happened. */
72
+ response: Response;
73
+ /**
74
+ * The decoded `Payment-Receipt`, when the server returned one and it
75
+ * decoded cleanly. A malformed receipt never throws — it leaves this
76
+ * absent (with a console warning) rather than destroying a response the
77
+ * caller already paid for.
78
+ */
79
+ receipt?: MppReceipt;
80
+ /**
81
+ * Whether the endpoint returned settlement evidence: a `Payment-Receipt` that
82
+ * decoded cleanly and does not state failure outright. Never derived from the
83
+ * HTTP status.
84
+ *
85
+ * `receipt.status` is read asymmetrically, on purpose. Success is NOT
86
+ * recognized — `'success'` is the only value with agreement behind it, so
87
+ * treating an unrecognized `'ok'`/`'completed'` as failure would report an
88
+ * unpaid call that was in fact paid. An explicit negative (`'failed'`,
89
+ * `'failure'`, `'declined'`, `'error'`) is a different thing from an unknown
90
+ * vocabulary, and is excluded: `paid: true` on a receipt that says the
91
+ * settlement failed is wrong in the one direction this field must never be
92
+ * wrong. Anything else stays `settled: true` with `receipt` on the result, so
93
+ * a caller paying third parties can judge the value itself.
94
+ *
95
+ * This SDK's own seller attaches a receipt only when settlement succeeded, so
96
+ * the distinction is third-party exposure rather than in-ecosystem.
97
+ */
98
+ settled: boolean;
99
+ /**
100
+ * How many credentials were minted and presented to the endpoint during
101
+ * this call (0, 1 or 2). This is NOT the same as `settled`:
102
+ * `credentialsPresented > 0` with `settled: false` means the seller may
103
+ * already have burned credits for a credential whose fate is unknown to
104
+ * the caller — treat that as "do not blindly retry", not as "nothing
105
+ * happened".
106
+ */
107
+ credentialsPresented: number;
108
+ /**
109
+ * TOTAL credits named by every challenge a credential was minted against
110
+ * during this call, as a decimal string — summed, not the last turn's
111
+ * amount, since a re-challenge is free to name a different price and the
112
+ * caller is accounting for the call. Present whenever
113
+ * `credentialsPresented > 0`.
114
+ *
115
+ * It is an **upper bound** on what may have burned, never a lower one. A
116
+ * seller that answers a retryable code while replaying the identical
117
+ * challenge id gets a second credential minted against that same challenge
118
+ * (a code decides alone — see {@link mppFetch}), and against a seller that
119
+ * keys single-use on the challenge id, as this SDK's middleware does, that
120
+ * second credential is refused as a replay and burns nothing. The count is
121
+ * deliberately not lowered for it: this field answers "what could have left",
122
+ * and guessing which of a remote's credentials it honoured would answer a
123
+ * question the buyer cannot see.
124
+ */
125
+ creditsPresented?: string;
126
+ /**
127
+ * Whether a credential was presented AND the final response looks
128
+ * successful: `response.ok && settled`. A 2xx with no receipt (a
129
+ * settlement that silently failed) and a non-2xx with a receipt (settle-
130
+ * then-error) are both `paid: false` — check `settled` and
131
+ * `credentialsPresented` for the honest picture in either case.
132
+ *
133
+ * `ok: true, paid: false, credentialsPresented: 1` is a ROUTINE outcome, not
134
+ * an exotic one: a seller whose handler streams or flushes has already sent
135
+ * its headers when settlement runs, so `Payment-Receipt` cannot be attached
136
+ * to a response that is already on the wire (this SDK's own middleware says
137
+ * so where it settles detached). The credits were burned. Never read this
138
+ * combination as "the payment did not happen" and retry.
139
+ */
140
+ paid: boolean;
141
+ }
142
+ /** Mints an MPP access token for a plan. Supplied by `MppAPI`. */
143
+ export type MppTokenMinter = (planId: string, agentId?: string, tokenOptions?: X402TokenOptions) => Promise<{
144
+ accessToken: string;
145
+ }>;
146
+ /**
147
+ * Performs the request, paying an MPP challenge if one comes back.
148
+ *
149
+ * At most one re-challenge cycle is followed: a seller that keeps challenging
150
+ * a freshly paid credential is not going to be satisfied by looping, and a
151
+ * loop would burn a credential per turn.
152
+ *
153
+ * The default on a retry-turn 402 is to STOP, not to pay again. **A code, when
154
+ * present, decides alone**: one {@link isRetryableMppCode} accepts
155
+ * (`BCK.MPP.0004` expired, `BCK.MPP.0005` body-digest mismatch) is retried,
156
+ * every other code is terminal — including a non-`BCK.MPP.*` one, e.g. the
157
+ * `network_error`/`http_500`-shaped code `MppAPI.post` can synthesize and this
158
+ * repo's own seller forwards. The challenge id is not consulted on that path,
159
+ * so a retryable code replaying the identical id does re-mint; `maxCredits`,
160
+ * not id-freshness, is what bounds what that can cost.
161
+ *
162
+ * **With no code**, freshness is the whole signal: a challenge whose `id`
163
+ * differs from the one just presented is a real re-challenge and is retried
164
+ * once, while the identical id replayed, an unparseable challenge or an
165
+ * unreadable body are terminal — a credential already proven invalid is never
166
+ * paid for twice.
167
+ *
168
+ * ### What throws and what comes back as a 402
169
+ *
170
+ * Only a rejection the remote NAMED throws. Three dead ends RETURN the 402
171
+ * instead, because the response is evidence the caller may need and throwing
172
+ * would discard it:
173
+ *
174
+ * 1. A 402 with no USABLE `Payment` challenge (either turn): no
175
+ * `WWW-Authenticate`, another scheme, or a `Payment` challenge missing a
176
+ * required param — {@link parseChallengeHeader} yields nothing for all
177
+ * three, so a seller that announced `Payment` and then sent it malformed
178
+ * lands here too, not only one that does not speak MPP.
179
+ * `credentialsPresented: 0`.
180
+ * 2. A retry-turn 402 that IS retryable but carries no challenge to retry
181
+ * against, so the next turn finds nothing to mint for.
182
+ * 3. The one re-challenge cycle spent: two credentials presented and the
183
+ * seller still answering 402.
184
+ *
185
+ * In cases 2 and 3 a credential WAS presented and may have been burned, so the
186
+ * result is `paid: false` with a non-zero `credentialsPresented` and the last
187
+ * 402 as `response`. Checking `response.ok` is therefore not optional — a
188
+ * resolved promise does not mean the request was paid for.
189
+ *
190
+ * Every throw raised after a credential was presented carries the same
191
+ * accounting as {@link MppSpendReport}, readable with `mppSpendOf(error)`,
192
+ * including a transport failure on the credential-bearing retry (wrapped as
193
+ * {@link MppSpendOutcomeUnknownError} so `instanceof MppError` catches it).
194
+ */
195
+ export declare function mppFetch(mintToken: MppTokenMinter, input: string | URL, init: RequestInit | undefined, options: MppFetchOptions): Promise<MppFetchResult>;
196
+ //# sourceMappingURL=fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/mpp/fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAI5E,OAAO,KAAK,EAAgB,UAAU,EAAE,MAAM,YAAY,CAAA;AAE1D;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,eAAe;IAC9B,sEAAsE;IACtE,gBAAgB,EAAE,gBAAgB,CAAA;IAClC;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;;;;;;;;;OAYG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;CACtC;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,QAAQ,EAAE,QAAQ,CAAA;IAClB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,UAAU,CAAA;IACpB;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,EAAE,OAAO,CAAA;IAChB;;;;;;;OAOG;IACH,oBAAoB,EAAE,MAAM,CAAA;IAC5B;;;;;;;;;;;;;;;;OAgBG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;;;;;;;;;OAaG;IACH,IAAI,EAAE,OAAO,CAAA;CACd;AAED,kEAAkE;AAClE,MAAM,MAAM,cAAc,GAAG,CAC3B,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,gBAAgB,KAC5B,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAuPrC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,wBAAsB,QAAQ,CAC5B,SAAS,EAAE,cAAc,EACzB,KAAK,EAAE,MAAM,GAAG,GAAG,EACnB,IAAI,EAAE,WAAW,GAAG,SAAS,EAC7B,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,cAAc,CAAC,CAoKzB"}