mppx 0.10.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/README.md +22 -1
- package/dist/cli/agent.d.ts +3 -0
- package/dist/cli/agent.js +20 -0
- package/dist/cli/cli.d.ts +1 -1
- package/dist/cli/cli.js +135 -135
- package/dist/cli/plugins/index.js +12 -12
- package/dist/cli/validate/payment.js +29 -12
- package/dist/client/internal/protocols/X402.js +0 -2
- package/dist/evm/server/Methods.d.ts +164 -3
- package/dist/evm/server/Methods.js +50 -2
- package/dist/internal/version.d.ts +2 -2
- package/dist/internal/version.js +1 -1
- package/dist/server/Mppx.d.ts +5 -1
- package/dist/server/Mppx.js +120 -49
- package/dist/server/internal/html/compose.main.gen.d.ts +1 -1
- package/dist/server/internal/html/serviceWorker.gen.d.ts +1 -1
- package/dist/stripe/server/Methods.js +13 -7
- package/dist/stripe/server/internal/analytics.d.ts +1 -0
- package/dist/stripe/server/internal/analytics.js +2 -2
- package/dist/stripe/server/internal/html.gen.d.ts +1 -1
- package/dist/stripe/server/internal/record-payment.js +2 -2
- package/dist/tempo/Methods.d.ts +5 -3
- package/dist/tempo/Methods.js +6 -5
- package/dist/tempo/client/Charge.d.ts +6 -4
- package/dist/tempo/client/Charge.js +61 -28
- package/dist/tempo/client/Methods.d.ts +5 -3
- package/dist/tempo/index.d.ts +2 -1
- package/dist/tempo/index.js +2 -1
- package/dist/tempo/internal/currencies.d.ts +19 -0
- package/dist/tempo/internal/currencies.js +41 -0
- package/dist/tempo/internal/defaults.d.ts +16 -3
- package/dist/tempo/internal/defaults.js +17 -2
- package/dist/tempo/internal/fee-payer.d.ts +5 -4
- package/dist/tempo/internal/fee-payer.js +7 -5
- package/dist/tempo/internal/machine-token-charge.d.ts +56 -0
- package/dist/tempo/internal/machine-token-charge.js +108 -0
- package/dist/tempo/server/Charge.d.ts +15 -1
- package/dist/tempo/server/Charge.js +101 -67
- package/dist/tempo/server/Methods.d.ts +5008 -40
- package/dist/tempo/server/Methods.js +105 -11
- package/dist/tempo/server/internal/html.gen.d.ts +1 -1
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/session/server/ChannelStore.d.ts +2 -0
- package/dist/tempo/session/server/ChannelStore.js +12 -3
- package/dist/tempo/session/server/CredentialVerification.d.ts +2 -0
- package/dist/tempo/session/server/CredentialVerification.js +22 -13
- package/dist/tempo/session/server/RequestState.d.ts +6 -0
- package/dist/tempo/session/server/RequestState.js +11 -4
- package/dist/tempo/session/server/Session.js +8 -1
- package/dist/tempo/session/server/Settlement.d.ts +2 -0
- package/dist/tempo/session/server/Settlement.js +13 -8
- package/dist/tempo/subscription/KeyAuthorization.d.ts +70 -8
- package/package.json +3 -3
- package/dist/tempo/Tokens.d.ts +0 -13
- package/dist/tempo/Tokens.js +0 -13
|
@@ -15,6 +15,7 @@ import * as Charge_internal from '../internal/charge.js';
|
|
|
15
15
|
import * as defaults from '../internal/defaults.js';
|
|
16
16
|
import * as FeePayer from '../internal/fee-payer.js';
|
|
17
17
|
import { resolveFeeToken } from '../internal/fee-token.js';
|
|
18
|
+
import * as MachineTokenCharge from '../internal/machine-token-charge.js';
|
|
18
19
|
import * as Proof from '../internal/proof.js';
|
|
19
20
|
import * as Selectors from '../internal/selectors.js';
|
|
20
21
|
import * as Methods from '../Methods.js';
|
|
@@ -32,7 +33,9 @@ import * as SponsorBudget from './SponsorBudget.js';
|
|
|
32
33
|
* ```
|
|
33
34
|
*/
|
|
34
35
|
export function charge(parameters = {}) {
|
|
35
|
-
const { amount, currency = defaults.resolveCurrency(parameters), decimals = defaults.decimals, description, externalId, feeToken: configuredFeeToken, feePayerPolicy, html,
|
|
36
|
+
const { machineTokenEnabled, allowedFeeTokens: configuredAllowedFeeTokens, amount, currency = defaults.resolveCurrency(parameters), decimals = defaults.decimals, description, externalId, feeToken: configuredFeeToken, feePayerPolicy, html, relay, sponsorBudget, splits, supportedModes, validateSender, waitForConfirmation = true, } = parameters;
|
|
37
|
+
if (configuredAllowedFeeTokens?.length === 0)
|
|
38
|
+
throw new Error('`allowedFeeTokens` must contain at least one token.');
|
|
36
39
|
const storeKeyPrefix = parameters.storeKeyPrefix ?? '';
|
|
37
40
|
const rawStore = (parameters.store ?? Store.memory());
|
|
38
41
|
const store = Store.from(rawStore, { keyPrefix: storeKeyPrefix });
|
|
@@ -83,36 +86,45 @@ export function charge(parameters = {}) {
|
|
|
83
86
|
const supportedModes = methodDetails?.supportedModes;
|
|
84
87
|
const currency = resolvedRequest.currency;
|
|
85
88
|
const recipient = resolvedRequest.recipient;
|
|
86
|
-
const
|
|
89
|
+
const machineTokenEnabled = methodDetails?.machineTokenEnabled === true;
|
|
90
|
+
const settlementSender = machineTokenEnabled
|
|
91
|
+
? MachineTokenCharge.getSettlementSender(chainId)
|
|
92
|
+
: undefined;
|
|
87
93
|
const isZeroAmount = BigInt(amount) === 0n;
|
|
88
94
|
Expires.assert(challenge.expires, challenge.id);
|
|
89
95
|
if (isZeroAmount && payload.type !== 'proof')
|
|
90
96
|
throw new MismatchError('Zero-amount challenges require a proof credential.', {});
|
|
97
|
+
// Request-level local sponsorship takes precedence over the hosted provider.
|
|
98
|
+
const localFeePayer = Account.is(request.feePayer) ? request.feePayer : feePayer;
|
|
99
|
+
const allowedFeeTokens = configuredAllowedFeeTokens ??
|
|
100
|
+
(localFeePayer || !remoteFeePayer ? FeePayer.defaultAllowedFeeTokens(chainId) : undefined);
|
|
91
101
|
return {
|
|
102
|
+
allowedFeeTokens,
|
|
92
103
|
amount,
|
|
104
|
+
machineTokenEnabled,
|
|
93
105
|
chainId,
|
|
94
106
|
challenge,
|
|
95
107
|
client: await getClient({ chainId }),
|
|
96
108
|
currency,
|
|
97
109
|
isZeroAmount,
|
|
98
|
-
memo,
|
|
99
110
|
methodDetails,
|
|
100
111
|
payload,
|
|
101
112
|
recipient,
|
|
102
113
|
requestAllowsFeePayer: request.feePayer !== false,
|
|
103
114
|
resolvedRequest,
|
|
115
|
+
settlementSenders: settlementSender ? [settlementSender] : [],
|
|
104
116
|
supportedModes,
|
|
105
117
|
};
|
|
106
118
|
}
|
|
107
119
|
async function validateHashCredential(credential, payload, context) {
|
|
108
|
-
const { amount, chainId, challenge, client, currency,
|
|
120
|
+
const { amount, chainId, challenge, client, currency, methodDetails, recipient, settlementSenders, supportedModes, } = context;
|
|
109
121
|
if (supportedModes && !supportedModes.includes('push'))
|
|
110
122
|
throw new MismatchError('Hash credentials are not supported for this challenge.', {});
|
|
111
123
|
const source = parseHashCredentialSource({
|
|
112
124
|
chainId: chainId ?? client.chain?.id,
|
|
113
125
|
source: credential.source,
|
|
114
126
|
});
|
|
115
|
-
const transfers = getExpectedTransfers({ amount,
|
|
127
|
+
const transfers = getExpectedTransfers({ amount, methodDetails, recipient });
|
|
116
128
|
const receipt = await getTransactionReceipt(client, {
|
|
117
129
|
hash: payload.hash,
|
|
118
130
|
});
|
|
@@ -120,15 +132,15 @@ export function charge(parameters = {}) {
|
|
|
120
132
|
const matchedLogs = await assertTransferLogs(receipt, {
|
|
121
133
|
currency,
|
|
122
134
|
sender,
|
|
135
|
+
settlementSenders,
|
|
123
136
|
source,
|
|
124
137
|
transfers,
|
|
125
138
|
validateSender,
|
|
126
139
|
});
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
});
|
|
140
|
+
assertChallengeBoundMemo(matchedLogs, {
|
|
141
|
+
challengeId: challenge.id,
|
|
142
|
+
realm: challenge.realm,
|
|
143
|
+
});
|
|
132
144
|
return { receipt: toReceipt(receipt), sender, transfers };
|
|
133
145
|
}
|
|
134
146
|
async function validateProofCredential(credential, payload, context) {
|
|
@@ -169,7 +181,7 @@ export function charge(parameters = {}) {
|
|
|
169
181
|
return { sender: source.address };
|
|
170
182
|
}
|
|
171
183
|
async function validateTransactionCredential(credential, payload, request, context) {
|
|
172
|
-
const { amount, chainId, challenge, client, currency,
|
|
184
|
+
const { machineTokenEnabled, amount, chainId, challenge, client, currency, methodDetails, recipient, requestAllowsFeePayer, supportedModes, } = context;
|
|
173
185
|
if (supportedModes && !supportedModes.includes('pull'))
|
|
174
186
|
throw new MismatchError('Transaction credentials are not supported for this challenge.', {});
|
|
175
187
|
const serializedTransaction = payload.signature;
|
|
@@ -181,25 +193,40 @@ export function charge(parameters = {}) {
|
|
|
181
193
|
const isFeePayerTx = methodDetails?.feePayer === true &&
|
|
182
194
|
requestAllowsFeePayer &&
|
|
183
195
|
!!(Account.is(request.feePayer) ? request.feePayer : feePayer || remoteFeePayer);
|
|
184
|
-
const transfers = getExpectedTransfers({ amount,
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
196
|
+
const transfers = getExpectedTransfers({ amount, methodDetails, recipient });
|
|
197
|
+
const machineTokenRoute = machineTokenEnabled
|
|
198
|
+
? MachineTokenCharge.matchRoute({
|
|
199
|
+
calls: transaction.calls ?? [],
|
|
200
|
+
chainId: chainId ?? client.chain?.id,
|
|
201
|
+
currency,
|
|
202
|
+
transfers,
|
|
203
|
+
})
|
|
204
|
+
: undefined;
|
|
205
|
+
const matchedCalls = machineTokenRoute?.transfers ??
|
|
206
|
+
assertTransferCalls(transaction.calls ?? [], {
|
|
207
|
+
currency,
|
|
208
|
+
exactCount: isFeePayerTx,
|
|
209
|
+
transfers,
|
|
194
210
|
});
|
|
211
|
+
assertChallengeBoundCallMemo(matchedCalls, {
|
|
212
|
+
challengeId: challenge.id,
|
|
213
|
+
realm: challenge.realm,
|
|
214
|
+
});
|
|
195
215
|
if (isFeePayerTx) {
|
|
196
|
-
|
|
197
|
-
|
|
216
|
+
if (!machineTokenRoute)
|
|
217
|
+
FeePayer.validateCalls(transaction.calls, { amount, currency, recipient }, { currency, expectedTransfers: transfers });
|
|
218
|
+
FeePayer.assertAllowedFeeToken(transaction, context.allowedFeeTokens);
|
|
198
219
|
}
|
|
199
220
|
else {
|
|
200
221
|
await viem_call(client, FeePayer.simulationTransaction(transaction, { feePayer: false }));
|
|
201
222
|
}
|
|
202
|
-
return {
|
|
223
|
+
return {
|
|
224
|
+
isFeePayerTx,
|
|
225
|
+
serializedTransaction,
|
|
226
|
+
settlementSenders: machineTokenRoute ? [machineTokenRoute.settlementSender] : [],
|
|
227
|
+
transaction,
|
|
228
|
+
transfers,
|
|
229
|
+
};
|
|
203
230
|
}
|
|
204
231
|
async function validateCredential(credential, request, context) {
|
|
205
232
|
switch (context.payload.type) {
|
|
@@ -217,7 +244,7 @@ export function charge(parameters = {}) {
|
|
|
217
244
|
return { details: { mode: 'proof', sender }, type: 'proof' };
|
|
218
245
|
}
|
|
219
246
|
case 'transaction': {
|
|
220
|
-
const { isFeePayerTx, serializedTransaction, transaction, transfers } = await validateTransactionCredential(credential, context.payload, request, context);
|
|
247
|
+
const { isFeePayerTx, serializedTransaction, settlementSenders, transaction, transfers } = await validateTransactionCredential(credential, context.payload, request, context);
|
|
221
248
|
return {
|
|
222
249
|
details: {
|
|
223
250
|
mode: 'pull',
|
|
@@ -227,6 +254,7 @@ export function charge(parameters = {}) {
|
|
|
227
254
|
},
|
|
228
255
|
isFeePayerTx,
|
|
229
256
|
serializedTransaction,
|
|
257
|
+
settlementSenders,
|
|
230
258
|
transaction,
|
|
231
259
|
transfers,
|
|
232
260
|
type: 'transaction',
|
|
@@ -238,12 +266,12 @@ export function charge(parameters = {}) {
|
|
|
238
266
|
canOffer: parameters.canOffer,
|
|
239
267
|
onPaymentSuccess: parameters.onPaymentSuccess,
|
|
240
268
|
defaults: {
|
|
269
|
+
machineTokenEnabled,
|
|
241
270
|
amount,
|
|
242
271
|
currency,
|
|
243
272
|
decimals,
|
|
244
273
|
description,
|
|
245
274
|
externalId,
|
|
246
|
-
memo,
|
|
247
275
|
recipient,
|
|
248
276
|
splits,
|
|
249
277
|
supportedModes,
|
|
@@ -284,6 +312,8 @@ export function charge(parameters = {}) {
|
|
|
284
312
|
const chainId = await (async () => {
|
|
285
313
|
if (request.chainId)
|
|
286
314
|
return request.chainId;
|
|
315
|
+
if (parameters.chainId !== undefined)
|
|
316
|
+
return parameters.chainId;
|
|
287
317
|
if (parameters.testnet)
|
|
288
318
|
return defaults.chainId.testnet;
|
|
289
319
|
return (await getClient({})).chain?.id;
|
|
@@ -298,6 +328,13 @@ export function charge(parameters = {}) {
|
|
|
298
328
|
})();
|
|
299
329
|
if (client.chain?.id !== chainId)
|
|
300
330
|
throw new Error(`Client not configured with chainId ${chainId}.`);
|
|
331
|
+
if (MachineTokenCharge.isMachineToken({
|
|
332
|
+
chainId,
|
|
333
|
+
currency: request.currency,
|
|
334
|
+
}))
|
|
335
|
+
throw new Error('MACH cannot be advertised as a charge currency. Advertise the settlement currency with `machineTokenEnabled: true`.');
|
|
336
|
+
if (request.machineTokenEnabled && !MachineTokenCharge.isSupported(chainId))
|
|
337
|
+
throw new Error(`Machine tokens are not supported on chainId ${chainId}.`);
|
|
301
338
|
const resolvedFeePayer = (() => {
|
|
302
339
|
if (request.feePayer === false)
|
|
303
340
|
return credential ? false : undefined;
|
|
@@ -308,9 +345,9 @@ export function charge(parameters = {}) {
|
|
|
308
345
|
})();
|
|
309
346
|
return {
|
|
310
347
|
...request,
|
|
348
|
+
machineTokenEnabled: request.machineTokenEnabled || undefined,
|
|
311
349
|
chainId,
|
|
312
350
|
feePayer: resolvedFeePayer,
|
|
313
|
-
memo: request.memo || undefined,
|
|
314
351
|
};
|
|
315
352
|
},
|
|
316
353
|
async validate({ credential, request }) {
|
|
@@ -328,7 +365,7 @@ export function charge(parameters = {}) {
|
|
|
328
365
|
},
|
|
329
366
|
async broadcast({ credential, request }) {
|
|
330
367
|
const context = await resolveCredentialContext({ credential, request });
|
|
331
|
-
const { amount, chainId, challenge, client, currency,
|
|
368
|
+
const { amount, chainId, challenge, client, currency, methodDetails, recipient, requestAllowsFeePayer, } = context;
|
|
332
369
|
const validated = await validateCredential(credential, request, context);
|
|
333
370
|
const feePayerAccount = methodDetails?.feePayer === true && requestAllowsFeePayer
|
|
334
371
|
? Account.is(request.feePayer)
|
|
@@ -362,7 +399,7 @@ export function charge(parameters = {}) {
|
|
|
362
399
|
};
|
|
363
400
|
}
|
|
364
401
|
case 'transaction': {
|
|
365
|
-
const { isFeePayerTx, serializedTransaction, transaction, transfers } = validated;
|
|
402
|
+
const { isFeePayerTx, serializedTransaction, settlementSenders, transaction, transfers } = validated;
|
|
366
403
|
// Pre-broadcast dedup: catch exact byte-for-byte replays early.
|
|
367
404
|
const hash = keccak256(serializedTransaction);
|
|
368
405
|
if (!(await tryClaimHash(store, hash, replayExpires))) {
|
|
@@ -374,12 +411,10 @@ export function charge(parameters = {}) {
|
|
|
374
411
|
let finalHash;
|
|
375
412
|
let reservation;
|
|
376
413
|
try {
|
|
377
|
-
const allowedFeeTokens =
|
|
378
|
-
if (isFeePayerTx)
|
|
379
|
-
FeePayer.assertAllowedFeeToken(transaction, allowedFeeTokens);
|
|
380
|
-
const selectableFeeTokens = allowedFeeTokens;
|
|
414
|
+
const { allowedFeeTokens } = context;
|
|
381
415
|
const completedTransaction = await (async () => {
|
|
382
416
|
if (feePayerAccount && methodDetails?.feePayer !== false) {
|
|
417
|
+
const selectableFeeTokens = allowedFeeTokens ?? FeePayer.defaultAllowedFeeTokens(chainId);
|
|
383
418
|
const completed = await FeePayer.preflightSponsorship({
|
|
384
419
|
transaction,
|
|
385
420
|
simulate: (request) => viem_call(client, request),
|
|
@@ -394,7 +429,7 @@ export function charge(parameters = {}) {
|
|
|
394
429
|
}));
|
|
395
430
|
const sponsored = FeePayer.prepareSponsoredTransaction({
|
|
396
431
|
account: feePayerAccount,
|
|
397
|
-
allowedFeeTokens,
|
|
432
|
+
allowedFeeTokens: selectableFeeTokens,
|
|
398
433
|
challengeExpires: expires,
|
|
399
434
|
chainId: chainId ?? client.chain.id,
|
|
400
435
|
details: { amount, currency, recipient },
|
|
@@ -495,13 +530,13 @@ export function charge(parameters = {}) {
|
|
|
495
530
|
const matchedLogs = await assertTransferLogs(receipt, {
|
|
496
531
|
currency,
|
|
497
532
|
sender: transaction.from,
|
|
533
|
+
settlementSenders,
|
|
498
534
|
transfers,
|
|
499
535
|
});
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
});
|
|
536
|
+
assertChallengeBoundMemo(matchedLogs, {
|
|
537
|
+
challengeId: challenge.id,
|
|
538
|
+
realm: challenge.realm,
|
|
539
|
+
});
|
|
505
540
|
if (receipt.transactionHash.toLowerCase() !== finalHash.toLowerCase())
|
|
506
541
|
throw new VerificationFailedError({
|
|
507
542
|
reason: 'Broadcast transaction hash does not match the signed transaction',
|
|
@@ -550,16 +585,16 @@ function chargeBinding(request) {
|
|
|
550
585
|
// Exhaustively destructure so new charge request fields require an explicit binding decision.
|
|
551
586
|
const { amount, currency, description, externalId, methodDetails, recipient, ...requestRest } = request;
|
|
552
587
|
requestRest;
|
|
553
|
-
const { chainId, feePayer,
|
|
588
|
+
const { chainId, feePayer, machineTokenEnabled, splits, supportedModes, ...methodDetailsRest } = methodDetails ?? {};
|
|
554
589
|
methodDetailsRest;
|
|
555
590
|
void feePayer;
|
|
556
591
|
return {
|
|
592
|
+
machineTokenEnabled,
|
|
557
593
|
amount,
|
|
558
594
|
chainId,
|
|
559
595
|
currency,
|
|
560
596
|
description,
|
|
561
597
|
externalId,
|
|
562
|
-
memo,
|
|
563
598
|
recipient,
|
|
564
599
|
splits,
|
|
565
600
|
supportedModes,
|
|
@@ -569,7 +604,6 @@ function getExpectedTransfers(parameters) {
|
|
|
569
604
|
return Charge_internal.getTransfers({
|
|
570
605
|
amount: parameters.amount,
|
|
571
606
|
methodDetails: {
|
|
572
|
-
memo: parameters.memo,
|
|
573
607
|
splits: parameters.methodDetails?.splits,
|
|
574
608
|
},
|
|
575
609
|
recipient: parameters.recipient,
|
|
@@ -695,7 +729,9 @@ async function assertTransferLogs(receipt, parameters) {
|
|
|
695
729
|
if (!(await isValidTransferSender({
|
|
696
730
|
expectedSender: parameters.sender,
|
|
697
731
|
sender: log.args.from,
|
|
732
|
+
settlementSenders: parameters.settlementSenders,
|
|
698
733
|
source: parameters.source,
|
|
734
|
+
transactionSender: receipt.from,
|
|
699
735
|
validateSender: parameters.validateSender,
|
|
700
736
|
})))
|
|
701
737
|
continue;
|
|
@@ -770,6 +806,9 @@ function isSameTransferLog(a, b) {
|
|
|
770
806
|
async function isValidTransferSender(parameters) {
|
|
771
807
|
if (TempoAddress.isEqual(parameters.sender, parameters.expectedSender))
|
|
772
808
|
return true;
|
|
809
|
+
if (TempoAddress.isEqual(parameters.transactionSender, parameters.expectedSender) &&
|
|
810
|
+
parameters.settlementSenders?.some((sender) => TempoAddress.isEqual(parameters.sender, sender)))
|
|
811
|
+
return true;
|
|
773
812
|
if (!parameters.validateSender)
|
|
774
813
|
return false;
|
|
775
814
|
return parameters.validateSender({
|
|
@@ -869,34 +908,29 @@ function toReceipt(receipt) {
|
|
|
869
908
|
reference: transactionHash,
|
|
870
909
|
};
|
|
871
910
|
}
|
|
872
|
-
/**
|
|
873
|
-
* Asserts that at least one of the matched payment logs carries a
|
|
874
|
-
* challenge-bound memo nonce (keccak256(challengeId)[0..6] in bytes 25–31).
|
|
875
|
-
* Only checks logs that were matched by `assertTransferLogs`, not the
|
|
876
|
-
* entire receipt — preventing unrelated dust transfers from satisfying
|
|
877
|
-
* the binding.
|
|
878
|
-
* @internal
|
|
879
|
-
*/
|
|
880
911
|
function assertChallengeBoundMemo(matchedLogs, parameters) {
|
|
881
|
-
|
|
882
|
-
if (log.kind !== 'memo')
|
|
883
|
-
return false;
|
|
884
|
-
if (!Attribution.verifyServer(log.args.memo, parameters.realm))
|
|
885
|
-
return false;
|
|
886
|
-
return Attribution.verifyChallengeBinding(log.args.memo, parameters.challengeId);
|
|
887
|
-
});
|
|
888
|
-
if (!bound)
|
|
889
|
-
throw new MismatchError('Payment verification failed: memo is not bound to this challenge.', {});
|
|
912
|
+
assertChallengeBoundMemos(matchedLogs.map((log) => (log.kind === 'memo' ? log.args.memo : undefined)), parameters);
|
|
890
913
|
}
|
|
891
914
|
function assertChallengeBoundCallMemo(matchedCalls, parameters) {
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
915
|
+
assertChallengeBoundMemos(matchedCalls.map((call) => call.memo), parameters);
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Requires a challenge-bound attribution memo and rejects conflicting attribution
|
|
919
|
+
* on matched payment transfers. Otherwise, a split payment could authorize another
|
|
920
|
+
* challenge after the first challenge's replay marker expires. Unmatched transfers
|
|
921
|
+
* cannot establish the binding; plain transfers and non-MPP memos remain allowed.
|
|
922
|
+
* @internal
|
|
923
|
+
*/
|
|
924
|
+
function assertChallengeBoundMemos(memos, parameters) {
|
|
925
|
+
let bound = false;
|
|
926
|
+
for (const memo of memos) {
|
|
927
|
+
if (!memo || !Attribution.isMppMemo(memo))
|
|
928
|
+
continue;
|
|
929
|
+
if (!Attribution.verifyServer(memo, parameters.realm) ||
|
|
930
|
+
!Attribution.verifyChallengeBinding(memo, parameters.challengeId))
|
|
931
|
+
throw new MismatchError('Payment verification failed: memo is not bound to this challenge.', {});
|
|
932
|
+
bound = true;
|
|
933
|
+
}
|
|
900
934
|
if (!bound)
|
|
901
935
|
throw new MismatchError('Payment verification failed: memo is not bound to this challenge.', {});
|
|
902
936
|
}
|