mppx 0.13.4 → 0.13.5

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 (40) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/Errors.d.ts +63 -2
  3. package/dist/Errors.js +52 -2
  4. package/dist/Method.d.ts +10 -0
  5. package/dist/cli/cli.js +133 -133
  6. package/dist/cli/plugins/index.js +12 -12
  7. package/dist/client/Mppx.d.ts +2 -0
  8. package/dist/client/Mppx.js +8 -10
  9. package/dist/client/internal/Fetch.js +107 -8
  10. package/dist/client/internal/MethodResponse.d.ts +6 -0
  11. package/dist/client/internal/MethodResponse.js +16 -0
  12. package/dist/client/internal/protocols/Mcp.js +36 -5
  13. package/dist/evm/server/Charge.js +3 -1
  14. package/dist/internal/version.d.ts +2 -2
  15. package/dist/internal/version.js +1 -1
  16. package/dist/internal/warnOnce.d.ts +3 -0
  17. package/dist/internal/warnOnce.js +9 -0
  18. package/dist/server/Mppx.d.ts +5 -0
  19. package/dist/server/Mppx.js +14 -13
  20. package/dist/server/Transport.js +10 -0
  21. package/dist/tempo/Attribution.d.ts +24 -9
  22. package/dist/tempo/Attribution.js +25 -5
  23. package/dist/tempo/client/Charge.d.ts +116 -10
  24. package/dist/tempo/client/Charge.js +116 -22
  25. package/dist/tempo/client/Methods.d.ts +88 -9
  26. package/dist/tempo/index.d.ts +1 -0
  27. package/dist/tempo/index.js +1 -0
  28. package/dist/tempo/internal/fee-payer.js +4 -0
  29. package/dist/tempo/server/Charge.d.ts +11 -0
  30. package/dist/tempo/server/Charge.js +160 -29
  31. package/dist/tempo/server/Methods.js +16 -4
  32. package/dist/tempo/server/Subscription.js +2 -1
  33. package/dist/tempo/server/internal/fallback-store.d.ts +9 -0
  34. package/dist/tempo/server/internal/fallback-store.js +13 -0
  35. package/dist/tempo/server/internal/html.gen.js +1 -1
  36. package/dist/tempo/session/precompile/Chain.d.ts +2 -0
  37. package/dist/tempo/session/precompile/Chain.js +3 -1
  38. package/dist/tempo/session/server/Session.js +10 -11
  39. package/dist/tempo/session/server/Settlement.js +11 -3
  40. package/package.json +1 -1
@@ -17,6 +17,8 @@ import { Bytes, Hash, Hex } from 'ox';
17
17
  *
18
18
  * The TAG prefix makes MPP transactions trivially distinguishable
19
19
  * from arbitrary memos via `TransferWithMemo` event topic filtering.
20
+ * The Tempo charge client uses the challenge `realm` as `serverId`, so
21
+ * `verifyServer(memo, realm)` matches on-chain transfers to a server.
20
22
  *
21
23
  * @module
22
24
  */
