mppx 0.17.1 → 0.17.4
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 +65 -0
- package/dist/Errors.d.ts +94 -0
- package/dist/Errors.js +83 -0
- package/dist/Store.d.ts +413 -0
- package/dist/Store.js +837 -0
- package/dist/cli/cli.js +141 -136
- package/dist/cli/internal.js +28 -6
- package/dist/cli/plugins/index.js +16 -11
- package/dist/cli/sessions/Manager.js +12 -1
- package/dist/cli/utils.d.ts +2 -0
- package/dist/cli/utils.js +6 -0
- package/dist/cli/validate/challenge.d.ts +4 -0
- package/dist/cli/validate/challenge.js +9 -6
- package/dist/client/Mppx.d.ts +1 -1
- package/dist/client/Mppx.js +7 -3
- package/dist/client/internal/Fetch.d.ts +1 -1
- package/dist/client/internal/Fetch.js +9 -0
- package/dist/client/internal/MethodResponse.d.ts +8 -0
- package/dist/client/internal/MethodResponse.js +9 -0
- package/dist/client/internal/ProofRealm.d.ts +3 -3
- package/dist/client/internal/ProofRealm.js +4 -4
- package/dist/client/internal/protocols/Mcp.d.ts +4 -0
- package/dist/client/internal/protocols/Mcp.js +27 -14
- package/dist/cloudflare/ChannelStore.d.ts +97 -0
- package/dist/cloudflare/ChannelStore.js +304 -0
- package/dist/cloudflare/Channels.d.ts +92 -0
- package/dist/cloudflare/Channels.js +325 -0
- package/dist/cloudflare/index.d.ts +5 -0
- package/dist/cloudflare/index.js +5 -0
- package/dist/identity/Client.js +7 -1
- package/dist/internal/sse.d.ts +21 -0
- package/dist/internal/sse.js +82 -0
- package/dist/internal/version.d.ts +2 -2
- package/dist/internal/version.js +1 -1
- package/dist/mcp/client/McpClient.d.ts +2 -2
- package/dist/mcp/client/McpClient.js +5 -2
- package/dist/proxy/Proxy.d.ts +2 -0
- package/dist/proxy/Proxy.js +31 -6
- package/dist/proxy/internal/Replay.js +1 -1
- package/dist/server/Mppx.js +5 -4
- package/dist/server/Transport.js +1 -1
- package/dist/server/internal/html/config.d.ts +7 -1
- package/dist/server/internal/html/config.js +4 -0
- package/dist/stripe/internal/types.d.ts +82 -6
- package/dist/stripe/server/Charge.js +1 -2
- package/dist/stripe/server/Methods.d.ts +2 -0
- package/dist/stripe/server/Methods.js +2 -1
- package/dist/stripe/server/internal/html.gen.js +1 -1
- package/dist/stripe/server/internal/record-payment.d.ts +49 -0
- package/dist/stripe/server/internal/record-payment.js +75 -18
- package/dist/tempo/Methods.d.ts +8 -0
- package/dist/tempo/Methods.js +18 -2
- package/dist/tempo/client/Charge.d.ts +32 -1
- package/dist/tempo/client/Charge.js +201 -3
- package/dist/tempo/client/Methods.d.ts +16 -0
- package/dist/tempo/internal/fee-payer.d.ts +64 -14
- package/dist/tempo/internal/fee-payer.js +231 -20
- package/dist/tempo/internal/zone.d.ts +79 -0
- package/dist/tempo/internal/zone.js +133 -0
- package/dist/tempo/legacy/session/Chain.js +2 -0
- package/dist/tempo/server/Charge.d.ts +7 -3
- package/dist/tempo/server/Charge.js +100 -6
- package/dist/tempo/server/Methods.d.ts +40 -0
- package/dist/tempo/server/Subscription.js +1 -0
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/server/internal/transport.d.ts +6 -0
- package/dist/tempo/server/internal/transport.js +21 -21
- package/dist/tempo/session/client/ChannelStore.d.ts +2 -0
- package/dist/tempo/session/client/CredentialState.js +2 -0
- package/dist/tempo/session/client/Session.d.ts +11 -1
- package/dist/tempo/session/client/Session.js +39 -6
- package/dist/tempo/session/client/SessionManager.d.ts +1 -1
- package/dist/tempo/session/client/SessionManager.js +8 -4
- package/dist/tempo/session/client/Transports.d.ts +2 -0
- package/dist/tempo/session/client/Transports.js +4 -1
- package/dist/tempo/session/precompile/Chain.js +1 -0
- package/dist/tempo/session/server/ChannelStore.d.ts +14 -0
- package/dist/tempo/session/server/ChannelStore.js +10 -3
- package/dist/tempo/session/server/CredentialVerification.js +1 -1
- package/dist/tempo/session/server/MeteredStream.d.ts +4 -0
- package/dist/tempo/session/server/MeteredStream.js +6 -1
- package/dist/tempo/session/server/Sse.d.ts +59 -0
- package/dist/tempo/session/server/Sse.js +175 -10
- package/dist/validation/core.js +2 -0
- package/dist/zod.d.ts +2 -1
- package/dist/zod.js +1 -1
- package/package.json +5 -3
- package/dist/stripe/server/internal/html/types.d.ts +0 -2
- package/dist/stripe/server/internal/html/types.js +0 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Address } from 'viem';
|
|
1
|
+
import { type Address, type Client as viem_Client } 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';
|
|
@@ -61,6 +61,11 @@ export declare function charge(parameters?: charge.Parameters): {
|
|
|
61
61
|
}, z.core.$strip>, z.ZodMiniObject<{
|
|
62
62
|
signature: z.ZodMiniString<string>;
|
|
63
63
|
type: z.ZodMiniLiteral<"proof">;
|
|
64
|
+
}, z.core.$strip>, z.ZodMiniObject<{
|
|
65
|
+
hash: z.ZodMiniString<string>;
|
|
66
|
+
senderTag: z.ZodMiniString<string>;
|
|
67
|
+
type: z.ZodMiniLiteral<"zone">;
|
|
68
|
+
zoneId: z.ZodMiniNumber<number>;
|
|
64
69
|
}, z.core.$strip>], "type">;
|
|
65
70
|
};
|
|
66
71
|
request: z.ZodMiniPipe<z.ZodMiniObject<{
|
|
@@ -83,6 +88,7 @@ export declare function charge(parameters?: charge.Parameters): {
|
|
|
83
88
|
pull: "pull";
|
|
84
89
|
push: "push";
|
|
85
90
|
}>>>;
|
|
91
|
+
zones: z.ZodMiniOptional<z.ZodMiniUnion<readonly [z.ZodMiniLiteral<true>, z.ZodMiniArray<z.ZodMiniNumber<number>>]>>;
|
|
86
92
|
}, z.core.$strip>, z.ZodMiniTransform<{
|
|
87
93
|
currency: string;
|
|
88
94
|
description?: string | undefined;
|
|
@@ -99,6 +105,7 @@ export declare function charge(parameters?: charge.Parameters): {
|
|
|
99
105
|
amount: string;
|
|
100
106
|
}[] | undefined;
|
|
101
107
|
supportedModes?: ("pull" | "push")[] | undefined;
|
|
108
|
+
zones?: true | number[] | undefined;
|
|
102
109
|
} | undefined;
|
|
103
110
|
}, {
|
|
104
111
|
machineTokenEnabled?: boolean | undefined;
|
|
@@ -117,6 +124,7 @@ export declare function charge(parameters?: charge.Parameters): {
|
|
|
117
124
|
recipient: `0x${string}`;
|
|
118
125
|
}[] | undefined;
|
|
119
126
|
supportedModes?: ("pull" | "push")[] | undefined;
|
|
127
|
+
zones?: true | number[] | undefined;
|
|
120
128
|
}>>;
|
|
121
129
|
};
|
|
122
130
|
readonly createCredential: Method.CreateCredentialFn<{
|
|
@@ -133,6 +141,11 @@ export declare function charge(parameters?: charge.Parameters): {
|
|
|
133
141
|
}, z.core.$strip>, z.ZodMiniObject<{
|
|
134
142
|
signature: z.ZodMiniString<string>;
|
|
135
143
|
type: z.ZodMiniLiteral<"proof">;
|
|
144
|
+
}, z.core.$strip>, z.ZodMiniObject<{
|
|
145
|
+
hash: z.ZodMiniString<string>;
|
|
146
|
+
senderTag: z.ZodMiniString<string>;
|
|
147
|
+
type: z.ZodMiniLiteral<"zone">;
|
|
148
|
+
zoneId: z.ZodMiniNumber<number>;
|
|
136
149
|
}, z.core.$strip>], "type">;
|
|
137
150
|
};
|
|
138
151
|
request: z.ZodMiniPipe<z.ZodMiniObject<{
|
|
@@ -155,6 +168,7 @@ export declare function charge(parameters?: charge.Parameters): {
|
|
|
155
168
|
pull: "pull";
|
|
156
169
|
push: "push";
|
|
157
170
|
}>>>;
|
|
171
|
+
zones: z.ZodMiniOptional<z.ZodMiniUnion<readonly [z.ZodMiniLiteral<true>, z.ZodMiniArray<z.ZodMiniNumber<number>>]>>;
|
|
158
172
|
}, z.core.$strip>, z.ZodMiniTransform<{
|
|
159
173
|
currency: string;
|
|
160
174
|
description?: string | undefined;
|
|
@@ -171,6 +185,7 @@ export declare function charge(parameters?: charge.Parameters): {
|
|
|
171
185
|
amount: string;
|
|
172
186
|
}[] | undefined;
|
|
173
187
|
supportedModes?: ("pull" | "push")[] | undefined;
|
|
188
|
+
zones?: true | number[] | undefined;
|
|
174
189
|
} | undefined;
|
|
175
190
|
}, {
|
|
176
191
|
machineTokenEnabled?: boolean | undefined;
|
|
@@ -189,6 +204,7 @@ export declare function charge(parameters?: charge.Parameters): {
|
|
|
189
204
|
recipient: `0x${string}`;
|
|
190
205
|
}[] | undefined;
|
|
191
206
|
supportedModes?: ("pull" | "push")[] | undefined;
|
|
207
|
+
zones?: true | number[] | undefined;
|
|
192
208
|
}>>;
|
|
193
209
|
};
|
|
194
210
|
}, {
|
|
@@ -203,6 +219,15 @@ export declare namespace charge {
|
|
|
203
219
|
type Context = ChargeContext;
|
|
204
220
|
type ResolveAccount = AccountResolution.ResolveAccount;
|
|
205
221
|
type ResolveAccountInfo = AccountResolution.ResolveAccountInfo;
|
|
222
|
+
/** Pays from a Zone balance when a challenge accepts that Zone. */
|
|
223
|
+
type Zone = {
|
|
224
|
+
/** Client for the Zone chain, with the paying account and Zone RPC authorization. */
|
|
225
|
+
client: viem_Client;
|
|
226
|
+
/** Zone ID. Derived from `client.chain.id` when omitted. */
|
|
227
|
+
zoneId?: number | undefined;
|
|
228
|
+
/** Milliseconds to wait for the withdrawal to settle on Tempo. @default 60_000 */
|
|
229
|
+
settlementTimeoutMs?: number | undefined;
|
|
230
|
+
};
|
|
206
231
|
type Parameters = {
|
|
207
232
|
/**
|
|
208
233
|
* Automatically swap from a fallback currency (pathUsd, USDC.e) via the
|
|
@@ -277,6 +302,12 @@ export declare namespace charge {
|
|
|
277
302
|
mode?: Methods.ChargeMode | undefined;
|
|
278
303
|
/** Selects the account that signs this charge after the challenge and chain are known. */
|
|
279
304
|
resolveAccount?: ResolveAccount | undefined;
|
|
305
|
+
/**
|
|
306
|
+
* Pays from a Zone balance instead of the account's Tempo balance when the
|
|
307
|
+
* challenge accepts this Zone (`methodDetails.zones`). Other challenges,
|
|
308
|
+
* and split payments, are paid on Tempo as usual.
|
|
309
|
+
*/
|
|
310
|
+
zone?: Zone | undefined;
|
|
280
311
|
} & Account.getResolver.Parameters & Client.getResolver.Parameters;
|
|
281
312
|
}
|
|
282
313
|
//# sourceMappingURL=Charge.d.ts.map
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { TxEnvelopeTempo } from 'ox/tempo';
|
|
2
|
-
import { keccak256, parseUnits } from 'viem';
|
|
3
|
-
import { prepareTransactionRequest, sendCallsSync, sendTransactionSync, signTypedData, signTransaction, } from 'viem/actions';
|
|
4
|
-
import { Actions } from 'viem/tempo';
|
|
2
|
+
import { keccak256, parseEventLogs, parseUnits, } from 'viem';
|
|
3
|
+
import { prepareTransactionRequest, sendCallsSync, sendRawTransactionSync, sendTransactionSync, signTypedData, signTransaction, waitForTransactionReceipt, } from 'viem/actions';
|
|
4
|
+
import { Abis, Actions, Addresses } from 'viem/tempo';
|
|
5
5
|
import { tempo as tempo_chain } from 'viem/tempo/chains';
|
|
6
6
|
import * as MethodResponse from '../../client/internal/MethodResponse.js';
|
|
7
7
|
import * as Credential from '../../Credential.js';
|
|
8
|
+
import * as Errors from '../../Errors.js';
|
|
8
9
|
import * as Method from '../../Method.js';
|
|
9
10
|
import * as Account from '../../viem/Account.js';
|
|
10
11
|
import * as Client from '../../viem/Client.js';
|
|
@@ -16,6 +17,7 @@ import * as defaults from '../internal/defaults.js';
|
|
|
16
17
|
import { defaultFeeTokens, resolveFeeToken } from '../internal/fee-token.js';
|
|
17
18
|
import * as MachineTokenCharge from '../internal/machine-token-charge.js';
|
|
18
19
|
import * as Proof from '../internal/proof.js';
|
|
20
|
+
import * as Zone from '../internal/zone.js';
|
|
19
21
|
import * as Methods from '../Methods.js';
|
|
20
22
|
/**
|
|
21
23
|
* Seconds from now the client sets as the expiring-nonce `validBefore`,
|
|
@@ -63,6 +65,9 @@ export function charge(parameters = {}) {
|
|
|
63
65
|
rpcUrl: defaults.rpcUrl,
|
|
64
66
|
});
|
|
65
67
|
const getAccount = Account.getResolver({ account: parameters.account });
|
|
68
|
+
assertZone(parameters.zone);
|
|
69
|
+
// Zone withdrawals still settling, by challenge ID, so a retry resumes waiting instead of paying again.
|
|
70
|
+
const pendingZoneWithdrawals = new Map();
|
|
66
71
|
const method = Method.toClient(Methods.charge, {
|
|
67
72
|
// Skip offers that violate client policy so an acceptable offer can be chosen.
|
|
68
73
|
canHandleChallenge({ challenge }) {
|
|
@@ -91,6 +96,36 @@ export function charge(parameters = {}) {
|
|
|
91
96
|
const { request } = challenge;
|
|
92
97
|
const { amount, methodDetails } = request;
|
|
93
98
|
const supportedModes = methodDetails?.supportedModes ?? ['pull', 'push'];
|
|
99
|
+
const zoneChain = parameters.zone?.client.chain;
|
|
100
|
+
const zoneId = parameters.zone
|
|
101
|
+
? (parameters.zone.zoneId ?? Zone.idFromChainId(zoneChain?.id ?? 0))
|
|
102
|
+
: undefined;
|
|
103
|
+
// A Zone ID is only unique per parent chain, so the Zone must settle on the challenge's chain.
|
|
104
|
+
const zoneParentChainId = zoneChain?.sourceId ??
|
|
105
|
+
(zoneChain ? Zone.parentChainIdFromChainId(zoneChain.id) : undefined);
|
|
106
|
+
const explicitMode = context?.mode ?? parameters.mode;
|
|
107
|
+
// Pay from a Zone balance when the challenge accepts the configured Zone.
|
|
108
|
+
// Zone payments are push payments, so they follow the challenge's and client's modes.
|
|
109
|
+
if (BigInt(amount) > 0n &&
|
|
110
|
+
parameters.zone &&
|
|
111
|
+
zoneId !== undefined &&
|
|
112
|
+
zoneParentChainId === chainId &&
|
|
113
|
+
supportedModes.includes('push') &&
|
|
114
|
+
(explicitMode === undefined || explicitMode === 'push') &&
|
|
115
|
+
Zone.isAccepted(methodDetails?.zones, zoneId) &&
|
|
116
|
+
!methodDetails?.splits &&
|
|
117
|
+
// A withdrawal can't be undone, so start one only when it can settle before the
|
|
118
|
+
// challenge expires; an expired challenge would invite another payment.
|
|
119
|
+
(pendingZoneWithdrawals.has(zoneWithdrawalKey(challenge)) ||
|
|
120
|
+
settlesBeforeExpiry(challenge.expires, parameters.zone.settlementTimeoutMs)))
|
|
121
|
+
return createZoneCredential({
|
|
122
|
+
challenge,
|
|
123
|
+
clientId,
|
|
124
|
+
client,
|
|
125
|
+
pending: pendingZoneWithdrawals,
|
|
126
|
+
zone: parameters.zone,
|
|
127
|
+
zoneId,
|
|
128
|
+
});
|
|
94
129
|
const defaultAccount = getAccount(client, context);
|
|
95
130
|
const resolveAccount = async (operation) => (await parameters.resolveAccount?.({
|
|
96
131
|
account: defaultAccount,
|
|
@@ -326,6 +361,8 @@ function transactionHash(credential) {
|
|
|
326
361
|
const payload = parsed.data;
|
|
327
362
|
if (payload.type === 'hash')
|
|
328
363
|
return payload.hash;
|
|
364
|
+
if (payload.type === 'zone')
|
|
365
|
+
return payload.hash;
|
|
329
366
|
const serialized = payload.signature;
|
|
330
367
|
if (payload.type !== 'transaction' || !serialized.startsWith(TxEnvelopeTempo.serializedType))
|
|
331
368
|
return undefined;
|
|
@@ -337,6 +374,167 @@ function transactionHash(credential) {
|
|
|
337
374
|
return undefined;
|
|
338
375
|
return keccak256(serialized);
|
|
339
376
|
}
|
|
377
|
+
/**
|
|
378
|
+
* Pays a challenge from a Zone balance: requests a withdrawal to the recipient
|
|
379
|
+
* with the challenge-bound memo, and returns a `zone` credential that names it
|
|
380
|
+
* by sender tag and the Tempo transaction hash that settled it.
|
|
381
|
+
*/
|
|
382
|
+
async function createZoneCredential(options) {
|
|
383
|
+
const { challenge, client, pending, zone, zoneId } = options;
|
|
384
|
+
const request = challenge.request;
|
|
385
|
+
const recipient = request.recipient;
|
|
386
|
+
// The whole challenge, so a challenge from another realm or request with the same ID never matches.
|
|
387
|
+
const key = zoneWithdrawalKey(challenge);
|
|
388
|
+
// A withdrawal can't be undone, so a retry after a failed wait resumes it instead of paying again.
|
|
389
|
+
// Concurrent calls for one challenge share the request, which is stored before it is sent.
|
|
390
|
+
let withdrawalRequest = pending.get(key);
|
|
391
|
+
if (!withdrawalRequest) {
|
|
392
|
+
const state = { sent: false };
|
|
393
|
+
withdrawalRequest = requestZoneWithdrawal(options, state);
|
|
394
|
+
pending.set(key, withdrawalRequest);
|
|
395
|
+
// Only a failure before the transaction was sent is safe to retry. After that, the
|
|
396
|
+
// withdrawal may have gone through, so retries of this challenge keep failing.
|
|
397
|
+
withdrawalRequest.catch(() => {
|
|
398
|
+
if (!state.sent)
|
|
399
|
+
pending.delete(key);
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
const senderTag = await withdrawalRequest;
|
|
403
|
+
let hash;
|
|
404
|
+
try {
|
|
405
|
+
hash = await Zone.waitForSettlement(client, {
|
|
406
|
+
portal: Zone.portalAddress(zoneId),
|
|
407
|
+
senderTag,
|
|
408
|
+
timeoutMs: settlementTimeoutMs(challenge.expires, zone.settlementTimeoutMs),
|
|
409
|
+
to: recipient,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
catch (cause) {
|
|
413
|
+
throw new Errors.ZoneSettlementPendingError({
|
|
414
|
+
cause,
|
|
415
|
+
challengeId: challenge.id,
|
|
416
|
+
senderTag,
|
|
417
|
+
zoneId,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
pending.delete(key);
|
|
421
|
+
return Credential.serialize({
|
|
422
|
+
challenge,
|
|
423
|
+
payload: { hash, senderTag, type: 'zone', zoneId },
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Throws on Zone options that would fail only after an irreversible withdrawal:
|
|
428
|
+
* a `settlementTimeoutMs` that is not a finite, non-negative number, or a
|
|
429
|
+
* `zoneId` that names a different Zone from the one `zone.client` serves.
|
|
430
|
+
*/
|
|
431
|
+
function assertZone(zone) {
|
|
432
|
+
const timeoutMs = zone?.settlementTimeoutMs;
|
|
433
|
+
if (timeoutMs !== undefined && (!Number.isFinite(timeoutMs) || timeoutMs < 0))
|
|
434
|
+
throw new Error('`zone.settlementTimeoutMs` must be a finite, non-negative number.');
|
|
435
|
+
const chainId = zone?.client.chain?.id;
|
|
436
|
+
if (zone?.zoneId === undefined || chainId === undefined)
|
|
437
|
+
return;
|
|
438
|
+
// Only chain IDs that follow a known Zone scheme name their Zone.
|
|
439
|
+
if (Zone.parentChainIdFromChainId(chainId) === undefined)
|
|
440
|
+
return;
|
|
441
|
+
const derived = Zone.idFromChainId(chainId);
|
|
442
|
+
if (derived !== zone.zoneId)
|
|
443
|
+
throw new Error(`zone.zoneId ${zone.zoneId} does not match Zone ${derived} of the zone.client chain ${chainId}.`);
|
|
444
|
+
}
|
|
445
|
+
/** Time the client allows for sending a Zone withdrawal before it waits for settlement. */
|
|
446
|
+
const zoneSubmissionMarginMs = 15_000;
|
|
447
|
+
/**
|
|
448
|
+
* Returns whether a Zone withdrawal can be sent and waited on for up to
|
|
449
|
+
* `settlementTimeoutMs` before `expires`.
|
|
450
|
+
*/
|
|
451
|
+
function settlesBeforeExpiry(expires, settlementTimeoutMs) {
|
|
452
|
+
if (!expires)
|
|
453
|
+
return true;
|
|
454
|
+
const timeoutMs = settlementTimeoutMs ?? Zone.defaultSettlementTimeoutMs;
|
|
455
|
+
return new Date(expires).getTime() - Date.now() > timeoutMs + zoneSubmissionMarginMs;
|
|
456
|
+
}
|
|
457
|
+
/** Returns how long to wait for settlement: `settlementTimeoutMs`, cut short at `expires`. */
|
|
458
|
+
function settlementTimeoutMs(expires, settlementTimeoutMs) {
|
|
459
|
+
const timeoutMs = settlementTimeoutMs ?? Zone.defaultSettlementTimeoutMs;
|
|
460
|
+
if (!expires)
|
|
461
|
+
return timeoutMs;
|
|
462
|
+
return Math.max(0, Math.min(timeoutMs, new Date(expires).getTime() - Date.now()));
|
|
463
|
+
}
|
|
464
|
+
/** Keys a Zone withdrawal by its whole Challenge. Unlike `Challenge.serialize`, it accepts any field value. */
|
|
465
|
+
function zoneWithdrawalKey(challenge) {
|
|
466
|
+
return JSON.stringify(challenge);
|
|
467
|
+
}
|
|
468
|
+
/** How long to look for a withdrawal's receipt on the Zone after its send response fails. */
|
|
469
|
+
const zoneReceiptRecoveryTimeoutMs = 15_000;
|
|
470
|
+
/** Requests a Zone withdrawal of the challenge amount to its recipient, bound to the challenge by its memo. */
|
|
471
|
+
async function requestZoneWithdrawal(options, state) {
|
|
472
|
+
const { challenge, clientId, zone } = options;
|
|
473
|
+
const request = challenge.request;
|
|
474
|
+
const account = zone.client.account;
|
|
475
|
+
if (!account)
|
|
476
|
+
throw new Error('`zone.client` requires an account.');
|
|
477
|
+
const amount = BigInt(request.amount);
|
|
478
|
+
const currency = request.currency;
|
|
479
|
+
const recipient = request.recipient;
|
|
480
|
+
const memo = Attribution.encode({
|
|
481
|
+
challengeId: challenge.id,
|
|
482
|
+
clientId,
|
|
483
|
+
serverId: challenge.realm,
|
|
484
|
+
});
|
|
485
|
+
// The outbox pulls the amount plus its withdrawal fee.
|
|
486
|
+
const fee = await Actions.zone.getWithdrawalFee(zone.client);
|
|
487
|
+
const [, withdrawal] = Actions.zone.requestWithdrawal.calls({
|
|
488
|
+
amount,
|
|
489
|
+
// A bounced payout refunds the payer, not the payee's Zone address.
|
|
490
|
+
fallbackRecipient: account.address,
|
|
491
|
+
memo,
|
|
492
|
+
to: recipient,
|
|
493
|
+
token: currency,
|
|
494
|
+
});
|
|
495
|
+
// Prepare and sign first: a failure there sent nothing, so the challenge may be retried.
|
|
496
|
+
const prepared = await prepareTransactionRequest(zone.client, {
|
|
497
|
+
account,
|
|
498
|
+
calls: [
|
|
499
|
+
Actions.token.approve.call({
|
|
500
|
+
amount: amount + fee,
|
|
501
|
+
spender: Addresses.zoneOutbox,
|
|
502
|
+
token: currency,
|
|
503
|
+
}),
|
|
504
|
+
withdrawal,
|
|
505
|
+
],
|
|
506
|
+
});
|
|
507
|
+
const serializedTransaction = await signTransaction(zone.client, prepared);
|
|
508
|
+
state.sent = true;
|
|
509
|
+
// The signed transaction fixes its hash, so a lost send response can still be recovered from the Zone.
|
|
510
|
+
const hash = keccak256(serializedTransaction);
|
|
511
|
+
const receipt = await sendRawTransactionSync(zone.client, { serializedTransaction }).catch((cause) => waitForTransactionReceipt(zone.client, {
|
|
512
|
+
hash,
|
|
513
|
+
timeout: zoneReceiptRecoveryTimeoutMs,
|
|
514
|
+
}).catch(() => {
|
|
515
|
+
const error = new Error(`Zone withdrawal outcome is unknown: transaction ${hash} may have been sent. Retrying this challenge does not withdraw again.`);
|
|
516
|
+
// Defined manually (non-enumerable, like native `cause`) so consumers on pre-ES2022 libs still typecheck.
|
|
517
|
+
Object.defineProperty(error, 'cause', { configurable: true, value: cause, writable: true });
|
|
518
|
+
throw error;
|
|
519
|
+
}));
|
|
520
|
+
// A reverted withdrawal moved nothing, so the challenge may be retried.
|
|
521
|
+
if (receipt.status === 'reverted') {
|
|
522
|
+
state.sent = false;
|
|
523
|
+
throw new Error(`Zone withdrawal transaction ${receipt.transactionHash} reverted.`);
|
|
524
|
+
}
|
|
525
|
+
const [requested] = parseEventLogs({
|
|
526
|
+
abi: Abis.zoneOutbox,
|
|
527
|
+
eventName: 'WithdrawalRequested',
|
|
528
|
+
logs: receipt.logs,
|
|
529
|
+
});
|
|
530
|
+
if (!requested)
|
|
531
|
+
throw new Error('Zone withdrawal request did not emit `WithdrawalRequested`.');
|
|
532
|
+
return Zone.senderTag({
|
|
533
|
+
fallbackNonce: requested.args.fallbackNonce,
|
|
534
|
+
sender: requested.args.sender,
|
|
535
|
+
transactionHash: receipt.transactionHash,
|
|
536
|
+
});
|
|
537
|
+
}
|
|
340
538
|
/** Parses `maxAmount` to base units, rejecting values that cannot be represented exactly. */
|
|
341
539
|
function parseMaxAmount(maxAmount, decimals) {
|
|
342
540
|
if (maxAmount === undefined)
|
|
@@ -43,6 +43,11 @@ export declare function tempo(parameters?: tempo.Parameters): readonly [{
|
|
|
43
43
|
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniObject<{
|
|
44
44
|
signature: import("zod/mini").ZodMiniString<string>;
|
|
45
45
|
type: import("zod/mini").ZodMiniLiteral<"proof">;
|
|
46
|
+
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniObject<{
|
|
47
|
+
hash: import("zod/mini").ZodMiniString<string>;
|
|
48
|
+
senderTag: import("zod/mini").ZodMiniString<string>;
|
|
49
|
+
type: import("zod/mini").ZodMiniLiteral<"zone">;
|
|
50
|
+
zoneId: import("zod/mini").ZodMiniNumber<number>;
|
|
46
51
|
}, import("zod/v4/core").$strip>], "type">;
|
|
47
52
|
};
|
|
48
53
|
request: import("zod/mini").ZodMiniPipe<import("zod/mini").ZodMiniObject<{
|
|
@@ -65,6 +70,7 @@ export declare function tempo(parameters?: tempo.Parameters): readonly [{
|
|
|
65
70
|
pull: "pull";
|
|
66
71
|
push: "push";
|
|
67
72
|
}>>>;
|
|
73
|
+
zones: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniUnion<readonly [import("zod/mini").ZodMiniLiteral<true>, import("zod/mini").ZodMiniArray<import("zod/mini").ZodMiniNumber<number>>]>>;
|
|
68
74
|
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniTransform<{
|
|
69
75
|
currency: string;
|
|
70
76
|
description?: string | undefined;
|
|
@@ -81,6 +87,7 @@ export declare function tempo(parameters?: tempo.Parameters): readonly [{
|
|
|
81
87
|
amount: string;
|
|
82
88
|
}[] | undefined;
|
|
83
89
|
supportedModes?: ("pull" | "push")[] | undefined;
|
|
90
|
+
zones?: true | number[] | undefined;
|
|
84
91
|
} | undefined;
|
|
85
92
|
}, {
|
|
86
93
|
machineTokenEnabled?: boolean | undefined;
|
|
@@ -99,6 +106,7 @@ export declare function tempo(parameters?: tempo.Parameters): readonly [{
|
|
|
99
106
|
recipient: `0x${string}`;
|
|
100
107
|
}[] | undefined;
|
|
101
108
|
supportedModes?: ("pull" | "push")[] | undefined;
|
|
109
|
+
zones?: true | number[] | undefined;
|
|
102
110
|
}>>;
|
|
103
111
|
};
|
|
104
112
|
readonly createCredential: import("../../Method.js").CreateCredentialFn<{
|
|
@@ -115,6 +123,11 @@ export declare function tempo(parameters?: tempo.Parameters): readonly [{
|
|
|
115
123
|
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniObject<{
|
|
116
124
|
signature: import("zod/mini").ZodMiniString<string>;
|
|
117
125
|
type: import("zod/mini").ZodMiniLiteral<"proof">;
|
|
126
|
+
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniObject<{
|
|
127
|
+
hash: import("zod/mini").ZodMiniString<string>;
|
|
128
|
+
senderTag: import("zod/mini").ZodMiniString<string>;
|
|
129
|
+
type: import("zod/mini").ZodMiniLiteral<"zone">;
|
|
130
|
+
zoneId: import("zod/mini").ZodMiniNumber<number>;
|
|
118
131
|
}, import("zod/v4/core").$strip>], "type">;
|
|
119
132
|
};
|
|
120
133
|
request: import("zod/mini").ZodMiniPipe<import("zod/mini").ZodMiniObject<{
|
|
@@ -137,6 +150,7 @@ export declare function tempo(parameters?: tempo.Parameters): readonly [{
|
|
|
137
150
|
pull: "pull";
|
|
138
151
|
push: "push";
|
|
139
152
|
}>>>;
|
|
153
|
+
zones: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniUnion<readonly [import("zod/mini").ZodMiniLiteral<true>, import("zod/mini").ZodMiniArray<import("zod/mini").ZodMiniNumber<number>>]>>;
|
|
140
154
|
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniTransform<{
|
|
141
155
|
currency: string;
|
|
142
156
|
description?: string | undefined;
|
|
@@ -153,6 +167,7 @@ export declare function tempo(parameters?: tempo.Parameters): readonly [{
|
|
|
153
167
|
amount: string;
|
|
154
168
|
}[] | undefined;
|
|
155
169
|
supportedModes?: ("pull" | "push")[] | undefined;
|
|
170
|
+
zones?: true | number[] | undefined;
|
|
156
171
|
} | undefined;
|
|
157
172
|
}, {
|
|
158
173
|
machineTokenEnabled?: boolean | undefined;
|
|
@@ -171,6 +186,7 @@ export declare function tempo(parameters?: tempo.Parameters): readonly [{
|
|
|
171
186
|
recipient: `0x${string}`;
|
|
172
187
|
}[] | undefined;
|
|
173
188
|
supportedModes?: ("pull" | "push")[] | undefined;
|
|
189
|
+
zones?: true | number[] | undefined;
|
|
174
190
|
}>>;
|
|
175
191
|
};
|
|
176
192
|
}, {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Signature } from 'ox';
|
|
1
2
|
import { TempoAddress } from 'ox/tempo';
|
|
2
3
|
import type { Hex } from 'viem';
|
|
3
4
|
import type { Account } from 'viem';
|
|
@@ -67,50 +68,99 @@ export declare function fillHostedFeePayerTransaction(parameters: {
|
|
|
67
68
|
transaction: SponsoredTransaction;
|
|
68
69
|
}): Promise<{
|
|
69
70
|
feePayer: `0x${string}`;
|
|
71
|
+
feePayerSignature: {
|
|
72
|
+
readonly r: bigint;
|
|
73
|
+
readonly s: bigint;
|
|
74
|
+
readonly yParity: number;
|
|
75
|
+
};
|
|
70
76
|
feeToken: TempoAddress.Address;
|
|
71
77
|
serializedTransaction: `0x${string}`;
|
|
72
78
|
}>;
|
|
73
79
|
/**
|
|
74
80
|
* Returns a transaction shape suitable for pre-broadcast simulation.
|
|
75
81
|
*
|
|
76
|
-
* Sponsored transactions are first simulated as calls from the sender
|
|
77
|
-
* fee
|
|
78
|
-
*
|
|
79
|
-
*
|
|
82
|
+
* Sponsored transactions are first simulated as calls from the sender, without
|
|
83
|
+
* signatures, fee payer or fee token, and with zero fee fields: Tempo nodes
|
|
84
|
+
* (v1.18+) skip the fee-token balance check for a zero-fee `eth_call`, so the
|
|
85
|
+
* sender's fee balance is irrelevant. The call keeps the transaction's exact
|
|
86
|
+
* gas limit and the fields that set its intrinsic gas (nonce, nonce key,
|
|
87
|
+
* validity window, key authorization, signature type), so it is the gas-bounded
|
|
88
|
+
* check at the broadcast limit: a transaction whose gas is too low fails here
|
|
89
|
+
* with out of gas instead of reverting onchain at the sponsor's expense.
|
|
90
|
+
* Transferred value and call-level balances are still checked by the RPC.
|
|
80
91
|
*/
|
|
81
92
|
export declare function simulationTransaction(transaction: SponsoredTransaction, options: {
|
|
82
93
|
feePayer: boolean;
|
|
83
94
|
}): any;
|
|
84
95
|
/**
|
|
85
96
|
* Returns the final fee-sponsored transaction shape for pre-broadcast
|
|
86
|
-
* simulation.
|
|
97
|
+
* simulation.
|
|
98
|
+
*
|
|
99
|
+
* Tempo nodes recover the fee payer of an `eth_call` only from
|
|
100
|
+
* `feePayerSignature`; a bare `feePayer` address is ignored and the sender is
|
|
101
|
+
* billed instead. Pass the sponsor's signature so the node charges the fee
|
|
102
|
+
* token to the sponsor, not to a sender that may not hold it. The sender's own
|
|
103
|
+
* signature is never sent.
|
|
87
104
|
*/
|
|
88
105
|
export declare function sponsoredSimulationTransaction(transaction: SponsoredTransaction, options: {
|
|
89
106
|
feePayer: TempoAddress.Address;
|
|
107
|
+
feePayerSignature?: Signature.Signature | undefined;
|
|
90
108
|
feeToken?: TempoAddress.Address | undefined;
|
|
91
109
|
}): any;
|
|
92
110
|
/** A completed sponsorship envelope that can be simulated before signing or broadcast. */
|
|
93
111
|
export type PreflightSponsorship = {
|
|
94
112
|
feePayer: TempoAddress.Address;
|
|
113
|
+
/**
|
|
114
|
+
* The sponsor's signature over the final envelope. Hosted sponsors return it
|
|
115
|
+
* from `eth_fillTransaction`; local accounts are signed in `preflightSponsorship`.
|
|
116
|
+
*/
|
|
117
|
+
feePayerSignature?: Signature.Signature | undefined;
|
|
95
118
|
feeToken?: TempoAddress.Address | undefined;
|
|
96
119
|
transaction: SponsoredTransaction;
|
|
97
120
|
};
|
|
98
121
|
/**
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* and signatures so the sender's fee balance is irrelevant. Next, `complete`
|
|
103
|
-
* resolves the sponsor and produces the co-signed transaction. Finally, it
|
|
104
|
-
* simulates that transaction with its concrete fee payer and fee token.
|
|
105
|
-
*
|
|
106
|
-
* `complete` runs only after sender-context execution succeeds, so a reverting
|
|
107
|
-
* transaction never reaches a local signer or hosted fee-payer.
|
|
122
|
+
* Signs the fee-payer payload of a sponsored envelope with a local fee-payer
|
|
123
|
+
* account, the same payload `Transaction.serialize` signs before broadcast.
|
|
124
|
+
* Returns `undefined` when the envelope's fee payer cannot sign locally.
|
|
108
125
|
*/
|
|
126
|
+
export declare function signFeePayerSimulation(transaction: SponsoredTransaction): Promise<Signature.Signature | undefined>;
|
|
109
127
|
export declare function preflightSponsorship<sponsorship extends PreflightSponsorship>(parameters: {
|
|
110
128
|
complete: () => Promise<sponsorship>;
|
|
129
|
+
/** The fee payer policy the completed transaction was checked against. */
|
|
130
|
+
policy?: Partial<Policy> | undefined;
|
|
111
131
|
simulate: (request: Record<string, unknown>) => Promise<unknown>;
|
|
112
132
|
transaction: SponsoredTransaction;
|
|
113
133
|
}): Promise<sponsorship>;
|
|
134
|
+
/**
|
|
135
|
+
* Returns the gas limit of a local fee payer's simulation-only copy: the
|
|
136
|
+
* transaction's gas plus {@link sponsoredSimulationGasBuffer}, clamped to the
|
|
137
|
+
* fee payer policy's `maxGas` and to `maxTotalFee / maxFeePerGas`, so the
|
|
138
|
+
* signed copy never exceeds a cap the real transaction is held to.
|
|
139
|
+
*/
|
|
140
|
+
export declare function sponsoredSimulationGas(transaction: Pick<SponsoredTransaction, 'chainId' | 'gas' | 'maxFeePerGas'>, policyOverrides?: Partial<Policy> | undefined): bigint;
|
|
141
|
+
/**
|
|
142
|
+
* Returns true when a simulation failed only because it ran out of gas.
|
|
143
|
+
*
|
|
144
|
+
* Tempo nodes report out of gas as `out of gas: …` (code -32003, shared with
|
|
145
|
+
* `insufficient fee token balance`) or `intrinsic gas too low` (code -32000),
|
|
146
|
+
* so this reads the node's own message rather than the code. Only the terminal
|
|
147
|
+
* RPC failure counts: the deepest `details` in the cause chain (viem's
|
|
148
|
+
* `RpcRequestError` carries the node's message there), or the deepest message
|
|
149
|
+
* when no error has details. Wrapper messages are ignored, the match is anchored
|
|
150
|
+
* to the start of the node's message, and an error carrying revert data is never
|
|
151
|
+
* treated as gas exhaustion, so a revert reason or wrapper text that merely
|
|
152
|
+
* mentions gas cannot hide another failure.
|
|
153
|
+
*/
|
|
154
|
+
export declare function isOutOfGasError(error: unknown): boolean;
|
|
155
|
+
/**
|
|
156
|
+
* Extra gas a local fee payer signs into its simulation-only envelope. Tempo's sponsored
|
|
157
|
+
* `eth_call` charges more gas than the same transaction uses when mined (about 6,000 for a
|
|
158
|
+
* session top-up on v1.18.1), so simulating at the signed limit would reject transactions
|
|
159
|
+
* that succeed onchain. The padding is clamped to the fee payer policy; hosted signatures
|
|
160
|
+
* cover the client's gas and are simulated as signed. In both cases the gas bound at the
|
|
161
|
+
* broadcast limit comes from the sender simulation.
|
|
162
|
+
*/
|
|
163
|
+
export declare const sponsoredSimulationGasBuffer = 10000n;
|
|
114
164
|
/**
|
|
115
165
|
* Validates an allowed fee-payer pattern. Returns the funding currency when the
|
|
116
166
|
* calls are bound to the expected payment transfers.
|