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.
- package/CHANGELOG.md +16 -0
- package/dist/Errors.d.ts +10 -0
- package/dist/Errors.d.ts.map +1 -1
- package/dist/Errors.js +11 -2
- package/dist/Errors.js.map +1 -1
- package/dist/Method.d.ts +105 -3
- package/dist/Method.d.ts.map +1 -1
- package/dist/Method.js +102 -2
- package/dist/Method.js.map +1 -1
- package/dist/client/internal/Fetch.d.ts.map +1 -1
- package/dist/client/internal/Fetch.js +12 -2
- package/dist/client/internal/Fetch.js.map +1 -1
- package/dist/client/internal/MethodChallenge.d.ts +17 -0
- package/dist/client/internal/MethodChallenge.d.ts.map +1 -0
- package/dist/client/internal/MethodChallenge.js +11 -0
- package/dist/client/internal/MethodChallenge.js.map +1 -0
- package/dist/server/Mppx.d.ts +23 -4
- package/dist/server/Mppx.d.ts.map +1 -1
- package/dist/server/Mppx.js +91 -82
- package/dist/server/Mppx.js.map +1 -1
- package/dist/tempo/client/Methods.d.ts +1 -0
- package/dist/tempo/client/Methods.d.ts.map +1 -1
- package/dist/tempo/internal/fee-payer.d.ts +1 -0
- package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
- package/dist/tempo/internal/fee-payer.js +7 -2
- package/dist/tempo/internal/fee-payer.js.map +1 -1
- package/dist/tempo/legacy/client/ChannelOps.d.ts.map +1 -1
- package/dist/tempo/legacy/client/ChannelOps.js +2 -1
- package/dist/tempo/legacy/client/ChannelOps.js.map +1 -1
- package/dist/tempo/server/Charge.d.ts +38 -77
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js +289 -209
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/Methods.d.ts +12 -1
- package/dist/tempo/server/Methods.d.ts.map +1 -1
- package/dist/tempo/server/Methods.js +4 -0
- package/dist/tempo/server/Methods.js.map +1 -1
- package/dist/tempo/server/Relay.d.ts +48 -0
- package/dist/tempo/server/Relay.d.ts.map +1 -0
- package/dist/tempo/server/Relay.js +177 -0
- package/dist/tempo/server/Relay.js.map +1 -0
- package/dist/tempo/server/SponsorBudget.d.ts +59 -0
- package/dist/tempo/server/SponsorBudget.d.ts.map +1 -0
- package/dist/tempo/server/SponsorBudget.js +144 -0
- package/dist/tempo/server/SponsorBudget.js.map +1 -0
- package/dist/tempo/server/internal/html.gen.d.ts +1 -1
- package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/server/internal/html.gen.js.map +1 -1
- package/dist/tempo/session/client/ChannelOps.d.ts +7 -1
- package/dist/tempo/session/client/ChannelOps.d.ts.map +1 -1
- package/dist/tempo/session/client/ChannelOps.js +9 -4
- package/dist/tempo/session/client/ChannelOps.js.map +1 -1
- package/dist/tempo/session/client/CredentialState.d.ts +5 -1
- package/dist/tempo/session/client/CredentialState.d.ts.map +1 -1
- package/dist/tempo/session/client/CredentialState.js +37 -19
- package/dist/tempo/session/client/CredentialState.js.map +1 -1
- package/dist/tempo/session/client/Runtime.d.ts +8 -0
- package/dist/tempo/session/client/Runtime.d.ts.map +1 -1
- package/dist/tempo/session/client/Runtime.js +17 -0
- package/dist/tempo/session/client/Runtime.js.map +1 -1
- package/dist/tempo/session/client/Session.d.ts +15 -5
- package/dist/tempo/session/client/Session.d.ts.map +1 -1
- package/dist/tempo/session/client/Session.js +93 -36
- package/dist/tempo/session/client/Session.js.map +1 -1
- package/dist/tempo/session/client/SessionManager.d.ts +8 -1
- package/dist/tempo/session/client/SessionManager.d.ts.map +1 -1
- package/dist/tempo/session/client/SessionManager.js +54 -26
- package/dist/tempo/session/client/SessionManager.js.map +1 -1
- package/dist/tempo/session/client/Transports.d.ts +7 -5
- package/dist/tempo/session/client/Transports.d.ts.map +1 -1
- package/dist/tempo/session/client/Transports.js +48 -24
- package/dist/tempo/session/client/Transports.js.map +1 -1
- package/dist/tempo/session/precompile/Chain.d.ts.map +1 -1
- package/dist/tempo/session/precompile/Chain.js +96 -8
- package/dist/tempo/session/precompile/Chain.js.map +1 -1
- package/dist/tempo/session/precompile/Channel.d.ts +2 -0
- package/dist/tempo/session/precompile/Channel.d.ts.map +1 -1
- package/dist/tempo/session/precompile/Channel.js +4 -0
- package/dist/tempo/session/precompile/Channel.js.map +1 -1
- package/dist/tempo/session/server/RequestState.js +3 -1
- package/dist/tempo/session/server/RequestState.js.map +1 -1
- package/package.json +1 -1
- package/src/Errors.test.ts +23 -0
- package/src/Errors.ts +21 -2
- package/src/Method.test.ts +102 -1
- package/src/Method.ts +241 -5
- package/src/client/Mppx.test-d.ts +3 -3
- package/src/client/internal/Fetch.ts +16 -7
- package/src/client/internal/MethodChallenge.ts +30 -0
- package/src/server/Mppx.test-d.ts +2 -0
- package/src/server/Mppx.test.ts +205 -0
- package/src/server/Mppx.ts +167 -103
- package/src/tempo/PublicExports.test-d.ts +20 -0
- package/src/tempo/internal/fee-payer.ts +7 -3
- package/src/tempo/legacy/client/ChannelOps.test.ts +7 -0
- package/src/tempo/legacy/client/ChannelOps.ts +2 -1
- package/src/tempo/server/Charge.test.ts +337 -63
- package/src/tempo/server/Charge.ts +435 -267
- package/src/tempo/server/Methods.ts +11 -0
- package/src/tempo/server/Relay.test.ts +523 -0
- package/src/tempo/server/Relay.ts +288 -0
- package/src/tempo/server/SponsorBudget.test.ts +125 -0
- package/src/tempo/server/SponsorBudget.ts +213 -0
- package/src/tempo/server/internal/html.gen.ts +1 -1
- package/src/tempo/session/client/ChannelOps.test.ts +17 -2
- package/src/tempo/session/client/ChannelOps.ts +12 -4
- package/src/tempo/session/client/CredentialState.ts +61 -20
- package/src/tempo/session/client/Runtime.test.ts +12 -0
- package/src/tempo/session/client/Runtime.ts +22 -0
- package/src/tempo/session/client/Session.test.ts +235 -2
- package/src/tempo/session/client/Session.ts +130 -41
- package/src/tempo/session/client/SessionManager.test.ts +102 -4
- package/src/tempo/session/client/SessionManager.ts +71 -27
- package/src/tempo/session/client/Transports.test.ts +32 -1
- package/src/tempo/session/client/Transports.ts +79 -35
- package/src/tempo/session/precompile/Chain.integration.test.ts +19 -1
- package/src/tempo/session/precompile/Chain.test.ts +197 -10
- package/src/tempo/session/precompile/Chain.ts +102 -8
- package/src/tempo/session/precompile/Channel.test.ts +9 -0
- package/src/tempo/session/precompile/Channel.ts +5 -0
- package/src/tempo/session/server/RequestState.test.ts +8 -1
- 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":"
|
|
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
|
|
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
|
-
|
|
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
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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({
|
|
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({
|
|
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
|
-
|
|
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({
|
|
360
|
+
throw new VerificationFailedError({
|
|
361
|
+
reason: 'Transaction hash has already been used',
|
|
362
|
+
});
|
|
278
363
|
}
|
|
279
|
-
let
|
|
280
|
-
let
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
416
|
+
policy: transactionFeePayerPolicy,
|
|
364
417
|
transaction,
|
|
365
418
|
url: feePayerUrl,
|
|
366
419
|
});
|
|
367
420
|
return { ...hosted, transaction };
|
|
368
421
|
},
|
|
369
422
|
});
|
|
370
|
-
return
|
|
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
|
-
|
|
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: '
|
|
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
|
-
|
|
411
|
-
if (reference.toLowerCase() !== hash.toLowerCase() &&
|
|
412
|
-
!(await markHashUsed(store, reference))) {
|
|
505
|
+
if (reference.toLowerCase() !== finalHash.toLowerCase())
|
|
413
506
|
throw new VerificationFailedError({
|
|
414
|
-
reason: '
|
|
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 (
|
|
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
|