@@ -37,12 +39,12 @@ function fingerprint(value) {
37
39
  /**
38
40
  * Encodes an MPP attribution memo as a `bytes32` hex string.
39
41
  *
40
- * @param parameters - The serverId (server identity) and optional clientId.
42
+ * @param parameters - The challengeId, serverId (server identity), and optional clientId.
41
43
  * @returns A `0x`-prefixed 64-char hex string (32 bytes).
42
44
  *
43
45
  * @example
44
46
  * ```ts
45
- * import * as Attribution from './Attribution.js'
47
+ * import { Attribution } from 'mppx/tempo'
46
48
  *
47
49
  * const memo = Attribution.encode({ challengeId: 'challenge-123', clientId: 'my-app', serverId: 'api.example.com' })
48
50
  * ```
@@ -69,7 +71,7 @@ export function encode(parameters) {
69
71
  *
70
72
  * @example
71
73
  * ```ts
72
- * import * as Attribution from './Attribution.js'
74
+ * import { Attribution } from 'mppx/tempo'
73
75
  *
74
76
  * Attribution.isMppMemo(Attribution.encode({ challengeId: 'challenge-123', serverId: 'api.example.com' })) // true
75
77
  * Attribution.isMppMemo('0x0000...0000') // false
@@ -90,6 +92,15 @@ export function isMppMemo(memo) {
90
92
  * @param memo - A `0x`-prefixed hex string (bytes32).
91
93
  * @param serverId - The expected server identity.
92
94
  * @returns `true` if the memo has a valid MPP tag and matching server fingerprint.
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * import { Attribution } from 'mppx/tempo'
99
+ *
100
+ * const memo = Attribution.encode({ challengeId: 'challenge-123', serverId: 'api.example.com' })
101
+ * Attribution.verifyServer(memo, 'api.example.com') // true
102
+ * Attribution.verifyServer(memo, 'other.example.com') // false
103
+ * ```
93
104
  */
94
105
  export function verifyServer(memo, serverId) {
95
106
  if (!isMppMemo(memo))
@@ -106,7 +117,7 @@ export function verifyServer(memo, serverId) {
106
117
  *
107
118
  * @example
108
119
  * ```ts
109
- * import * as Attribution from './Attribution.js'
120
+ * import { Attribution } from 'mppx/tempo'
110
121
  *
111
122
  * const memo = Attribution.encode({ challengeId: 'challenge-123', clientId: 'my-app', serverId: 'api.example.com' })
112
123
  * const decoded = Attribution.decode(memo)
@@ -127,7 +138,7 @@ export function decode(memo) {
127
138
  * Computes the 7-byte challenge-bound nonce: keccak256(challengeId)[0..6].
128
139
  * @internal
129
140
  */
130
- export function challengeNonce(challengeId) {
141
+ function challengeNonce(challengeId) {
131
142
  const hash = Hash.keccak256(Bytes.fromString(challengeId), { as: 'Hex' });
132
143
  return Hex.toBytes(Hex.slice(hash, 0, 7));
133
144
  }
@@ -139,6 +150,15 @@ export function challengeNonce(challengeId) {
139
150
  * @param memo - A `0x`-prefixed hex string (bytes32).
140
151
  * @param challengeId - The expected challenge ID.
141
152
  * @returns `true` if the memo has a valid MPP tag and matching challenge nonce.
153
+ *
154
+ * @example
155
+ * ```ts
156
+ * import { Attribution } from 'mppx/tempo'
157
+ *
158
+ * const memo = Attribution.encode({ challengeId: 'challenge-123', serverId: 'api.example.com' })
159
+ * Attribution.verifyChallengeBinding(memo, 'challenge-123') // true
160
+ * Attribution.verifyChallengeBinding(memo, 'challenge-456') // false
161
+ * ```
142
162
  */
143
163
  export function verifyChallengeBinding(memo, challengeId) {
144
164
  const decoded = decode(memo);
@@ -1,4 +1,4 @@
1
- import type { Address } from 'viem';
1
+ import { type Address } from 'viem';
2
2
  import * as Method from '../../Method.js';
3
3
  import * as Account from '../../viem/Account.js';
4
4
  import * as Client from '../../viem/Client.js';
@@ -25,7 +25,16 @@ export type ChargeContext = {
25
25
  * })
26
26
  * ```
27
27
  */
28
- export declare function charge(parameters?: charge.Parameters): Method.Client<{
28
+ export declare function charge(parameters?: charge.Parameters): {
29
+ readonly canHandleChallenge?: Method.CanHandleChallengeFn | undefined;
30
+ readonly context?: z.ZodMiniObject<{
31
+ account: z.ZodMiniOptional<z.ZodMiniCustom<`0x${string}` | Account.Account | undefined, `0x${string}` | Account.Account | undefined>>;
32
+ autoSwap: z.ZodMiniOptional<z.ZodMiniCustom<AutoSwap.resolve.Value | undefined, AutoSwap.resolve.Value | undefined>>;
33
+ mode: z.ZodMiniOptional<z.ZodMiniEnum<{
34
+ pull: "pull";
35
+ push: "push";
36
+ }>>;
37
+ }, z.core.$strip> | undefined;
29
38
  readonly name: "tempo";
30
39
  readonly intent: "charge";
31
40
  readonly schema: {
@@ -97,14 +106,84 @@ export declare function charge(parameters?: charge.Parameters): Method.Client<{
97
106
  supportedModes?: ("pull" | "push")[] | undefined;
98
107
  }>>;
99
108
  };
100
- }, z.ZodMiniObject<{
101
- account: z.ZodMiniOptional<z.ZodMiniCustom<`0x${string}` | Account.Account | undefined, `0x${string}` | Account.Account | undefined>>;
102
- autoSwap: z.ZodMiniOptional<z.ZodMiniCustom<AutoSwap.resolve.Value | undefined, AutoSwap.resolve.Value | undefined>>;
103
- mode: z.ZodMiniOptional<z.ZodMiniEnum<{
104
- pull: "pull";
105
- push: "push";
106
- }>>;
107
- }, z.core.$strip>>;
109
+ readonly createCredential: Method.CreateCredentialFn<{
110
+ readonly name: "tempo";
111
+ readonly intent: "charge";
112
+ readonly schema: {
113
+ credential: {
114
+ payload: z.ZodMiniDiscriminatedUnion<[z.ZodMiniObject<{
115
+ hash: z.ZodMiniString<string>;
116
+ type: z.ZodMiniLiteral<"hash">;
117
+ }, z.core.$strip>, z.ZodMiniObject<{
118
+ signature: z.ZodMiniString<string>;
119
+ type: z.ZodMiniLiteral<"transaction">;
120
+ }, z.core.$strip>, z.ZodMiniObject<{
121
+ signature: z.ZodMiniString<string>;
122
+ type: z.ZodMiniLiteral<"proof">;
123
+ }, z.core.$strip>], "type">;
124
+ };
125
+ request: z.ZodMiniPipe<z.ZodMiniObject<{
126
+ machineTokenEnabled: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
127
+ amount: z.ZodMiniString<string>;
128
+ chainId: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
129
+ currency: z.ZodMiniString<string>;
130
+ decimals: z.ZodMiniNumber<number>;
131
+ description: z.ZodMiniOptional<z.ZodMiniString<string>>;
132
+ externalId: z.ZodMiniOptional<z.ZodMiniString<string>>;
133
+ feePayer: z.ZodMiniOptional<z.ZodMiniPipe<z.ZodMiniUnion<readonly [z.ZodMiniBoolean<boolean>, z.ZodMiniCustom<import("viem").Account, import("viem").Account>]>, z.ZodMiniTransform<boolean, boolean | import("viem").Account>>>;
134
+ memo: z.ZodMiniOptional<z.ZodMiniNever>;
135
+ recipient: z.ZodMiniOptional<z.ZodMiniString<string>>;
136
+ splits: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniObject<{
137
+ amount: z.ZodMiniString<string>;
138
+ memo: z.ZodMiniOptional<z.ZodMiniString<string>>;
139
+ recipient: z.ZodMiniPipe<z.ZodMiniString<string>, z.ZodMiniTransform<`0x${string}`, string>>;
140
+ }, z.core.$strip>>>;
141
+ supportedModes: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniEnum<{
142
+ pull: "pull";
143
+ push: "push";
144
+ }>>>;
145
+ }, z.core.$strip>, z.ZodMiniTransform<{
146
+ currency: string;
147
+ description?: string | undefined;
148
+ externalId?: string | undefined;
149
+ recipient?: string | undefined;
150
+ amount: string;
151
+ methodDetails?: {
152
+ machineTokenEnabled?: boolean | undefined;
153
+ chainId?: number | undefined;
154
+ feePayer?: boolean | undefined;
155
+ splits?: {
156
+ memo?: string | undefined;
157
+ recipient: `0x${string}`;
158
+ amount: string;
159
+ }[] | undefined;
160
+ supportedModes?: ("pull" | "push")[] | undefined;
161
+ } | undefined;
162
+ }, {
163
+ machineTokenEnabled?: boolean | undefined;
164
+ amount: string;
165
+ chainId?: number | undefined;
166
+ currency: string;
167
+ decimals: number;
168
+ description?: string | undefined;
169
+ externalId?: string | undefined;
170
+ feePayer?: boolean | undefined;
171
+ memo?: undefined;
172
+ recipient?: string | undefined;
173
+ splits?: {
174
+ amount: string;
175
+ memo?: string | undefined;
176
+ recipient: `0x${string}`;
177
+ }[] | undefined;
178
+ supportedModes?: ("pull" | "push")[] | undefined;
179
+ }>>;
180
+ };
181
+ }, {
182
+ account?: `0x${string}` | Account.Account | undefined;
183
+ autoSwap?: AutoSwap.resolve.Value | undefined;
184
+ mode?: "pull" | "push" | undefined;
185
+ }>;
186
+ };
108
187
  export declare namespace charge {
109
188
  type AutoSwap = AutoSwap.resolve.Value;
110
189
  type Context = ChargeContext;
@@ -137,6 +216,33 @@ export declare namespace charge {
137
216
  * recipient and every split recipient must be included in this list.
138
217
  */
139
218
  expectedRecipients?: readonly Address[] | undefined;
219
+ /**
220
+ * Allowlist of TIP-20 currency addresses this client will pay in, compared
221
+ * case-insensitively. Offers in other currencies are skipped during selection
222
+ * and rejected before signing; an empty list rejects all. Required with
223
+ * `maxAmount` or `maxAtomicAmount`, which are denominated in the challenge currency.
224
+ */
225
+ currencies?: readonly Address[] | undefined;
226
+ /**
227
+ * Decimals of the `currencies`, used to parse `maxAmount`. Required with
228
+ * `maxAmount` because challenges do not carry currency decimals.
229
+ */
230
+ decimals?: number | undefined;
231
+ /**
232
+ * Maximum display-unit amount (e.g. `'1.5'`) the client is willing to pay per
233
+ * charge, parsed with `decimals`. Values with more fractional digits than
234
+ * `decimals` are rejected rather than rounded. Compared against the challenge
235
+ * amount, which includes splits; larger offers are skipped during selection and
236
+ * rejected before signing. Applies per credential, not cumulatively across
237
+ * payment retries. Requires `currencies`.
238
+ */
239
+ maxAmount?: string | undefined;
240
+ /**
241
+ * Maximum amount the client is willing to pay per charge, in raw base units of
242
+ * the challenge currency (compared directly with the challenge `amount`).
243
+ * Checked like `maxAmount`. Requires `currencies`.
244
+ */
245
+ maxAtomicAmount?: string | undefined;
140
246
  /**
141
247
  * Controls how the charge transaction is submitted.
142
248
  *
@@ -1,6 +1,9 @@
1
+ import { TxEnvelopeTempo } from 'ox/tempo';
2
+ import { keccak256, parseUnits } from 'viem';
1
3
  import { prepareTransactionRequest, sendCallsSync, sendTransactionSync, signTypedData, signTransaction, } from 'viem/actions';
2
4
  import { Actions } from 'viem/tempo';
3
5
  import { tempo as tempo_chain } from 'viem/tempo/chains';
6
+ import * as MethodResponse from '../../client/internal/MethodResponse.js';
4
7
  import * as Credential from '../../Credential.js';
5
8
  import * as Method from '../../Method.js';
6
9
  import * as Account from '../../viem/Account.js';
@@ -34,24 +37,36 @@ const chargeContextSchema = z.object({
34
37
  */
35
38
  export function charge(parameters = {}) {
36
39
  const { clientId } = parameters;
40
+ // Caps are denominated in the challenge currency, so they require a currency pin.
41
+ if ((parameters.maxAmount !== undefined || parameters.maxAtomicAmount !== undefined) &&
42
+ parameters.currencies === undefined)
43
+ throw new Error('Tempo charge maxAmount and maxAtomicAmount require currencies.');
44
+ const caps = {
45
+ maxAmount: parseMaxAmount(parameters.maxAmount, parameters.decimals),
46
+ maxAtomicAmount: parameters.maxAtomicAmount === undefined ? undefined : BigInt(parameters.maxAtomicAmount),
47
+ };
37
48
  const getClient = Client.getResolver({
38
49
  chain: tempo_chain,
39
50
  getClient: parameters.getClient,
40
51
  rpcUrl: defaults.rpcUrl,
41
52
  });
42
53
  const getAccount = Account.getResolver({ account: parameters.account });
43
- return Method.toClient(Methods.charge, {
54
+ const method = Method.toClient(Methods.charge, {
55
+ // Skip offers that violate client policy so an acceptable offer can be chosen.
56
+ canHandleChallenge({ challenge }) {
57
+ try {
58
+ assertChallengePolicy(parameters, caps, challenge.request);
59
+ return true;
60
+ }
61
+ catch {
62
+ return false;
63
+ }
64
+ },
44
65
  context: chargeContextSchema,
45
66
  async createCredential({ challenge, context }) {
46
- // Chain pinning: reject a challenge whose chain ID conflicts with the
47
- // pinned one, and sign on the pin when the challenge omits a chain ID.
67
+ assertChallengePolicy(parameters, caps, challenge.request);
68
+ // Sign on the pinned chain when the challenge omits a chain ID.
48
69
  const challengeChainId = challenge.request.methodDetails?.chainId;
49
- if (parameters.expectedChainId !== undefined &&
50
- challengeChainId !== undefined &&
51
- challengeChainId !== parameters.expectedChainId)
52
- throw new Error(`Chain ID mismatch: expected ${parameters.expectedChainId}, got ${challengeChainId}.`);
53
- if (challengeChainId !== undefined || parameters.allowedChainIds?.length === 0)
54
- Client.assertAllowedChainId(parameters.allowedChainIds, challengeChainId);
55
70
  const resolvedChainId = challengeChainId ??
56
71
  parameters.expectedChainId ??
57
72
  (parameters.allowedChainIds?.length === 1 ? parameters.allowedChainIds[0] : undefined);
@@ -70,18 +85,6 @@ export function charge(parameters = {}) {
70
85
  chainId,
71
86
  operation,
72
87
  })) ?? defaultAccount;
73
- if (parameters.expectedRecipients) {
74
- const allowed = new Set(parameters.expectedRecipients.map((a) => a.toLowerCase()));
75
- if (!request.recipient || !allowed.has(request.recipient.toLowerCase()))
76
- throw new Error(`Unexpected primary recipient: ${request.recipient}`);
77
- const splits = methodDetails?.splits;
78
- if (splits) {
79
- for (const split of splits) {
80
- if (!allowed.has(split.recipient.toLowerCase()))
81
- throw new Error(`Unexpected split recipient: ${split.recipient}`);
82
- }
83
- }
84
- }
85
88
  // Zero-amount: sign EIP-712 typed data instead of creating a transaction.
86
89
  if (BigInt(amount) === 0n) {
87
90
  const account = await resolveAccount({ kind: 'signPaymentProof' });
@@ -198,7 +201,9 @@ export function charge(parameters = {}) {
198
201
  const defaultExpiry = Math.floor(Date.now() / 1000) + 25;
199
202
  if (!challenge.expires)
200
203
  return defaultExpiry;
201
- const challengeExpiry = Math.floor(new Date(challenge.expires).getTime() / 1000);
204
+ // `validBefore` is an exclusive whole-second bound, so cap at the second
205
+ // after a (possibly fractional) expiry to stay valid as long as the challenge.
206
+ const challengeExpiry = Math.floor(new Date(challenge.expires).getTime() / 1000) + 1;
202
207
  return Math.min(defaultExpiry, challengeExpiry);
203
208
  })();
204
209
  if (mode === 'push') {
@@ -254,5 +259,94 @@ export function charge(parameters = {}) {
254
259
  });
255
260
  },
256
261
  });
262
+ // Share an in-flight signing so concurrent calls for one challenge cannot yield
263
+ // two independently settleable transactions. Settled signings are not reused.
264
+ const signing = new Map();
265
+ const createCredential = async (options) => {
266
+ // Enforce client policy on every call, including ones joining a signing.
267
+ assertChallengePolicy(parameters, caps, options.challenge.request);
268
+ const account = options.context?.account ?? parameters.account;
269
+ const signer = (typeof account === 'string' ? account : account?.address)?.toLowerCase();
270
+ const key = `${signer ?? ''}:${options.challenge.id}`;
271
+ let promise = signing.get(key);
272
+ if (!promise) {
273
+ promise = method.createCredential(options).finally(() => signing.delete(key));
274
+ signing.set(key, promise);
275
+ }
276
+ return promise;
277
+ };
278
+ return MethodResponse.registerTransactionHash({ ...method, createCredential }, transactionHash);
279
+ }
280
+ /**
281
+ * Returns the final transaction hash of a credential, when known before settlement.
282
+ *
283
+ * Push credentials carry the broadcast hash; pull credentials hash a canonical,
284
+ * sender-signed 0x76 transaction. Sponsored transactions (0x78 envelopes or 0x76
285
+ * awaiting a fee payer) return `undefined` because the fee payer's co-signature
286
+ * changes the final hash.
287
+ */
288
+ function transactionHash(credential) {
289
+ const parsed = Methods.charge.schema.credential.payload.safeParse(Credential.deserialize(credential).payload);
290
+ if (!parsed.success)
291
+ return undefined;
292
+ const payload = parsed.data;
293
+ if (payload.type === 'hash')
294
+ return payload.hash;
295
+ const serialized = payload.signature;
296
+ if (payload.type !== 'transaction' || !serialized.startsWith(TxEnvelopeTempo.serializedType))
297
+ return undefined;
298
+ const transaction = TxEnvelopeTempo.deserialize(serialized);
299
+ if (!transaction.signature || transaction.feePayerSignature === null)
300
+ return undefined;
301
+ // Reject trailing or non-canonical bytes, which never identify an on-chain transaction.
302
+ if (TxEnvelopeTempo.serialize(transaction) !== serialized)
303
+ return undefined;
304
+ return keccak256(serialized);
305
+ }
306
+ /** Parses `maxAmount` to base units, rejecting values that cannot be represented exactly. */
307
+ function parseMaxAmount(maxAmount, decimals) {
308
+ if (maxAmount === undefined)
309
+ return undefined;
310
+ if (decimals === undefined)
311
+ throw new Error('Tempo charge maxAmount requires currency decimals.');
312
+ // `parseUnits` rounds excess fractional digits, which could loosen the cap.
313
+ const fraction = maxAmount.split('.')[1]?.replace(/0+$/, '') ?? '';
314
+ if (fraction.length > decimals)
315
+ throw new Error(`Tempo charge maxAmount ${maxAmount} has more fractional digits than ${decimals} decimals allow.`);
316
+ return parseUnits(maxAmount, decimals);
317
+ }
318
+ /**
319
+ * Rejects a challenge whose chain, currency, recipients, or amount violate the client policy.
320
+ * Runs during challenge selection and again before any client, account, or signing work.
321
+ */
322
+ function assertChallengePolicy(parameters, caps, request) {
323
+ const { amount, methodDetails, recipient } = request;
324
+ // Chain pinning: reject a challenge whose chain ID conflicts with the pinned one.
325
+ const challengeChainId = methodDetails?.chainId;
326
+ if (parameters.expectedChainId !== undefined &&
327
+ challengeChainId !== undefined &&
328
+ challengeChainId !== parameters.expectedChainId)
329
+ throw new Error(`Chain ID mismatch: expected ${parameters.expectedChainId}, got ${challengeChainId}.`);
330
+ if (challengeChainId !== undefined || parameters.allowedChainIds?.length === 0)
331
+ Client.assertAllowedChainId(parameters.allowedChainIds, challengeChainId);
332
+ if (parameters.currencies &&
333
+ !parameters.currencies.some((c) => c.toLowerCase() === request.currency.toLowerCase()))
334
+ throw new Error(`Tempo charge currency is not allowed: ${request.currency}.`);
335
+ if (parameters.expectedRecipients) {
336
+ const allowed = new Set(parameters.expectedRecipients.map((a) => a.toLowerCase()));
337
+ if (!recipient || !allowed.has(recipient.toLowerCase()))
338
+ throw new Error(`Unexpected primary recipient: ${recipient}`);
339
+ const splits = methodDetails?.splits;
340
+ if (splits) {
341
+ for (const split of splits) {
342
+ if (!allowed.has(split.recipient.toLowerCase()))
343
+ throw new Error(`Unexpected split recipient: ${split.recipient}`);
344
+ }
345
+ }
346
+ }
347
+ if (caps.maxAtomicAmount !== undefined && BigInt(amount) > caps.maxAtomicAmount)
348
+ throw new Error('Tempo charge amount exceeds maxAtomicAmount.');
349
+ if (caps.maxAmount !== undefined && BigInt(amount) > caps.maxAmount)
350
+ throw new Error('Tempo charge amount exceeds maxAmount.');
257
351
  }
258
352
  //# sourceMappingURL=Charge.js.map
@@ -19,7 +19,16 @@ export { sessionClient as session };
19
19
  * })
20
20
  * ```
21
21
  */
22
- export declare function tempo(parameters?: tempo.Parameters): readonly [import("../../Method.js").Client<{
22
+ export declare function tempo(parameters?: tempo.Parameters): readonly [{
23
+ readonly canHandleChallenge?: import("../../Method.js").CanHandleChallengeFn | undefined;
24
+ readonly context?: import("zod/mini").ZodMiniObject<{
25
+ account: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniCustom<`0x${string}` | import("../../viem/Account.js").Account | undefined, `0x${string}` | import("../../viem/Account.js").Account | undefined>>;
26
+ autoSwap: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniCustom<import("../internal/auto-swap.js").resolve.Value | undefined, import("../internal/auto-swap.js").resolve.Value | undefined>>;
27
+ mode: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniEnum<{
28
+ pull: "pull";
29
+ push: "push";
30
+ }>>;
31
+ }, import("zod/v4/core").$strip> | undefined;
23
32
  readonly name: "tempo";
24
33
  readonly intent: "charge";
25
34
  readonly schema: {
@@ -91,14 +100,84 @@ export declare function tempo(parameters?: tempo.Parameters): readonly [import("
91
100
  supportedModes?: ("pull" | "push")[] | undefined;
92
101
  }>>;
93
102
  };
94
- }, import("zod/mini").ZodMiniObject<{
95
- account: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniCustom<`0x${string}` | import("../../viem/Account.js").Account | undefined, `0x${string}` | import("../../viem/Account.js").Account | undefined>>;
96
- autoSwap: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniCustom<import("../internal/auto-swap.js").resolve.Value | undefined, import("../internal/auto-swap.js").resolve.Value | undefined>>;
97
- mode: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniEnum<{
98
- pull: "pull";
99
- push: "push";
100
- }>>;
101
- }, import("zod/v4/core").$strip>>, import("../../Method.js").Client<{
103
+ readonly createCredential: import("../../Method.js").CreateCredentialFn<{
104
+ readonly name: "tempo";
105
+ readonly intent: "charge";
106
+ readonly schema: {
107
+ credential: {
108
+ payload: import("zod/mini").ZodMiniDiscriminatedUnion<[import("zod/mini").ZodMiniObject<{
109
+ hash: import("zod/mini").ZodMiniString<string>;
110
+ type: import("zod/mini").ZodMiniLiteral<"hash">;
111
+ }, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniObject<{
112
+ signature: import("zod/mini").ZodMiniString<string>;
113
+ type: import("zod/mini").ZodMiniLiteral<"transaction">;
114
+ }, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniObject<{
115
+ signature: import("zod/mini").ZodMiniString<string>;
116
+ type: import("zod/mini").ZodMiniLiteral<"proof">;
117
+ }, import("zod/v4/core").$strip>], "type">;
118
+ };
119
+ request: import("zod/mini").ZodMiniPipe<import("zod/mini").ZodMiniObject<{
120
+ machineTokenEnabled: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniBoolean<boolean>>;
121
+ amount: import("zod/mini").ZodMiniString<string>;
122
+ chainId: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniNumber<number>>;
123
+ currency: import("zod/mini").ZodMiniString<string>;
124
+ decimals: import("zod/mini").ZodMiniNumber<number>;
125
+ description: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
126
+ externalId: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
127
+ feePayer: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniPipe<import("zod/mini").ZodMiniUnion<readonly [import("zod/mini").ZodMiniBoolean<boolean>, import("zod/mini").ZodMiniCustom<import("viem").Account, import("viem").Account>]>, import("zod/mini").ZodMiniTransform<boolean, boolean | import("viem").Account>>>;
128
+ memo: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniNever>;
129
+ recipient: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
130
+ splits: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniArray<import("zod/mini").ZodMiniObject<{
131
+ amount: import("zod/mini").ZodMiniString<string>;
132
+ memo: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
133
+ recipient: import("zod/mini").ZodMiniPipe<import("zod/mini").ZodMiniString<string>, import("zod/mini").ZodMiniTransform<`0x${string}`, string>>;
134
+ }, import("zod/v4/core").$strip>>>;
135
+ supportedModes: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniArray<import("zod/mini").ZodMiniEnum<{
136
+ pull: "pull";
137
+ push: "push";
138
+ }>>>;
139
+ }, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniTransform<{
140
+ currency: string;
141
+ description?: string | undefined;
142
+ externalId?: string | undefined;
143
+ recipient?: string | undefined;
144
+ amount: string;
145
+ methodDetails?: {
146
+ machineTokenEnabled?: boolean | undefined;
147
+ chainId?: number | undefined;
148
+ feePayer?: boolean | undefined;
149
+ splits?: {
150
+ memo?: string | undefined;
151
+ recipient: `0x${string}`;
152
+ amount: string;
153
+ }[] | undefined;
154
+ supportedModes?: ("pull" | "push")[] | undefined;
155
+ } | undefined;
156
+ }, {
157
+ machineTokenEnabled?: boolean | undefined;
158
+ amount: string;
159
+ chainId?: number | undefined;
160
+ currency: string;
161
+ decimals: number;
162
+ description?: string | undefined;
163
+ externalId?: string | undefined;
164
+ feePayer?: boolean | undefined;
165
+ memo?: undefined;
166
+ recipient?: string | undefined;
167
+ splits?: {
168
+ amount: string;
169
+ memo?: string | undefined;
170
+ recipient: `0x${string}`;
171
+ }[] | undefined;
172
+ supportedModes?: ("pull" | "push")[] | undefined;
173
+ }>>;
174
+ };
175
+ }, {
176
+ account?: `0x${string}` | import("../../viem/Account.js").Account | undefined;
177
+ autoSwap?: import("../internal/auto-swap.js").resolve.Value | undefined;
178
+ mode?: "pull" | "push" | undefined;
179
+ }>;
180
+ }, import("../../Method.js").Client<{
102
181
  readonly name: "tempo";
103
182
  readonly intent: "session";
104
183
  readonly schema: {
@@ -1,3 +1,4 @@
1
+ export * as Attribution from './Attribution.js';
1
2
  export * as Proof from './Proof.js';
2
3
  export * as Methods from './Methods.js';
3
4
  export * as Session from './session/index.js';
@@ -1,3 +1,4 @@
1
+ export * as Attribution from './Attribution.js';
1
2
  export * as Proof from './Proof.js';
2
3
  export * as Methods from './Methods.js';
3
4
  export * as Session from './session/index.js';
@@ -495,6 +495,10 @@ export function assertTransactionPolicy(parameters) {
495
495
  fail('fee-sponsored transaction has already expired', {
496
496
  validBefore: String(validBeforeValue),
497
497
  });
498
+ if (validBeforeValue < Math.ceil(now.getTime() / 1_000) + 15)
499
+ fail('fee-sponsored transaction needs at least 15 seconds of remaining validity', {
500
+ validBefore: String(validBeforeValue),
501
+ });
498
502
  const challengeExpirySeconds = challengeExpires
499
503
  ? Math.floor(new Date(challengeExpires).getTime() / 1_000)
500
504
  : undefined;
@@ -55,6 +55,17 @@ export declare namespace charge {
55
55
  * checks still apply. This does not change the payment currency or sessions.
56
56
  */
57
57
  allowedFeeTokens?: readonly `0x${string}`[] | undefined;
58
+ /**
59
+ * Milliseconds to wait for a charge transaction receipt when
60
+ * `waitForConfirmation` is `true`. The synchronous broadcast may use at most
61
+ * two thirds of this budget. If it fails with an unknown outcome (e.g. a
62
+ * timeout, a lost connection, or "already known"), mppx polls for the
63
+ * receipt until the deadline, or until 2 seconds after the transaction's
64
+ * `validBefore` followed by one final lookup, before surfacing the broadcast
65
+ * error. Definitive rejections fail immediately.
66
+ * @default 30_000
67
+ */
68
+ confirmationTimeoutMs?: number | undefined;
58
69
  /** Enables first-party machine-token funding through the canonical swapper. */
59
70
  machineTokenEnabled?: boolean | undefined;
60
71
  /** Render payment page when Accept header is text/html (e.g. in browsers) */