mppx 0.17.0 → 0.17.1
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 +17 -0
- package/dist/BodyDigest.js +13 -1
- package/dist/Method.d.ts +5 -0
- package/dist/cli/cli.js +133 -133
- package/dist/cli/plugins/index.js +11 -11
- package/dist/cli/sessions/store.js +7 -1
- package/dist/cli/validate/helpers.d.ts +1 -1
- package/dist/cli/validate/helpers.js +14 -2
- package/dist/client/Mppx.js +11 -3
- package/dist/client/internal/Fetch.d.ts +2 -0
- package/dist/client/internal/Fetch.js +59 -3
- package/dist/internal/AcceptPayment.d.ts +12 -2
- package/dist/internal/AcceptPayment.js +12 -3
- package/dist/internal/version.d.ts +2 -2
- package/dist/internal/version.js +1 -1
- package/dist/mcp/client/McpClient.js +1 -1
- package/dist/proxy/Proxy.d.ts +15 -0
- package/dist/proxy/Proxy.js +89 -1
- package/dist/proxy/internal/Replay.d.ts +105 -0
- package/dist/proxy/internal/Replay.js +257 -0
- package/dist/server/Mppx.d.ts +2 -0
- package/dist/server/Mppx.js +2 -1
- package/dist/tempo/client/Charge.d.ts +9 -0
- package/dist/tempo/client/Charge.js +13 -9
- package/dist/tempo/client/Methods.d.ts +2 -0
- package/dist/tempo/server/Charge.js +4 -1
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/session/client/ChannelOps.d.ts +2 -0
- package/dist/tempo/session/client/ChannelStore.js +5 -0
- package/dist/tempo/session/client/Session.d.ts +26 -1
- package/dist/tempo/session/client/Session.js +72 -1
- package/dist/tempo/session/client/SessionManager.d.ts +1 -0
- package/dist/tempo/session/client/SessionManager.js +54 -7
- package/dist/tempo/session/client/Transports.d.ts +2 -0
- package/dist/tempo/session/client/Transports.js +31 -2
- package/dist/tempo/session/precompile/Voucher.js +20 -7
- package/dist/tempo/session/server/Session.d.ts +2 -1
- package/dist/tempo/session/server/Session.js +2 -1
- package/dist/tempo/session/server/Settlement.d.ts +4 -2
- package/dist/tempo/session/server/Settlement.js +2 -2
- package/dist/tempo/session/server/Ws.js +29 -12
- package/package.json +1 -1
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { Base64, Bytes, Hash } from 'ox';
|
|
2
|
+
import * as Credential from '../../Credential.js';
|
|
3
|
+
const keyPrefix = 'mppx:proxy:replay:';
|
|
4
|
+
/** Applies defaults to replay options. */
|
|
5
|
+
export function resolve(options) {
|
|
6
|
+
const ttl = options.ttl ?? 900_000;
|
|
7
|
+
// AbortSignal.timeout needs a positive integer that fits a 32-bit timer.
|
|
8
|
+
if (!Number.isSafeInteger(ttl) || ttl <= 0 || ttl > 2_147_483_647)
|
|
9
|
+
throw new RangeError('replay.ttl must be a positive integer number of milliseconds up to 2147483647.');
|
|
10
|
+
return {
|
|
11
|
+
maxAttempts: options.maxAttempts ?? 3,
|
|
12
|
+
maxBodySize: options.maxBodySize ?? 1_048_576,
|
|
13
|
+
maxRequestBodySize: options.maxRequestBodySize ?? 1_048_576,
|
|
14
|
+
retryAfter: options.retryAfter ?? 5,
|
|
15
|
+
store: options.store,
|
|
16
|
+
ttl,
|
|
17
|
+
waitUntil: options.waitUntil,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** Returns the request's Payment credential header value, if it carries one. */
|
|
21
|
+
export function credentialOf(request, headerNames = ['authorization']) {
|
|
22
|
+
// Only the headers the route's payment handler verifies can name a replay.
|
|
23
|
+
for (const name of headerNames) {
|
|
24
|
+
const header = request.headers.get(name);
|
|
25
|
+
const payment = header ? Credential.extractPaymentScheme(header) : null;
|
|
26
|
+
if (payment)
|
|
27
|
+
return payment;
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
/** Reads a request body, or returns null when it is larger than maxRequestBodySize. */
|
|
32
|
+
export async function readBody(request, maxRequestBodySize) {
|
|
33
|
+
const length = Number(request.headers.get('content-length'));
|
|
34
|
+
if (Number.isFinite(length) && length > maxRequestBodySize)
|
|
35
|
+
return null;
|
|
36
|
+
if (!request.body)
|
|
37
|
+
return new Uint8Array();
|
|
38
|
+
return keep(request.body, maxRequestBodySize);
|
|
39
|
+
}
|
|
40
|
+
/** Derives the replay key from the credential and the exact request (origin, path, query, method, body) it paid for. */
|
|
41
|
+
export function key(parameters) {
|
|
42
|
+
const bodyHash = Hash.sha256(parameters.body, { as: 'Hex' });
|
|
43
|
+
const input = JSON.stringify([
|
|
44
|
+
parameters.credential,
|
|
45
|
+
parameters.method.toUpperCase(),
|
|
46
|
+
parameters.target,
|
|
47
|
+
bodyHash,
|
|
48
|
+
]);
|
|
49
|
+
return keyPrefix + Hash.sha256(Bytes.fromString(input), { as: 'Hex' }).slice(2);
|
|
50
|
+
}
|
|
51
|
+
/** Rebuilds a request around an already-read body, optionally with another abort signal. */
|
|
52
|
+
export function withBody(request, body, signal, headers) {
|
|
53
|
+
const method = request.method.toUpperCase();
|
|
54
|
+
const hasBody = method !== 'GET' && method !== 'HEAD';
|
|
55
|
+
return new globalThis.Request(request.url, {
|
|
56
|
+
// An empty body stays absent, as on the original request: payment
|
|
57
|
+
// transports tell management requests apart by a missing body.
|
|
58
|
+
body: hasBody && body.byteLength > 0 ? body : null,
|
|
59
|
+
headers: headers ?? request.headers,
|
|
60
|
+
method: request.method,
|
|
61
|
+
redirect: request.redirect,
|
|
62
|
+
signal: signal ?? request.signal,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/** Looks up a delivery and, for a failed one, claims the next upstream attempt. */
|
|
66
|
+
export async function lookup(replay, replayKey) {
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
// Read first: only a failed delivery needs an atomic claim, so unknown
|
|
69
|
+
// credentials never cause a store write.
|
|
70
|
+
const seen = (await replay.store.get(replayKey));
|
|
71
|
+
if (!seen)
|
|
72
|
+
return { type: 'none' };
|
|
73
|
+
if (seen.expires > now && seen.status !== 'failed')
|
|
74
|
+
return { response: respond(replay, seen), type: 'respond' };
|
|
75
|
+
const result = await replay.store.update(replayKey, (current) => {
|
|
76
|
+
const record = current;
|
|
77
|
+
if (!record)
|
|
78
|
+
return { op: 'noop', result: { type: 'none' } };
|
|
79
|
+
if (record.expires <= now)
|
|
80
|
+
return { op: 'delete', result: { type: 'none' } };
|
|
81
|
+
if (record.status !== 'failed' || record.attempts >= replay.maxAttempts)
|
|
82
|
+
return { op: 'noop', result: record };
|
|
83
|
+
const attempts = record.attempts + 1;
|
|
84
|
+
return {
|
|
85
|
+
op: 'set',
|
|
86
|
+
value: { ...record, attempts, status: 'pending' },
|
|
87
|
+
result: {
|
|
88
|
+
attempts,
|
|
89
|
+
receipt: record.receipt,
|
|
90
|
+
requestHeaders: record.requestHeaders,
|
|
91
|
+
type: 'retry',
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
if ('type' in result)
|
|
96
|
+
return result;
|
|
97
|
+
return { response: respond(replay, result), type: 'respond' };
|
|
98
|
+
}
|
|
99
|
+
/** Builds the response for a recorded delivery that is not retried. */
|
|
100
|
+
function respond(replay, record) {
|
|
101
|
+
if (record.status === 'complete')
|
|
102
|
+
return fromRecord(record);
|
|
103
|
+
if (record.status === 'delivered')
|
|
104
|
+
return Response.json({ error: 'The paid response was delivered and is not stored for replay.' }, { status: 410 });
|
|
105
|
+
if (record.status === 'pending')
|
|
106
|
+
return new Response(null, {
|
|
107
|
+
headers: { 'Retry-After': String(replay.retryAfter) },
|
|
108
|
+
status: 202,
|
|
109
|
+
});
|
|
110
|
+
return withReceiptHeader(Response.json({ error: 'The paid upstream request failed.' }, { status: 502 }), record.receipt);
|
|
111
|
+
}
|
|
112
|
+
/** Runs a paid upstream delivery detached from the client and records its outcome. */
|
|
113
|
+
export async function deliver(parameters) {
|
|
114
|
+
const { applyReceipt, attempts, replay, replayKey, requestHeaders } = parameters;
|
|
115
|
+
// Outcomes are kept for ttl from when they are recorded, not from the start.
|
|
116
|
+
const expires = () => Date.now() + replay.ttl;
|
|
117
|
+
await record(replay, replayKey, {
|
|
118
|
+
attempts,
|
|
119
|
+
expires: expires(),
|
|
120
|
+
receipt: null,
|
|
121
|
+
requestHeaders,
|
|
122
|
+
status: 'pending',
|
|
123
|
+
});
|
|
124
|
+
let response;
|
|
125
|
+
try {
|
|
126
|
+
// ttl bounds the wait for the upstream to respond, not how long its body
|
|
127
|
+
// streams: a paid stream keeps going once it has started.
|
|
128
|
+
const controller = new AbortController();
|
|
129
|
+
const timer = setTimeout(() => controller.abort(new DOMException('Replay ttl elapsed.', 'TimeoutError')), replay.ttl);
|
|
130
|
+
let upstream;
|
|
131
|
+
try {
|
|
132
|
+
upstream = await parameters.run(controller.signal);
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
clearTimeout(timer);
|
|
136
|
+
}
|
|
137
|
+
response = await applyReceipt(upstream);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
const failed = await applyReceipt(Response.json({ error: 'The paid upstream request failed.' }, { status: 502 }));
|
|
141
|
+
await recordFailure(replay, replayKey, attempts, requestHeaders, expires(), failed.headers.get('Payment-Receipt'));
|
|
142
|
+
return failed;
|
|
143
|
+
}
|
|
144
|
+
const receipt = response.headers.get('Payment-Receipt');
|
|
145
|
+
if (response.status >= 500) {
|
|
146
|
+
await recordFailure(replay, replayKey, attempts, requestHeaders, expires(), receipt);
|
|
147
|
+
return response;
|
|
148
|
+
}
|
|
149
|
+
if (!response.body) {
|
|
150
|
+
await record(replay, replayKey, toRecord(response, new Uint8Array(), expires()));
|
|
151
|
+
return response;
|
|
152
|
+
}
|
|
153
|
+
if (/^text\/event-stream/i.test(response.headers.get('content-type') ?? '')) {
|
|
154
|
+
await record(replay, replayKey, { expires: expires(), status: 'delivered' });
|
|
155
|
+
return response;
|
|
156
|
+
}
|
|
157
|
+
// Keep a copy while the client reads the other branch, so a client that
|
|
158
|
+
// disconnects mid-body can still collect the response on retry.
|
|
159
|
+
const [client, kept] = response.body.tee();
|
|
160
|
+
const stored = keep(kept, replay.maxBodySize)
|
|
161
|
+
.then((body) => replay.store.put(replayKey, body
|
|
162
|
+
? toRecord(response, body, expires())
|
|
163
|
+
: { expires: expires(), status: 'delivered' }),
|
|
164
|
+
// The upstream body broke off: let the payer retry the delivery.
|
|
165
|
+
() => recordFailure(replay, replayKey, attempts, requestHeaders, expires(), receipt))
|
|
166
|
+
// A failed background write leaves the record pending until it expires.
|
|
167
|
+
.catch(() => { });
|
|
168
|
+
replay.waitUntil?.(stored);
|
|
169
|
+
return new Response(client, {
|
|
170
|
+
headers: response.headers,
|
|
171
|
+
status: response.status,
|
|
172
|
+
statusText: response.statusText,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/** Reads a body branch up to maxBodySize, or returns null when it is larger. */
|
|
176
|
+
async function keep(stream, maxBodySize) {
|
|
177
|
+
const reader = stream.getReader();
|
|
178
|
+
const chunks = [];
|
|
179
|
+
let size = 0;
|
|
180
|
+
for (;;) {
|
|
181
|
+
const { done, value } = await reader.read();
|
|
182
|
+
if (done)
|
|
183
|
+
break;
|
|
184
|
+
size += value.byteLength;
|
|
185
|
+
if (size > maxBodySize) {
|
|
186
|
+
await reader.cancel();
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
chunks.push(value);
|
|
190
|
+
}
|
|
191
|
+
const body = new Uint8Array(size);
|
|
192
|
+
let offset = 0;
|
|
193
|
+
for (const chunk of chunks) {
|
|
194
|
+
body.set(chunk, offset);
|
|
195
|
+
offset += chunk.byteLength;
|
|
196
|
+
}
|
|
197
|
+
return body;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Writes a replay record without letting a store failure break the paid
|
|
201
|
+
* delivery: the payer still gets the upstream response, only replay is lost.
|
|
202
|
+
*/
|
|
203
|
+
async function record(replay, replayKey, value) {
|
|
204
|
+
try {
|
|
205
|
+
await replay.store.put(replayKey, value);
|
|
206
|
+
}
|
|
207
|
+
catch { }
|
|
208
|
+
}
|
|
209
|
+
async function recordFailure(replay, replayKey, attempts, requestHeaders, expires, receipt) {
|
|
210
|
+
await record(replay, replayKey, {
|
|
211
|
+
attempts,
|
|
212
|
+
expires,
|
|
213
|
+
receipt,
|
|
214
|
+
requestHeaders,
|
|
215
|
+
status: 'failed',
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
/** Adds a stored receipt to a response built for a retried delivery. */
|
|
219
|
+
export function withReceiptHeader(response, receipt) {
|
|
220
|
+
if (!receipt)
|
|
221
|
+
return response;
|
|
222
|
+
const headers = new Headers(response.headers);
|
|
223
|
+
headers.set('Payment-Receipt', receipt);
|
|
224
|
+
headers.set('Cache-Control', withPrivateCacheControl(headers.get('Cache-Control')));
|
|
225
|
+
return new Response(response.body, {
|
|
226
|
+
headers,
|
|
227
|
+
status: response.status,
|
|
228
|
+
statusText: response.statusText,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
function withPrivateCacheControl(value) {
|
|
232
|
+
if (!value)
|
|
233
|
+
return 'private';
|
|
234
|
+
const directives = value.split(',').map((directive) => directive.trim().toLowerCase());
|
|
235
|
+
if (directives.includes('private'))
|
|
236
|
+
return value;
|
|
237
|
+
return `${value}, private`;
|
|
238
|
+
}
|
|
239
|
+
function toRecord(response, body, expires) {
|
|
240
|
+
return {
|
|
241
|
+
body: Base64.fromBytes(body),
|
|
242
|
+
expires,
|
|
243
|
+
headers: [...response.headers.entries()],
|
|
244
|
+
responseStatus: response.status,
|
|
245
|
+
status: 'complete',
|
|
246
|
+
statusText: response.statusText,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function fromRecord(record) {
|
|
250
|
+
const nullBody = [101, 204, 205, 304].includes(record.responseStatus);
|
|
251
|
+
return new Response(nullBody ? null : Base64.toBytes(record.body), {
|
|
252
|
+
headers: record.headers,
|
|
253
|
+
status: record.responseStatus,
|
|
254
|
+
statusText: record.statusText,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=Replay.js.map
|
package/dist/server/Mppx.d.ts
CHANGED
|
@@ -414,6 +414,8 @@ type ConfiguredHandler<transport extends Transport.AnyTransport = Transport.Http
|
|
|
414
414
|
_stableBinding?: Method.StableBindingFn<Method.Method> | undefined;
|
|
415
415
|
/** The offer's payment-stage identity verifiers, if any. */
|
|
416
416
|
_paymentIdentity?: readonly Identity.Verifier[] | undefined;
|
|
417
|
+
/** Whether the route requires identity verifiers (request or payment stage). */
|
|
418
|
+
_identity?: boolean | undefined;
|
|
417
419
|
_transport: Transport.AnyTransport;
|
|
418
420
|
};
|
|
419
421
|
};
|
package/dist/server/Mppx.js
CHANGED
|
@@ -468,6 +468,7 @@ function createMethodFn(parameters) {
|
|
|
468
468
|
],
|
|
469
469
|
_canonicalRequest: PaymentRequest.fromMethod(method, { ...defaults, ...rest }),
|
|
470
470
|
_paymentIdentity: paymentVerifiers.length > 0 ? paymentVerifiers : undefined,
|
|
471
|
+
_identity: identity.length > 0,
|
|
471
472
|
_stableBinding: stableBinding,
|
|
472
473
|
_transport: transport,
|
|
473
474
|
};
|
|
@@ -1916,11 +1917,11 @@ function composeHandlers(handlers, composeOptions, selectOffers) {
|
|
|
1916
1917
|
composed._internal = { offers, handlers: handlers, selectOffers };
|
|
1917
1918
|
return composed;
|
|
1918
1919
|
}
|
|
1919
|
-
/** Returns the flattened configured offers accepted by a handler. */
|
|
1920
1920
|
/** Whether two verifier lists hold the same verifiers in the same order. */
|
|
1921
1921
|
function sameVerifiers(a, b) {
|
|
1922
1922
|
return a.length === b.length && a.every((verifier, i) => verifier === b[i]);
|
|
1923
1923
|
}
|
|
1924
|
+
/** Returns the flattened configured offers accepted by a handler. */
|
|
1924
1925
|
function getConfiguredOffers(handler) {
|
|
1925
1926
|
const internal = handler._internal;
|
|
1926
1927
|
if (!internal)
|
|
@@ -21,6 +21,7 @@ export declare const validityWindowSeconds = 25;
|
|
|
21
21
|
export type ChargeContext = {
|
|
22
22
|
account?: Account.getResolver.Parameters['account'] | undefined;
|
|
23
23
|
autoSwap?: AutoSwap.resolve.Value | undefined;
|
|
24
|
+
feeToken?: Address | undefined;
|
|
24
25
|
mode?: Methods.ChargeMode | undefined;
|
|
25
26
|
};
|
|
26
27
|
/**
|
|
@@ -41,6 +42,7 @@ export declare function charge(parameters?: charge.Parameters): {
|
|
|
41
42
|
readonly context?: z.ZodMiniObject<{
|
|
42
43
|
account: z.ZodMiniOptional<z.ZodMiniCustom<`0x${string}` | Account.Account | undefined, `0x${string}` | Account.Account | undefined>>;
|
|
43
44
|
autoSwap: z.ZodMiniOptional<z.ZodMiniCustom<AutoSwap.resolve.Value | undefined, AutoSwap.resolve.Value | undefined>>;
|
|
45
|
+
feeToken: z.ZodMiniOptional<z.ZodMiniCustom<`0x${string}` | undefined, `0x${string}` | undefined>>;
|
|
44
46
|
mode: z.ZodMiniOptional<z.ZodMiniEnum<{
|
|
45
47
|
pull: "pull";
|
|
46
48
|
push: "push";
|
|
@@ -192,6 +194,7 @@ export declare function charge(parameters?: charge.Parameters): {
|
|
|
192
194
|
}, {
|
|
193
195
|
account?: `0x${string}` | Account.Account | undefined;
|
|
194
196
|
autoSwap?: AutoSwap.resolve.Value | undefined;
|
|
197
|
+
feeToken?: `0x${string}` | undefined;
|
|
195
198
|
mode?: "pull" | "push" | undefined;
|
|
196
199
|
}>;
|
|
197
200
|
};
|
|
@@ -210,6 +213,12 @@ export declare namespace charge {
|
|
|
210
213
|
autoSwap?: AutoSwap | undefined;
|
|
211
214
|
/** Client identifier used to derive the client fingerprint in attribution memos. */
|
|
212
215
|
clientId?: string | undefined;
|
|
216
|
+
/**
|
|
217
|
+
* Token the payer pays its transaction fee in, when the challenge is not fee-sponsored.
|
|
218
|
+
* Set it for an access key whose spending limits do not cover the default fee token,
|
|
219
|
+
* such as a key limited to OUSD paying a USDC.e charge.
|
|
220
|
+
*/
|
|
221
|
+
feeToken?: Address | undefined;
|
|
213
222
|
/**
|
|
214
223
|
* Chain ID this client is willing to pay on. When set, the client rejects
|
|
215
224
|
* any challenge whose `methodDetails.chainId` differs, and signs on this
|
|
@@ -31,6 +31,7 @@ export const validityWindowSeconds = 25;
|
|
|
31
31
|
const chargeContextSchema = z.object({
|
|
32
32
|
account: z.optional(z.custom()),
|
|
33
33
|
autoSwap: z.optional(z.custom()),
|
|
34
|
+
feeToken: z.optional(z.custom()),
|
|
34
35
|
mode: z.optional(z.enum(Methods.chargeModes)),
|
|
35
36
|
});
|
|
36
37
|
/**
|
|
@@ -188,15 +189,18 @@ export function charge(parameters = {}) {
|
|
|
188
189
|
}
|
|
189
190
|
const calls = machineTokenRoute?.calls ?? [...(swapCalls ?? []), ...transferCalls];
|
|
190
191
|
const allowedFeeTokens = defaultFeeTokens(chainId);
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
192
|
+
const explicitFeeToken = context?.feeToken ?? parameters.feeToken;
|
|
193
|
+
const feeToken = explicitFeeToken
|
|
194
|
+
? explicitFeeToken
|
|
195
|
+
: machineTokenRoute || isMachineTokenCurrency
|
|
196
|
+
? await resolveFeeToken({
|
|
197
|
+
account: account.address,
|
|
198
|
+
allowedTokens: allowedFeeTokens,
|
|
199
|
+
candidateTokens: allowedFeeTokens,
|
|
200
|
+
client,
|
|
201
|
+
prioritizeCandidates: true,
|
|
202
|
+
})
|
|
203
|
+
: undefined;
|
|
200
204
|
const mode = (() => {
|
|
201
205
|
const explicitMode = context?.mode ?? parameters.mode;
|
|
202
206
|
if (explicitMode) {
|
|
@@ -24,6 +24,7 @@ export declare function tempo(parameters?: tempo.Parameters): readonly [{
|
|
|
24
24
|
readonly context?: import("zod/mini").ZodMiniObject<{
|
|
25
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
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
|
+
feeToken: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniCustom<`0x${string}` | undefined, `0x${string}` | undefined>>;
|
|
27
28
|
mode: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniEnum<{
|
|
28
29
|
pull: "pull";
|
|
29
30
|
push: "push";
|
|
@@ -175,6 +176,7 @@ export declare function tempo(parameters?: tempo.Parameters): readonly [{
|
|
|
175
176
|
}, {
|
|
176
177
|
account?: `0x${string}` | import("../../viem/Account.js").Account | undefined;
|
|
177
178
|
autoSwap?: import("../internal/auto-swap.js").resolve.Value | undefined;
|
|
179
|
+
feeToken?: `0x${string}` | undefined;
|
|
178
180
|
mode?: "pull" | "push" | undefined;
|
|
179
181
|
}>;
|
|
180
182
|
}, import("../../Method.js").Client<{
|
|
@@ -1042,6 +1042,8 @@ const ambiguousErrorNames = new Set([
|
|
|
1042
1042
|
* - JSON-RPC code `4`: the EIP-7966 timeout for a pooled, unconfirmed transaction.
|
|
1043
1043
|
* - Messages containing the words "already known" or "known transaction": already
|
|
1044
1044
|
* pooled ("unknown transaction type" does not match).
|
|
1045
|
+
* - Tempo's `ExpiringNonceReplay`: an expiring-nonce transaction with this hash was
|
|
1046
|
+
* already accepted, such as when a transport retries a send the node took.
|
|
1045
1047
|
*/
|
|
1046
1048
|
function isAmbiguousBroadcastError(error) {
|
|
1047
1049
|
for (let cause = error; cause; cause = cause.cause) {
|
|
@@ -1054,7 +1056,8 @@ function isAmbiguousBroadcastError(error) {
|
|
|
1054
1056
|
return true;
|
|
1055
1057
|
if (code === 4)
|
|
1056
1058
|
return true;
|
|
1057
|
-
if (typeof message === 'string' &&
|
|
1059
|
+
if (typeof message === 'string' &&
|
|
1060
|
+
/\balready ?known\b|\bknown transaction\b|\bExpiringNonceReplay\b/i.test(message))
|
|
1058
1061
|
return true;
|
|
1059
1062
|
}
|
|
1060
1063
|
return false;
|