mppx 0.8.13 → 0.8.14

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 (123) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/Errors.d.ts +10 -0
  3. package/dist/Errors.d.ts.map +1 -1
  4. package/dist/Errors.js +11 -2
  5. package/dist/Errors.js.map +1 -1
  6. package/dist/Method.d.ts +105 -3
  7. package/dist/Method.d.ts.map +1 -1
  8. package/dist/Method.js +102 -2
  9. package/dist/Method.js.map +1 -1
  10. package/dist/client/internal/Fetch.d.ts.map +1 -1
  11. package/dist/client/internal/Fetch.js +12 -2
  12. package/dist/client/internal/Fetch.js.map +1 -1
  13. package/dist/client/internal/MethodChallenge.d.ts +17 -0
  14. package/dist/client/internal/MethodChallenge.d.ts.map +1 -0
  15. package/dist/client/internal/MethodChallenge.js +11 -0
  16. package/dist/client/internal/MethodChallenge.js.map +1 -0
  17. package/dist/server/Mppx.d.ts +23 -4
  18. package/dist/server/Mppx.d.ts.map +1 -1
  19. package/dist/server/Mppx.js +91 -82
  20. package/dist/server/Mppx.js.map +1 -1
  21. package/dist/tempo/client/Methods.d.ts +1 -0
  22. package/dist/tempo/client/Methods.d.ts.map +1 -1
  23. package/dist/tempo/internal/fee-payer.d.ts +1 -0
  24. package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
  25. package/dist/tempo/internal/fee-payer.js +7 -2
  26. package/dist/tempo/internal/fee-payer.js.map +1 -1
  27. package/dist/tempo/legacy/client/ChannelOps.d.ts.map +1 -1
  28. package/dist/tempo/legacy/client/ChannelOps.js +2 -1
  29. package/dist/tempo/legacy/client/ChannelOps.js.map +1 -1
  30. package/dist/tempo/server/Charge.d.ts +38 -77
  31. package/dist/tempo/server/Charge.d.ts.map +1 -1
  32. package/dist/tempo/server/Charge.js +289 -209
  33. package/dist/tempo/server/Charge.js.map +1 -1
  34. package/dist/tempo/server/Methods.d.ts +12 -1
  35. package/dist/tempo/server/Methods.d.ts.map +1 -1
  36. package/dist/tempo/server/Methods.js +4 -0
  37. package/dist/tempo/server/Methods.js.map +1 -1
  38. package/dist/tempo/server/Relay.d.ts +48 -0
  39. package/dist/tempo/server/Relay.d.ts.map +1 -0
  40. package/dist/tempo/server/Relay.js +177 -0
  41. package/dist/tempo/server/Relay.js.map +1 -0
  42. package/dist/tempo/server/SponsorBudget.d.ts +59 -0
  43. package/dist/tempo/server/SponsorBudget.d.ts.map +1 -0
  44. package/dist/tempo/server/SponsorBudget.js +144 -0
  45. package/dist/tempo/server/SponsorBudget.js.map +1 -0
  46. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  47. package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
  48. package/dist/tempo/server/internal/html.gen.js +1 -1
  49. package/dist/tempo/server/internal/html.gen.js.map +1 -1
  50. package/dist/tempo/session/client/ChannelOps.d.ts +7 -1
  51. package/dist/tempo/session/client/ChannelOps.d.ts.map +1 -1
  52. package/dist/tempo/session/client/ChannelOps.js +9 -4
  53. package/dist/tempo/session/client/ChannelOps.js.map +1 -1
  54. package/dist/tempo/session/client/CredentialState.d.ts +5 -1
  55. package/dist/tempo/session/client/CredentialState.d.ts.map +1 -1
  56. package/dist/tempo/session/client/CredentialState.js +37 -19
  57. package/dist/tempo/session/client/CredentialState.js.map +1 -1
  58. package/dist/tempo/session/client/Runtime.d.ts +8 -0
  59. package/dist/tempo/session/client/Runtime.d.ts.map +1 -1
  60. package/dist/tempo/session/client/Runtime.js +17 -0
  61. package/dist/tempo/session/client/Runtime.js.map +1 -1
  62. package/dist/tempo/session/client/Session.d.ts +15 -5
  63. package/dist/tempo/session/client/Session.d.ts.map +1 -1
  64. package/dist/tempo/session/client/Session.js +93 -36
  65. package/dist/tempo/session/client/Session.js.map +1 -1
  66. package/dist/tempo/session/client/SessionManager.d.ts +8 -1
  67. package/dist/tempo/session/client/SessionManager.d.ts.map +1 -1
  68. package/dist/tempo/session/client/SessionManager.js +54 -26
  69. package/dist/tempo/session/client/SessionManager.js.map +1 -1
  70. package/dist/tempo/session/client/Transports.d.ts +7 -5
  71. package/dist/tempo/session/client/Transports.d.ts.map +1 -1
  72. package/dist/tempo/session/client/Transports.js +48 -24
  73. package/dist/tempo/session/client/Transports.js.map +1 -1
  74. package/dist/tempo/session/precompile/Chain.d.ts.map +1 -1
  75. package/dist/tempo/session/precompile/Chain.js +96 -8
  76. package/dist/tempo/session/precompile/Chain.js.map +1 -1
  77. package/dist/tempo/session/precompile/Channel.d.ts +2 -0
  78. package/dist/tempo/session/precompile/Channel.d.ts.map +1 -1
  79. package/dist/tempo/session/precompile/Channel.js +4 -0
  80. package/dist/tempo/session/precompile/Channel.js.map +1 -1
  81. package/dist/tempo/session/server/RequestState.js +3 -1
  82. package/dist/tempo/session/server/RequestState.js.map +1 -1
  83. package/package.json +1 -1
  84. package/src/Errors.test.ts +23 -0
  85. package/src/Errors.ts +21 -2
  86. package/src/Method.test.ts +102 -1
  87. package/src/Method.ts +241 -5
  88. package/src/client/Mppx.test-d.ts +3 -3
  89. package/src/client/internal/Fetch.ts +16 -7
  90. package/src/client/internal/MethodChallenge.ts +30 -0
  91. package/src/server/Mppx.test-d.ts +2 -0
  92. package/src/server/Mppx.test.ts +205 -0
  93. package/src/server/Mppx.ts +167 -103
  94. package/src/tempo/PublicExports.test-d.ts +20 -0
  95. package/src/tempo/internal/fee-payer.ts +7 -3
  96. package/src/tempo/legacy/client/ChannelOps.test.ts +7 -0
  97. package/src/tempo/legacy/client/ChannelOps.ts +2 -1
  98. package/src/tempo/server/Charge.test.ts +337 -63
  99. package/src/tempo/server/Charge.ts +435 -267
  100. package/src/tempo/server/Methods.ts +11 -0
  101. package/src/tempo/server/Relay.test.ts +523 -0
  102. package/src/tempo/server/Relay.ts +288 -0
  103. package/src/tempo/server/SponsorBudget.test.ts +125 -0
  104. package/src/tempo/server/SponsorBudget.ts +213 -0
  105. package/src/tempo/server/internal/html.gen.ts +1 -1
  106. package/src/tempo/session/client/ChannelOps.test.ts +17 -2
  107. package/src/tempo/session/client/ChannelOps.ts +12 -4
  108. package/src/tempo/session/client/CredentialState.ts +61 -20
  109. package/src/tempo/session/client/Runtime.test.ts +12 -0
  110. package/src/tempo/session/client/Runtime.ts +22 -0
  111. package/src/tempo/session/client/Session.test.ts +235 -2
  112. package/src/tempo/session/client/Session.ts +130 -41
  113. package/src/tempo/session/client/SessionManager.test.ts +102 -4
  114. package/src/tempo/session/client/SessionManager.ts +71 -27
  115. package/src/tempo/session/client/Transports.test.ts +32 -1
  116. package/src/tempo/session/client/Transports.ts +79 -35
  117. package/src/tempo/session/precompile/Chain.integration.test.ts +19 -1
  118. package/src/tempo/session/precompile/Chain.test.ts +197 -10
  119. package/src/tempo/session/precompile/Chain.ts +102 -8
  120. package/src/tempo/session/precompile/Channel.test.ts +9 -0
  121. package/src/tempo/session/precompile/Channel.ts +5 -0
  122. package/src/tempo/session/server/RequestState.test.ts +8 -1
  123. package/src/tempo/session/server/RequestState.ts +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"Charge.d.ts","sourceRoot":"","sources":["../../../src/tempo/server/Charge.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AACrE,OAAO,KAAK,MAAM,MAAM,iBAAiB,CAAA;AACzC,OAAO,KAAK,KAAK,IAAI,MAAM,sCAAsC,CAAA;AACjE,OAAO,KAAK,KAAK,MAAM,gBAAgB,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,sBAAsB,CAAA;AAC9C,OAAO,KAAK,KAAK,CAAC,MAAM,cAAc,CAAA;AAEtC,OAAO,KAAK,OAAO,MAAM,wBAAwB,CAAA;AAIjD,OAAO,KAAK,QAAQ,MAAM,0BAA0B,CAAA;AAIpD,OAAO,KAAK,KAAK,KAAK,MAAM,sBAAsB,CAAA;AAClD,OAAO,KAAK,OAAO,MAAM,eAAe,CAAA;AAGxC;;;;;;;;;GASG;AACH,wBAAgB,MAAM,CAAC,KAAK,CAAC,UAAU,SAAS,MAAM,CAAC,UAAU,EAC/D,UAAU,GAAE,WAAW,CAAC,UAAU,EAAE,MAAM,CAAC,UAAU,CAGpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gEAseF;AAED,MAAM,CAAC,OAAO,WAAW,MAAM,CAAC;IAC9B,KAAK,YAAY,GAAG;QAAE,CAAC,GAAG,EAAE,eAAe,MAAM,EAAE,GAAG,MAAM,CAAA;KAAE,CAAA;IAE9D,KAAK,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC,eAAe,CAAC,OAAO,OAAO,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,WAAW,CAAC,CAAA;IAElG,KAAK,cAAc,GAAG,CAAC,UAAU,EAAE,wBAAwB,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAE1F,KAAK,wBAAwB,GAAG;QAC9B,6CAA6C;QAC7C,MAAM,EAAE,KAAK,MAAM,EAAE,CAAA;QACrB,8DAA8D;QAC9D,cAAc,EAAE,KAAK,MAAM,EAAE,CAAA;QAC7B,sEAAsE;QACtE,MAAM,CAAC,EAAE;YAAE,OAAO,EAAE,KAAK,MAAM,EAAE,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,GAAG,SAAS,CAAA;KACjE,CAAA;IAED,KAAK,UAAU,GAAG;QAChB,6EAA6E;QAC7E,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,SAAS,CAAA;QACxC;;;;;;;WAOG;QACH,cAAc,CAAC,EAAE,cAAc,GAAG,SAAS,CAAA;QAC3C;;;;;;WAMG;QACH,QAAQ,CAAC,EAAE,KAAK,MAAM,EAAE,GAAG,SAAS,CAAA;QACpC,oBAAoB;QACpB,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;QAC7B;;;;;;;;;;;;;WAaG;QACH,KAAK,CAAC,EAAE,KAAK,CAAC,WAAW,GAAG,SAAS,CAAA;QACrC;;;;WAIG;QACH,cAAc,CAAC,EAAE,cAAc,GAAG,SAAS,CAAA;QAC3C;;;;WAIG;QACH,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;QACnC;;;;;;;;;;WAUG;QACH,mBAAmB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;KAC1C,GAAG,MAAM,CAAC,WAAW,CAAC,UAAU,GAC/B,OAAO,CAAC,OAAO,CAAC,UAAU,GAC1B,QAAQ,CAAA;IAEV,KAAK,cAAc,CAAC,UAAU,SAAS,UAAU,IAAI,KAAK,CAAC,cAAc,CACvE,UAAU,EACV,QAAQ,EACR;QACE,QAAQ,EAAE,MAAM,CAAA;QAChB,QAAQ,EAAE,MAAM,CAAA;KACjB,CACF,CAAA;IAED,KAAK,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;CAC/C"}
1
+ {"version":3,"file":"Charge.d.ts","sourceRoot":"","sources":["../../../src/tempo/server/Charge.ts"],"names":[],"mappings":"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"}
@@ -19,6 +19,8 @@ import * as Proof from '../internal/proof.js';
19
19
  import * as Selectors from '../internal/selectors.js';
20
20
  import * as Methods from '../Methods.js';
21
21
  import { html as htmlContent } from './internal/html.gen.js';
22
+ import * as Relay from './Relay.js';
23
+ import * as SponsorBudget from './SponsorBudget.js';
22
24
  /**
23
25
  * Creates a Tempo charge method intent for usage on the server.
24
26
  *
@@ -30,9 +32,19 @@ import { html as htmlContent } from './internal/html.gen.js';
30
32
  * ```
31
33
  */
32
34
  export function charge(parameters = {}) {
33
- const { amount, currency = defaults.resolveCurrency(parameters), decimals = defaults.decimals, description, externalId, feeToken: configuredFeeToken, feePayerPolicy, html, memo, splits, supportedModes, validateSender, waitForConfirmation = true, } = parameters;
35
+ const { amount, currency = defaults.resolveCurrency(parameters), decimals = defaults.decimals, description, externalId, feeToken: configuredFeeToken, feePayerPolicy, html, memo, relay, splits, supportedModes, validateSender, waitForConfirmation = true, } = parameters;
34
36
  const storeKeyPrefix = parameters.storeKeyPrefix ?? '';
35
- const store = Store.from((parameters.store ?? Store.memory()), { keyPrefix: storeKeyPrefix });
37
+ const rawStore = (parameters.store ?? Store.memory());
38
+ const store = Store.from(rawStore, { keyPrefix: storeKeyPrefix });
39
+ // Aggregate exposure belongs to the actual sponsor, not a caller-specific
40
+ // replay namespace. Sharing the raw store gives every tenant/process the same
41
+ // atomic sponsor-wide budget.
42
+ const sponsorBudgetStore = Store.from(rawStore);
43
+ const { maxInFlightReservations = 100, maxInFlightTotalFee = (feePayerPolicy?.maxTotalFee ?? 50000000000000000n) * 10n, ...transactionFeePayerPolicy } = feePayerPolicy ?? {};
44
+ if (!Number.isSafeInteger(maxInFlightReservations) || maxInFlightReservations <= 0)
45
+ throw new Error('`feePayerPolicy.maxInFlightReservations` must be a positive safe integer.');
46
+ if (maxInFlightTotalFee <= 0n)
47
+ throw new Error('`feePayerPolicy.maxInFlightTotalFee` must be greater than zero.');
36
48
  const proofStore = parameters.store
37
49
  ? Store.from(parameters.store, {
38
50
  keyPrefix: storeKeyPrefix,
@@ -47,7 +59,174 @@ export function charge(parameters = {}) {
47
59
  getClient: parameters.getClient,
48
60
  rpcUrl: defaults.rpcUrl,
49
61
  });
50
- return Method.toServer(Methods.charge, {
62
+ function resolveRequest(request) {
63
+ const parsed = Methods.charge.schema.request.safeParse(request);
64
+ if (parsed.success)
65
+ return parsed.data;
66
+ // Credential handlers receive the HMAC-bound request in canonical output
67
+ // form, so it must not be transformed a second time.
68
+ return request;
69
+ }
70
+ async function resolveCredentialContext({ credential, request, }) {
71
+ const { challenge, payload } = credential;
72
+ const resolvedRequest = resolveRequest(request);
73
+ const chainId = resolvedRequest.methodDetails?.chainId ?? request.chainId;
74
+ const { amount, methodDetails } = resolvedRequest;
75
+ const supportedModes = methodDetails?.supportedModes;
76
+ const currency = resolvedRequest.currency;
77
+ const recipient = resolvedRequest.recipient;
78
+ const memo = methodDetails?.memo;
79
+ const isZeroAmount = BigInt(amount) === 0n;
80
+ Expires.assert(challenge.expires, challenge.id);
81
+ if (isZeroAmount && payload.type !== 'proof')
82
+ throw new MismatchError('Zero-amount challenges require a proof credential.', {});
83
+ return {
84
+ amount,
85
+ chainId,
86
+ challenge,
87
+ client: await getClient({ chainId }),
88
+ currency,
89
+ isZeroAmount,
90
+ memo,
91
+ methodDetails,
92
+ payload,
93
+ recipient,
94
+ requestAllowsFeePayer: request.feePayer !== false,
95
+ resolvedRequest,
96
+ supportedModes,
97
+ };
98
+ }
99
+ async function validateHashCredential(credential, payload, context) {
100
+ const { amount, chainId, challenge, client, currency, memo, methodDetails, recipient, supportedModes, } = context;
101
+ if (supportedModes && !supportedModes.includes('push'))
102
+ throw new MismatchError('Hash credentials are not supported for this challenge.', {});
103
+ const source = parseHashCredentialSource({
104
+ chainId: chainId ?? client.chain?.id,
105
+ source: credential.source,
106
+ });
107
+ const transfers = getExpectedTransfers({ amount, memo, methodDetails, recipient });
108
+ const receipt = await getTransactionReceipt(client, {
109
+ hash: payload.hash,
110
+ });
111
+ const sender = source?.address ?? receipt.from;
112
+ const matchedLogs = await assertTransferLogs(receipt, {
113
+ currency,
114
+ sender,
115
+ source,
116
+ transfers,
117
+ validateSender,
118
+ });
119
+ if (!memo)
120
+ assertChallengeBoundMemo(matchedLogs, {
121
+ challengeId: challenge.id,
122
+ realm: challenge.realm,
123
+ });
124
+ return { receipt: toReceipt(receipt), sender, transfers };
125
+ }
126
+ async function validateProofCredential(credential, payload, context) {
127
+ const { chainId, challenge, client, isZeroAmount } = context;
128
+ if (!isZeroAmount)
129
+ throw new MismatchError('Proof credentials are only valid for zero-amount challenges.', {});
130
+ const expectedSource = credential.source;
131
+ if (!expectedSource)
132
+ throw new MismatchError('Proof credential must include a source.', {});
133
+ const resolvedChainId = challenge.request.methodDetails?.chainId ?? chainId;
134
+ const source = Proof.parsePkhSource(expectedSource);
135
+ if (!source || source.chainId !== resolvedChainId)
136
+ throw new MismatchError('Proof credential source is invalid.', {});
137
+ const valid = await verifyTypedData(client, {
138
+ address: source.address,
139
+ ...Proof.typedData({
140
+ account: source.address,
141
+ chainId: resolvedChainId,
142
+ challengeId: challenge.id,
143
+ realm: challenge.realm,
144
+ }),
145
+ signature: payload.signature,
146
+ });
147
+ if (!valid) {
148
+ const proofSigner = recoverAuthorizedProofSigner({
149
+ chainId: resolvedChainId,
150
+ challengeId: challenge.id,
151
+ realm: challenge.realm,
152
+ signature: payload.signature,
153
+ sourceAddress: source.address,
154
+ });
155
+ const authorized = proofSigner
156
+ ? await isActiveAccessKey(client, { accessKey: proofSigner, account: source.address })
157
+ : false;
158
+ if (!authorized)
159
+ throw new MismatchError('Proof signature does not match source.', {});
160
+ }
161
+ return { sender: source.address };
162
+ }
163
+ async function validateTransactionCredential(credential, payload, request, context) {
164
+ const { amount, chainId, challenge, client, currency, memo, methodDetails, recipient, requestAllowsFeePayer, supportedModes, } = context;
165
+ if (supportedModes && !supportedModes.includes('pull'))
166
+ throw new MismatchError('Transaction credentials are not supported for this challenge.', {});
167
+ const serializedTransaction = payload.signature;
168
+ if (!FeePayer.isTempoTransaction(serializedTransaction))
169
+ throw new MismatchError('Only Tempo (0x76/0x78) transactions are supported.', {});
170
+ const transaction = Transaction.deserialize(serializedTransaction);
171
+ if (!transaction.signature || !transaction.from)
172
+ throw new MismatchError('Transaction must be signed by the sender before fee payer co-signing.', {});
173
+ const isFeePayerTx = methodDetails?.feePayer === true &&
174
+ requestAllowsFeePayer &&
175
+ !!(typeof request.feePayer === 'object' ? request.feePayer : feePayer || feePayerUrl);
176
+ const transfers = getExpectedTransfers({ amount, memo, methodDetails, recipient });
177
+ const matchedCalls = assertTransferCalls(transaction.calls ?? [], {
178
+ currency,
179
+ exactCount: isFeePayerTx,
180
+ transfers,
181
+ });
182
+ if (!memo)
183
+ assertChallengeBoundCallMemo(matchedCalls, {
184
+ challengeId: challenge.id,
185
+ realm: challenge.realm,
186
+ });
187
+ if (isFeePayerTx) {
188
+ FeePayer.validateCalls(transaction.calls, { amount, currency, recipient }, { currency, expectedTransfers: transfers });
189
+ FeePayer.assertAllowedFeeToken(transaction, FeePayer.defaultAllowedFeeTokens(chainId));
190
+ }
191
+ else {
192
+ await viem_call(client, FeePayer.simulationTransaction(transaction, { feePayer: false }));
193
+ }
194
+ return { isFeePayerTx, serializedTransaction, transaction, transfers };
195
+ }
196
+ async function validateCredential(credential, request, context) {
197
+ switch (context.payload.type) {
198
+ case 'hash': {
199
+ const { receipt, sender, transfers } = await validateHashCredential(credential, context.payload, context);
200
+ return {
201
+ details: { mode: 'push', sender, transfers },
202
+ hash: context.payload.hash,
203
+ receipt,
204
+ type: 'hash',
205
+ };
206
+ }
207
+ case 'proof': {
208
+ const { sender } = await validateProofCredential(credential, context.payload, context);
209
+ return { details: { mode: 'proof', sender }, type: 'proof' };
210
+ }
211
+ case 'transaction': {
212
+ const { isFeePayerTx, serializedTransaction, transaction, transfers } = await validateTransactionCredential(credential, context.payload, request, context);
213
+ return {
214
+ details: {
215
+ mode: 'pull',
216
+ sender: transaction.from,
217
+ serializedTransaction,
218
+ transfers,
219
+ },
220
+ isFeePayerTx,
221
+ serializedTransaction,
222
+ transaction,
223
+ transfers,
224
+ type: 'transaction',
225
+ };
226
+ }
227
+ }
228
+ }
229
+ const method = Method.toServer(Methods.charge, {
51
230
  defaults: {
52
231
  amount,
53
232
  currency,
@@ -127,138 +306,44 @@ export function charge(parameters = {}) {
127
306
  memo: request.memo || undefined,
128
307
  };
129
308
  },
130
- async verify({ credential, request }) {
131
- const { challenge } = credential;
132
- const resolvedRequest = (() => {
133
- const parsed = Methods.charge.schema.request.safeParse(request);
134
- if (parsed.success)
135
- return parsed.data;
136
- // verifyCredential() passes the HMAC-bound challenge request, which is
137
- // already in canonical output form and should not be transformed again.
138
- return request;
139
- })();
140
- const chainId = resolvedRequest.methodDetails?.chainId ?? request.chainId;
141
- const client = await getClient({ chainId });
142
- const { amount, methodDetails } = resolvedRequest;
143
- const requestAllowsFeePayer = request.feePayer !== false &&
144
- (request.feePayer === undefined ||
145
- request.feePayer === true ||
146
- typeof request.feePayer === 'object');
309
+ async validate({ credential, request }) {
310
+ const context = await resolveCredentialContext({ credential, request });
311
+ const validated = await validateCredential(credential, request, context);
312
+ return {
313
+ challenge: context.challenge,
314
+ credential,
315
+ details: validated.details,
316
+ intent: 'charge',
317
+ method: 'tempo',
318
+ request: context.resolvedRequest,
319
+ source: credential.source,
320
+ };
321
+ },
322
+ async broadcast({ credential, request }) {
323
+ const context = await resolveCredentialContext({ credential, request });
324
+ const { amount, chainId, challenge, client, currency, memo, methodDetails, recipient, requestAllowsFeePayer, } = context;
325
+ const validated = await validateCredential(credential, request, context);
147
326
  const feePayerAccount = methodDetails?.feePayer === true && requestAllowsFeePayer
148
327
  ? typeof request.feePayer === 'object'
149
328
  ? request.feePayer
150
329
  : feePayer
151
330
  : undefined;
152
331
  const expires = challenge.expires;
153
- const supportedModes = methodDetails?.supportedModes;
154
- const currency = resolvedRequest.currency;
155
- const recipient = resolvedRequest.recipient;
156
- Expires.assert(expires, challenge.id);
157
- const memo = methodDetails?.memo;
158
- const payload = credential.payload;
159
- const isZeroAmount = BigInt(amount) === 0n;
160
- if (isZeroAmount && payload.type !== 'proof')
161
- throw new MismatchError('Zero-amount challenges require a proof credential.', {});
162
- switch (payload.type) {
332
+ switch (validated.type) {
163
333
  case 'hash': {
164
- if (supportedModes && !supportedModes.includes('push'))
165
- throw new MismatchError('Hash credentials are not supported for this challenge.', {});
166
- const hash = payload.hash;
167
- // Validate client-supplied identity before reserving the hash so a
168
- // malformed source cannot burn an otherwise valid payment attempt.
169
- const source = parseHashCredentialSource({
170
- chainId: chainId ?? client.chain?.id,
171
- source: credential.source,
172
- });
173
- // Reserve the hash while we verify it. This blocks concurrent
174
- // requests from racing to reuse the same on-chain payment.
334
+ const { hash, receipt } = validated;
175
335
  if (!(await markHashUsed(store, hash))) {
176
- throw new VerificationFailedError({ reason: 'Transaction hash has already been used' });
177
- }
178
- // If verification fails after reservation, release it so transient
179
- // RPC/log-validation errors do not force the payer to pay again.
180
- // Once we have proven the receipt is a successful matching payment,
181
- // keep the marker to enforce single-use semantics.
182
- let releaseReservation = true;
183
- try {
184
- const expectedTransfers = getExpectedTransfers({
185
- amount,
186
- memo,
187
- methodDetails,
188
- recipient,
189
- });
190
- const receipt = await getTransactionReceipt(client, { hash });
191
- const sender = source?.address ?? receipt.from;
192
- const matchedLogs = await assertTransferLogs(receipt, {
193
- currency,
194
- sender,
195
- source,
196
- transfers: expectedTransfers,
197
- validateSender,
336
+ throw new VerificationFailedError({
337
+ reason: 'Transaction hash has already been used',
198
338
  });
199
- // Only verify challenge binding when using auto-generated attribution memos.
200
- // Explicit memos (set by the server) are strictly matched by assertTransferLogs
201
- // but are NOT challenge-bound — callers that set explicit memos are responsible
202
- // for ensuring memo uniqueness per challenge to prevent cross-challenge hash reuse.
203
- if (!memo)
204
- assertChallengeBoundMemo(matchedLogs, {
205
- challengeId: challenge.id,
206
- realm: challenge.realm,
207
- });
208
- const paymentReceipt = toReceipt(receipt);
209
- // `toReceipt` can throw for reverted transactions. Only keep the
210
- // reservation after it confirms the referenced transaction settled.
211
- releaseReservation = false;
212
- return paymentReceipt;
213
- }
214
- catch (error) {
215
- if (releaseReservation)
216
- await releaseHashUse(store, hash);
217
- throw error;
218
339
  }
340
+ return receipt;
219
341
  }
220
342
  case 'proof': {
221
- if (!isZeroAmount)
222
- throw new MismatchError('Proof credentials are only valid for zero-amount challenges.', {});
223
- const expectedSource = credential.source;
224
- if (!expectedSource)
225
- throw new MismatchError('Proof credential must include a source.', {});
226
- const resolvedChainId = challenge.request.methodDetails?.chainId ?? chainId;
227
- const source = Proof.parsePkhSource(expectedSource);
228
- if (!source || source.chainId !== resolvedChainId) {
229
- throw new MismatchError('Proof credential source is invalid.', {});
230
- }
231
- const valid = await verifyTypedData(client, {
232
- // Bind verification to the claimed payer (`source.address`): the
233
- // signer may be the payer itself or an access key authorized for it.
234
- address: source.address,
235
- ...Proof.typedData({
236
- account: source.address,
237
- chainId: resolvedChainId,
238
- challengeId: challenge.id,
239
- realm: challenge.realm,
240
- }),
241
- signature: payload.signature,
242
- });
243
- if (!valid) {
244
- const proofSigner = recoverAuthorizedProofSigner({
245
- chainId: resolvedChainId,
246
- challengeId: challenge.id,
247
- realm: challenge.realm,
248
- signature: payload.signature,
249
- sourceAddress: source.address,
250
- });
251
- const authorized = proofSigner
252
- ? await isActiveAccessKey(client, {
253
- accessKey: proofSigner,
254
- account: source.address,
255
- })
256
- : false;
257
- if (!authorized)
258
- throw new MismatchError('Proof signature does not match source.', {});
259
- }
260
343
  if (proofStore && !(await markProofUsed(proofStore, challenge.id))) {
261
- throw new VerificationFailedError({ reason: 'Proof credential has already been used' });
344
+ throw new VerificationFailedError({
345
+ reason: 'Proof credential has already been used',
346
+ });
262
347
  }
263
348
  return {
264
349
  method: 'tempo',
@@ -268,58 +353,23 @@ export function charge(parameters = {}) {
268
353
  };
269
354
  }
270
355
  case 'transaction': {
271
- if (supportedModes && !supportedModes.includes('pull'))
272
- throw new MismatchError('Transaction credentials are not supported for this challenge.', {});
273
- const serializedTransaction = payload.signature;
356
+ const { isFeePayerTx, serializedTransaction, transaction, transfers } = validated;
274
357
  // Pre-broadcast dedup: catch exact byte-for-byte replays early.
275
358
  const hash = keccak256(serializedTransaction);
276
359
  if (!(await markHashUsed(store, hash))) {
277
- throw new VerificationFailedError({ reason: 'Transaction hash has already been used' });
360
+ throw new VerificationFailedError({
361
+ reason: 'Transaction hash has already been used',
362
+ });
278
363
  }
279
- let releaseReservation = true;
280
- let sponsoredSenderReservation;
364
+ let broadcastAttempted = false;
365
+ let finalHash;
366
+ let reservation;
281
367
  try {
282
- if (!FeePayer.isTempoTransaction(serializedTransaction))
283
- throw new MismatchError('Only Tempo (0x76/0x78) transactions are supported.', {});
284
- const transaction = Transaction.deserialize(serializedTransaction);
285
- if (!transaction.signature || !transaction.from)
286
- throw new MismatchError('Transaction must be signed by the sender before fee payer co-signing.', {});
287
- const calls = (transaction.calls ?? []);
288
- const transfers = getExpectedTransfers({ amount, memo, methodDetails, recipient });
289
- const isFeePayerTx = methodDetails?.feePayer === true &&
290
- requestAllowsFeePayer &&
291
- !!(feePayerAccount || feePayerUrl);
292
- const matchedCalls = assertTransferCalls(calls, {
293
- currency,
294
- exactCount: isFeePayerTx,
295
- transfers,
296
- });
297
- if (!memo)
298
- assertChallengeBoundCallMemo(matchedCalls, {
299
- challengeId: challenge.id,
300
- realm: challenge.realm,
301
- });
302
- if (isFeePayerTx) {
303
- const reservationChainId = chainId ?? client.chain.id;
304
- if (!(await markSponsoredSenderInFlight(store, {
305
- chainId: reservationChainId,
306
- sender: transaction.from,
307
- }))) {
308
- throw new VerificationFailedError({
309
- reason: 'Sponsored transaction from this sender is already in flight',
310
- });
311
- }
312
- sponsoredSenderReservation = {
313
- chainId: reservationChainId,
314
- sender: transaction.from,
315
- };
316
- FeePayer.validateCalls(transaction.calls, { amount, currency, recipient }, { currency, expectedTransfers: transfers });
317
- }
318
368
  const allowedFeeTokens = FeePayer.defaultAllowedFeeTokens(chainId);
319
369
  if (isFeePayerTx)
320
370
  FeePayer.assertAllowedFeeToken(transaction, allowedFeeTokens);
321
371
  const selectableFeeTokens = allowedFeeTokens;
322
- const serializedTransaction_final = await (async () => {
372
+ const completedTransaction = await (async () => {
323
373
  if (feePayerAccount && methodDetails?.feePayer !== false) {
324
374
  const completed = await FeePayer.preflightSponsorship({
325
375
  transaction,
@@ -339,7 +389,7 @@ export function charge(parameters = {}) {
339
389
  challengeExpires: expires,
340
390
  chainId: chainId ?? client.chain.id,
341
391
  details: { amount, currency, recipient },
342
- policy: feePayerPolicy,
392
+ policy: transactionFeePayerPolicy,
343
393
  transaction: {
344
394
  ...transaction,
345
395
  ...(feeToken ? { feeToken } : {}),
@@ -348,7 +398,10 @@ export function charge(parameters = {}) {
348
398
  return { feePayer: feePayerAccount.address, transaction: sponsored };
349
399
  },
350
400
  });
351
- return signTransaction(client, completed.transaction);
401
+ return {
402
+ serializedTransaction: await signTransaction(client, completed.transaction),
403
+ sponsor: completed.feePayer,
404
+ };
352
405
  }
353
406
  if (feePayerUrl && isFeePayerTx) {
354
407
  const completed = await FeePayer.preflightSponsorship({
@@ -360,24 +413,73 @@ export function charge(parameters = {}) {
360
413
  challengeExpires: expires,
361
414
  chainId: chainId ?? client.chain.id,
362
415
  details: { amount, currency, recipient },
363
- policy: feePayerPolicy,
416
+ policy: transactionFeePayerPolicy,
364
417
  transaction,
365
418
  url: feePayerUrl,
366
419
  });
367
420
  return { ...hosted, transaction };
368
421
  },
369
422
  });
370
- return completed.serializedTransaction;
423
+ return {
424
+ serializedTransaction: completed.serializedTransaction,
425
+ sponsor: completed.feePayer,
426
+ };
371
427
  }
372
- return serializedTransaction;
428
+ return { serializedTransaction, sponsor: undefined };
373
429
  })();
430
+ const serializedTransaction_final = completedTransaction.serializedTransaction;
431
+ finalHash = keccak256(serializedTransaction_final);
432
+ if (finalHash.toLowerCase() !== hash.toLowerCase() &&
433
+ !(await markHashUsed(store, finalHash)))
434
+ throw new VerificationFailedError({
435
+ reason: 'Transaction hash has already been used',
436
+ });
437
+ if (isFeePayerTx) {
438
+ const sponsor = completedTransaction.sponsor;
439
+ if (!sponsor)
440
+ throw new VerificationFailedError({
441
+ reason: 'Sponsored transaction has no configured fee payer',
442
+ });
443
+ const reservationChainId = chainId ?? client.chain.id;
444
+ const { totalFee, validBeforeValue } = FeePayer.assertTransactionPolicy({
445
+ challengeExpires: expires,
446
+ chainId: reservationChainId,
447
+ details: { amount, currency, recipient },
448
+ policy: transactionFeePayerPolicy,
449
+ transaction,
450
+ });
451
+ const expiresAt = validBeforeValue * 1_000;
452
+ const waitUntil = Math.min(expiresAt, expires ? Date.parse(expires) : Number.MAX_SAFE_INTEGER);
453
+ reservation = await SponsorBudget.reserve(sponsorBudgetStore, {
454
+ chainId: reservationChainId,
455
+ expiresAt,
456
+ fee: totalFee,
457
+ getReceipt: (transactionHash) => getTransactionReceipt(client, { hash: transactionHash }),
458
+ id: finalHash.toLowerCase(),
459
+ maxReservations: maxInFlightReservations,
460
+ maxTotalFee: maxInFlightTotalFee,
461
+ owner: globalThis.crypto.randomUUID(),
462
+ sponsor,
463
+ transactionHash: finalHash,
464
+ waitUntil,
465
+ });
466
+ Expires.assert(challenge.expires, challenge.id);
467
+ }
374
468
  // Pre-broadcast simulation for non-sponsored transactions.
375
469
  if (!isFeePayerTx)
376
470
  await viem_call(client, FeePayer.simulationTransaction(transaction, { feePayer: false }));
471
+ if (reservation &&
472
+ !(await SponsorBudget.transition(sponsorBudgetStore, reservation, 'broadcasting')))
473
+ throw new VerificationFailedError({
474
+ reason: 'Sponsor budget reservation ownership was lost before broadcast',
475
+ });
476
+ broadcastAttempted = true;
377
477
  if (waitForConfirmation) {
378
478
  const receipt = await sendRawTransactionSync(client, {
379
479
  serializedTransaction: serializedTransaction_final,
380
480
  });
481
+ if (reservation)
482
+ await SponsorBudget.release(sponsorBudgetStore, reservation);
381
483
  const matchedLogs = await assertTransferLogs(receipt, {
382
484
  currency,
383
485
  sender: transaction.from,
@@ -388,17 +490,10 @@ export function charge(parameters = {}) {
388
490
  challengeId: challenge.id,
389
491
  realm: challenge.realm,
390
492
  });
391
- // Post-broadcast dedup: catch malleable input variants
392
- // (different serialized bytes, same underlying tx) that
393
- // bypass the pre-broadcast check. Skip if the broadcast
394
- // hash matches the input hash (already stored above).
395
- if (receipt.transactionHash.toLowerCase() !== hash.toLowerCase() &&
396
- !(await markHashUsed(store, receipt.transactionHash))) {
493
+ if (receipt.transactionHash.toLowerCase() !== finalHash.toLowerCase())
397
494
  throw new VerificationFailedError({
398
- reason: 'Transaction hash has already been used',
495
+ reason: 'Broadcast transaction hash does not match the signed transaction',
399
496
  });
400
- }
401
- releaseReservation = false;
402
497
  return toReceipt(receipt);
403
498
  }
404
499
  // Optimistic path: broadcast without waiting for confirmation
@@ -407,14 +502,15 @@ export function charge(parameters = {}) {
407
502
  const reference = await sendRawTransaction(client, {
408
503
  serializedTransaction: serializedTransaction_final,
409
504
  });
410
- // Post-broadcast dedup: same
411
- if (reference.toLowerCase() !== hash.toLowerCase() &&
412
- !(await markHashUsed(store, reference))) {
505
+ if (reference.toLowerCase() !== finalHash.toLowerCase())
413
506
  throw new VerificationFailedError({
414
- reason: 'Transaction hash has already been used',
507
+ reason: 'Broadcast transaction hash does not match the signed transaction',
508
+ });
509
+ if (reservation &&
510
+ !(await SponsorBudget.transition(sponsorBudgetStore, reservation, 'pending')))
511
+ throw new VerificationFailedError({
512
+ reason: 'Sponsor budget reservation ownership was lost after broadcast',
415
513
  });
416
- }
417
- releaseReservation = false;
418
514
  return {
419
515
  method: 'tempo',
420
516
  status: 'success',
@@ -423,20 +519,20 @@ export function charge(parameters = {}) {
423
519
  };
424
520
  }
425
521
  catch (error) {
426
- if (releaseReservation)
522
+ if (!broadcastAttempted) {
523
+ if (reservation)
524
+ await SponsorBudget.release(sponsorBudgetStore, reservation);
525
+ if (finalHash && finalHash.toLowerCase() !== hash.toLowerCase())
526
+ await releaseHashUse(store, finalHash);
427
527
  await releaseHashUse(store, hash);
528
+ }
428
529
  throw error;
429
530
  }
430
- finally {
431
- if (sponsoredSenderReservation)
432
- await releaseSponsoredSenderInFlight(store, sponsoredSenderReservation);
433
- }
434
531
  }
435
- default:
436
- throw new Error(`Unsupported credential type "${payload.type}".`);
437
532
  }
438
533
  },
439
534
  });
535
+ return relay ? Relay.configure(method, relay) : method;
440
536
  }
441
537
  function chargeBinding(request) {
442
538
  // Exhaustively destructure so new charge request fields require an explicit binding decision.
@@ -678,10 +774,6 @@ function getHashStoreKey(hash) {
678
774
  function getProofStoreKey(challengeId) {
679
775
  return `mppx:charge:proof:${challengeId}`;
680
776
  }
681
- /** @internal */
682
- function getSponsoredSenderStoreKey(parameters) {
683
- return `mppx:charge:sponsor:${parameters.chainId}:${parameters.sender.toLowerCase()}`;
684
- }
685
777
  async function markHashUsed(store, hash) {
686
778
  return store.update(getHashStoreKey(hash), (current) => {
687
779
  if (current !== null)
@@ -704,18 +796,6 @@ function parseHashCredentialSource(parameters) {
704
796
  return parsed;
705
797
  }
706
798
  /** @internal */
707
- async function markSponsoredSenderInFlight(store, parameters) {
708
- return store.update(getSponsoredSenderStoreKey(parameters), (current) => {
709
- if (current !== null)
710
- return { op: 'noop', result: false };
711
- return { op: 'set', value: Date.now(), result: true };
712
- });
713
- }
714
- /** @internal */
715
- async function releaseSponsoredSenderInFlight(store, parameters) {
716
- await store.delete(getSponsoredSenderStoreKey(parameters));
717
- }
718
- /** @internal */
719
799
  async function markProofUsed(store, challengeId) {
720
800
  return store.update(getProofStoreKey(challengeId), (current) => {
721
801
  if (current !== null)
@@ -827,7 +907,7 @@ class MismatchError extends PaymentError {
827
907
  ? reason
828
908
  : `Payment verification failed: ${reason}`,
829
909
  ...Object.entries(details).map(([k, v]) => ` - ${k}: ${v}`),
830
- ].join('\n'));
910
+ ].join('\n'), { details });
831
911
  }
832
912
  }
833
913
  //# sourceMappingURL=Charge.js.map