mppx 0.17.0 → 0.17.3
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 +45 -0
- package/dist/BodyDigest.js +13 -1
- package/dist/Method.d.ts +5 -0
- package/dist/Store.d.ts +225 -0
- package/dist/Store.js +358 -0
- package/dist/cli/cli.js +136 -135
- package/dist/cli/plugins/index.js +12 -11
- package/dist/cli/sessions/Manager.js +12 -1
- package/dist/cli/sessions/store.js +7 -1
- package/dist/cli/utils.d.ts +2 -0
- package/dist/cli/utils.js +6 -0
- package/dist/cli/validate/helpers.d.ts +1 -1
- package/dist/cli/validate/helpers.js +14 -2
- package/dist/client/Mppx.d.ts +1 -1
- package/dist/client/Mppx.js +18 -6
- package/dist/client/internal/Fetch.d.ts +3 -1
- package/dist/client/internal/Fetch.js +68 -3
- 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/identity/Client.js +7 -1
- package/dist/internal/AcceptPayment.d.ts +12 -2
- package/dist/internal/AcceptPayment.js +12 -3
- package/dist/internal/sse.d.ts +21 -0
- package/dist/internal/sse.js +65 -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 +6 -3
- package/dist/proxy/Proxy.d.ts +17 -0
- package/dist/proxy/Proxy.js +116 -3
- 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 +7 -5
- 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/internal/html.gen.js +1 -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/internal/fee-payer.d.ts +85 -10
- package/dist/tempo/internal/fee-payer.js +183 -19
- package/dist/tempo/legacy/session/Chain.js +2 -0
- package/dist/tempo/server/Charge.js +6 -3
- 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/ChannelOps.d.ts +2 -0
- package/dist/tempo/session/client/ChannelStore.d.ts +2 -0
- package/dist/tempo/session/client/ChannelStore.js +5 -0
- package/dist/tempo/session/client/CredentialState.js +2 -0
- package/dist/tempo/session/client/Session.d.ts +37 -2
- package/dist/tempo/session/client/Session.js +111 -7
- package/dist/tempo/session/client/SessionManager.d.ts +2 -1
- package/dist/tempo/session/client/SessionManager.js +60 -9
- package/dist/tempo/session/client/Transports.d.ts +4 -0
- package/dist/tempo/session/client/Transports.js +35 -3
- package/dist/tempo/session/precompile/Chain.js +1 -0
- package/dist/tempo/session/precompile/Voucher.js +20 -7
- package/dist/tempo/session/server/ChannelStore.d.ts +9 -0
- package/dist/tempo/session/server/ChannelStore.js +8 -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/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/Sse.d.ts +59 -0
- package/dist/tempo/session/server/Sse.js +175 -10
- package/dist/tempo/session/server/Ws.js +29 -12
- package/dist/zod.d.ts +2 -1
- package/dist/zod.js +1 -1
- package/package.json +1 -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 { Address, Secp256k1 } from 'ox';
|
|
1
|
+
import { Address, Secp256k1, Signature } from 'ox';
|
|
2
2
|
import { TempoAddress, TxEnvelopeTempo } from 'ox/tempo';
|
|
3
3
|
import { decodeFunctionData, encodeFunctionData, maxUint256, toFunctionSelector, toHex } from 'viem';
|
|
4
4
|
import { Abis, Addresses, Transaction } from 'viem/tempo';
|
|
@@ -163,6 +163,12 @@ export async function fillHostedFeePayerTransaction(parameters) {
|
|
|
163
163
|
})();
|
|
164
164
|
return {
|
|
165
165
|
feePayer,
|
|
166
|
+
// `Signature.fromRpc` rejects a yParity of 0 that arrives without `v`.
|
|
167
|
+
feePayerSignature: Signature.from({
|
|
168
|
+
r: BigInt(feePayerSignature.r),
|
|
169
|
+
s: BigInt(feePayerSignature.s),
|
|
170
|
+
yParity: feePayerSignature.yParity,
|
|
171
|
+
}),
|
|
166
172
|
feeToken,
|
|
167
173
|
serializedTransaction: await Transaction.serialize({
|
|
168
174
|
...transaction,
|
|
@@ -175,17 +181,36 @@ export async function fillHostedFeePayerTransaction(parameters) {
|
|
|
175
181
|
/**
|
|
176
182
|
* Returns a transaction shape suitable for pre-broadcast simulation.
|
|
177
183
|
*
|
|
178
|
-
* Sponsored transactions are first simulated as calls from the sender
|
|
179
|
-
* fee
|
|
180
|
-
*
|
|
181
|
-
*
|
|
184
|
+
* Sponsored transactions are first simulated as calls from the sender, without
|
|
185
|
+
* signatures, fee payer or fee token, and with zero fee fields: Tempo nodes
|
|
186
|
+
* (v1.18+) skip the fee-token balance check for a zero-fee `eth_call`, so the
|
|
187
|
+
* sender's fee balance is irrelevant. The call keeps the transaction's exact
|
|
188
|
+
* gas limit and the fields that set its intrinsic gas (nonce, nonce key,
|
|
189
|
+
* validity window, key authorization, signature type), so it is the gas-bounded
|
|
190
|
+
* check at the broadcast limit: a transaction whose gas is too low fails here
|
|
191
|
+
* with out of gas instead of reverting onchain at the sponsor's expense.
|
|
192
|
+
* Transferred value and call-level balances are still checked by the RPC.
|
|
182
193
|
*/
|
|
183
194
|
export function simulationTransaction(transaction, options) {
|
|
184
|
-
if (options.feePayer)
|
|
195
|
+
if (options.feePayer) {
|
|
196
|
+
const keyType = senderKeyType(transaction.signature);
|
|
185
197
|
return {
|
|
186
198
|
account: transaction.from,
|
|
187
199
|
calls: transaction.calls,
|
|
200
|
+
...(transaction.accessList?.length ? { accessList: transaction.accessList } : {}),
|
|
201
|
+
...(transaction.gas !== undefined ? { gas: transaction.gas } : {}),
|
|
202
|
+
...(transaction.keyAuthorization !== undefined
|
|
203
|
+
? { keyAuthorization: transaction.keyAuthorization }
|
|
204
|
+
: {}),
|
|
205
|
+
...(keyType ? { keyType } : {}),
|
|
206
|
+
maxFeePerGas: 0n,
|
|
207
|
+
maxPriorityFeePerGas: 0n,
|
|
208
|
+
...(transaction.nonce !== undefined ? { nonce: transaction.nonce } : {}),
|
|
209
|
+
...(transaction.nonceKey !== undefined ? { nonceKey: transaction.nonceKey } : {}),
|
|
210
|
+
...(transaction.validAfter !== undefined ? { validAfter: transaction.validAfter } : {}),
|
|
211
|
+
...(transaction.validBefore !== undefined ? { validBefore: transaction.validBefore } : {}),
|
|
188
212
|
};
|
|
213
|
+
}
|
|
189
214
|
return {
|
|
190
215
|
...transaction,
|
|
191
216
|
account: transaction.from,
|
|
@@ -193,43 +218,182 @@ export function simulationTransaction(transaction, options) {
|
|
|
193
218
|
feePayerSignature: undefined,
|
|
194
219
|
};
|
|
195
220
|
}
|
|
221
|
+
/** The key type hint for a sender signature whose verification costs more than secp256k1. */
|
|
222
|
+
function senderKeyType(signature) {
|
|
223
|
+
const envelope = signature;
|
|
224
|
+
const type = envelope?.type === 'keychain' ? envelope.inner?.type : envelope?.type;
|
|
225
|
+
return type === 'p256' || type === 'webAuthn' ? type : undefined;
|
|
226
|
+
}
|
|
196
227
|
/**
|
|
197
228
|
* Returns the final fee-sponsored transaction shape for pre-broadcast
|
|
198
|
-
* simulation.
|
|
229
|
+
* simulation.
|
|
230
|
+
*
|
|
231
|
+
* Tempo nodes recover the fee payer of an `eth_call` only from
|
|
232
|
+
* `feePayerSignature`; a bare `feePayer` address is ignored and the sender is
|
|
233
|
+
* billed instead. Pass the sponsor's signature so the node charges the fee
|
|
234
|
+
* token to the sponsor, not to a sender that may not hold it. The sender's own
|
|
235
|
+
* signature is never sent.
|
|
199
236
|
*/
|
|
200
237
|
export function sponsoredSimulationTransaction(transaction, options) {
|
|
201
|
-
const { feePayer, feeToken } = options;
|
|
238
|
+
const { feePayer, feePayerSignature, feeToken } = options;
|
|
202
239
|
return {
|
|
203
240
|
...transaction,
|
|
204
241
|
account: transaction.from,
|
|
205
242
|
calls: transaction.calls,
|
|
206
243
|
feePayer,
|
|
207
|
-
feePayerSignature: undefined,
|
|
244
|
+
feePayerSignature: feePayerSignature ? Signature.toRpc(feePayerSignature) : undefined,
|
|
208
245
|
...(feeToken !== undefined ? { feeToken } : {}),
|
|
209
246
|
signature: undefined,
|
|
210
247
|
};
|
|
211
248
|
}
|
|
212
249
|
/**
|
|
213
|
-
*
|
|
250
|
+
* Signs the fee-payer payload of a sponsored envelope with a local fee-payer
|
|
251
|
+
* account, the same payload `Transaction.serialize` signs before broadcast.
|
|
252
|
+
* Returns `undefined` when the envelope's fee payer cannot sign locally.
|
|
253
|
+
*/
|
|
254
|
+
export async function signFeePayerSimulation(transaction) {
|
|
255
|
+
const { account: _account, feePayer, ...rest } = transaction;
|
|
256
|
+
if (typeof feePayer !== 'object' || feePayer === null)
|
|
257
|
+
return undefined;
|
|
258
|
+
const sign = feePayer.sign;
|
|
259
|
+
if (typeof sign !== 'function' || !rest.from)
|
|
260
|
+
return undefined;
|
|
261
|
+
const payload = TxEnvelopeTempo.getFeePayerSignPayload(TxEnvelopeTempo.from({
|
|
262
|
+
...rest,
|
|
263
|
+
chainId: Number(rest.chainId),
|
|
264
|
+
feePayerSignature: undefined,
|
|
265
|
+
signature: undefined,
|
|
266
|
+
type: 'tempo',
|
|
267
|
+
}), { sender: rest.from });
|
|
268
|
+
return Signature.from(await sign({ hash: payload }));
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Runs the sender and final-envelope simulations around sponsorship.
|
|
272
|
+
*
|
|
273
|
+
* First, it simulates the sender's calls at the transaction's exact gas limit
|
|
274
|
+
* with zero fee fields (see {@link simulationTransaction}). This is the
|
|
275
|
+
* gas-bounded check at the broadcast limit, and needs no fee-token balance.
|
|
276
|
+
* Next, `complete` resolves the sponsor and produces the co-signed transaction.
|
|
277
|
+
* Finally, it simulates that transaction with its concrete fee payer, fee token
|
|
278
|
+
* and fee payer signature, so the node bills the sponsor rather than the sender
|
|
279
|
+
* and checks the sponsor's fee balance.
|
|
214
280
|
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
281
|
+
* A sponsored `eth_call` charges more gas than the mined transaction uses. A
|
|
282
|
+
* local fee payer therefore signs a simulation-only copy whose gas is raised by
|
|
283
|
+
* up to {@link sponsoredSimulationGasBuffer}, clamped so the copy stays within
|
|
284
|
+
* the same fee payer `policy` (`maxGas`, `maxTotalFee`) as the real
|
|
285
|
+
* transaction. The copy is never returned or broadcast. When the final
|
|
286
|
+
* simulation runs without the full buffer (hosted signatures cover the client's
|
|
287
|
+
* gas and cannot be padded, or the padding was clamped), a failure that is only
|
|
288
|
+
* out of gas is tolerated, since the sender simulation already bounded gas at
|
|
289
|
+
* the broadcast limit. Every other failure, such as an insufficient fee balance
|
|
290
|
+
* or a revert, is rethrown.
|
|
219
291
|
*
|
|
220
292
|
* `complete` runs only after sender-context execution succeeds, so a reverting
|
|
221
293
|
* transaction never reaches a local signer or hosted fee-payer.
|
|
222
294
|
*/
|
|
223
295
|
export async function preflightSponsorship(parameters) {
|
|
224
|
-
const { complete, simulate, transaction } = parameters;
|
|
296
|
+
const { complete, policy, simulate, transaction } = parameters;
|
|
225
297
|
await simulate(simulationTransaction(transaction, { feePayer: true }));
|
|
226
298
|
const completed = await complete();
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
299
|
+
const { gas } = completed.transaction;
|
|
300
|
+
const simulated = await (async () => {
|
|
301
|
+
if (completed.feePayerSignature)
|
|
302
|
+
return { feePayerSignature: completed.feePayerSignature, transaction: completed.transaction };
|
|
303
|
+
// The signature binds the gas limit, so a local fee payer signs a simulation-only copy
|
|
304
|
+
// with headroom for the node's sponsored `eth_call` overhead. The broadcast keeps its gas.
|
|
305
|
+
const padded = gas === undefined
|
|
306
|
+
? completed.transaction
|
|
307
|
+
: {
|
|
308
|
+
...completed.transaction,
|
|
309
|
+
gas: sponsoredSimulationGas(completed.transaction, policy),
|
|
310
|
+
};
|
|
311
|
+
const feePayerSignature = await signFeePayerSimulation(padded);
|
|
312
|
+
return feePayerSignature
|
|
313
|
+
? { feePayerSignature, transaction: padded }
|
|
314
|
+
: { feePayerSignature: undefined, transaction: completed.transaction };
|
|
315
|
+
})();
|
|
316
|
+
const simulatedGas = simulated.transaction.gas;
|
|
317
|
+
try {
|
|
318
|
+
await simulate(sponsoredSimulationTransaction(simulated.transaction, {
|
|
319
|
+
feePayer: completed.feePayer,
|
|
320
|
+
feePayerSignature: simulated.feePayerSignature,
|
|
321
|
+
feeToken: completed.feeToken,
|
|
322
|
+
}));
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
const unpadded = gas !== undefined &&
|
|
326
|
+
simulatedGas !== undefined &&
|
|
327
|
+
simulatedGas < gas + sponsoredSimulationGasBuffer;
|
|
328
|
+
if (!unpadded || !isOutOfGasError(error))
|
|
329
|
+
throw error;
|
|
330
|
+
}
|
|
231
331
|
return completed;
|
|
232
332
|
}
|
|
333
|
+
/**
|
|
334
|
+
* Returns the gas limit of a local fee payer's simulation-only copy: the
|
|
335
|
+
* transaction's gas plus {@link sponsoredSimulationGasBuffer}, clamped to the
|
|
336
|
+
* fee payer policy's `maxGas` and to `maxTotalFee / maxFeePerGas`, so the
|
|
337
|
+
* signed copy never exceeds a cap the real transaction is held to.
|
|
338
|
+
*/
|
|
339
|
+
export function sponsoredSimulationGas(transaction, policyOverrides) {
|
|
340
|
+
const { chainId, gas, maxFeePerGas } = transaction;
|
|
341
|
+
if (gas === undefined)
|
|
342
|
+
throw new FeePayerValidationError('fee-sponsored transaction must declare gas', {});
|
|
343
|
+
const policy = getPolicy(Number(chainId), policyOverrides);
|
|
344
|
+
let cap = policy.maxGas;
|
|
345
|
+
if (maxFeePerGas !== undefined && maxFeePerGas > 0n) {
|
|
346
|
+
const feeCap = policy.maxTotalFee / maxFeePerGas;
|
|
347
|
+
if (feeCap < cap)
|
|
348
|
+
cap = feeCap;
|
|
349
|
+
}
|
|
350
|
+
if (gas > cap)
|
|
351
|
+
throw new FeePayerValidationError('fee-sponsored transaction gas exceeds sponsor policy', {
|
|
352
|
+
gas: gas.toString(),
|
|
353
|
+
});
|
|
354
|
+
const padded = gas + sponsoredSimulationGasBuffer;
|
|
355
|
+
return padded < cap ? padded : cap;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Returns true when a simulation failed only because it ran out of gas.
|
|
359
|
+
*
|
|
360
|
+
* Tempo nodes report out of gas as `out of gas: …` (code -32003, shared with
|
|
361
|
+
* `insufficient fee token balance`) or `intrinsic gas too low` (code -32000),
|
|
362
|
+
* so this reads the node's own message rather than the code. Only the terminal
|
|
363
|
+
* RPC failure counts: the deepest `details` in the cause chain (viem's
|
|
364
|
+
* `RpcRequestError` carries the node's message there), or the deepest message
|
|
365
|
+
* when no error has details. Wrapper messages are ignored, the match is anchored
|
|
366
|
+
* to the start of the node's message, and an error carrying revert data is never
|
|
367
|
+
* treated as gas exhaustion, so a revert reason or wrapper text that merely
|
|
368
|
+
* mentions gas cannot hide another failure.
|
|
369
|
+
*/
|
|
370
|
+
export function isOutOfGasError(error) {
|
|
371
|
+
let terminal;
|
|
372
|
+
let fallback;
|
|
373
|
+
for (let current = error, depth = 0; current && depth < 10; depth++) {
|
|
374
|
+
const { cause, data, details, message } = current;
|
|
375
|
+
if (typeof data === 'string' && data !== '0x')
|
|
376
|
+
return false;
|
|
377
|
+
if (typeof details === 'string')
|
|
378
|
+
terminal = details;
|
|
379
|
+
if (typeof message === 'string')
|
|
380
|
+
fallback = message;
|
|
381
|
+
current = cause;
|
|
382
|
+
}
|
|
383
|
+
const nodeMessage = (terminal ?? fallback)?.trim();
|
|
384
|
+
if (!nodeMessage)
|
|
385
|
+
return false;
|
|
386
|
+
return /^(?:out of gas(?::|$)|intrinsic gas too low(?::|$))/i.test(nodeMessage);
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Extra gas a local fee payer signs into its simulation-only envelope. Tempo's sponsored
|
|
390
|
+
* `eth_call` charges more gas than the same transaction uses when mined (about 6,000 for a
|
|
391
|
+
* session top-up on v1.18.1), so simulating at the signed limit would reject transactions
|
|
392
|
+
* that succeed onchain. The padding is clamped to the fee payer policy; hosted signatures
|
|
393
|
+
* cover the client's gas and are simulated as signed. In both cases the gas bound at the
|
|
394
|
+
* broadcast limit comes from the sender simulation.
|
|
395
|
+
*/
|
|
396
|
+
export const sponsoredSimulationGasBuffer = 10000n;
|
|
233
397
|
/**
|
|
234
398
|
* maxTotalFee must be high enough to cover `transferWithMemo` and
|
|
235
399
|
* swap transactions at peak gas prices. Bumped from 0.01 ETH in #327.
|
|
@@ -355,6 +355,7 @@ export async function broadcastOpenTransaction(parameters) {
|
|
|
355
355
|
reason: 'transaction does not contain a valid escrow open call',
|
|
356
356
|
});
|
|
357
357
|
const completed = await FeePayer.preflightSponsorship({
|
|
358
|
+
policy: feePayerPolicy,
|
|
358
359
|
transaction,
|
|
359
360
|
simulate: (request) => call(client, request),
|
|
360
361
|
async complete() {
|
|
@@ -470,6 +471,7 @@ export async function broadcastTopUpTransaction(parameters) {
|
|
|
470
471
|
});
|
|
471
472
|
const defaultFeeToken = defaults.currency[client.chain?.id];
|
|
472
473
|
const completed = await FeePayer.preflightSponsorship({
|
|
474
|
+
policy: feePayerPolicy,
|
|
473
475
|
transaction,
|
|
474
476
|
simulate: (request) => call(client, request),
|
|
475
477
|
async complete() {
|
|
@@ -20,7 +20,6 @@ import * as Proof from '../internal/proof.js';
|
|
|
20
20
|
import * as Selectors from '../internal/selectors.js';
|
|
21
21
|
import * as Methods from '../Methods.js';
|
|
22
22
|
import { fallbackStore } from './internal/fallback-store.js';
|
|
23
|
-
import { html as htmlContent } from './internal/html.gen.js';
|
|
24
23
|
import * as Relay from './Relay.js';
|
|
25
24
|
import * as SponsorBudget from './SponsorBudget.js';
|
|
26
25
|
/**
|
|
@@ -322,7 +321,7 @@ export function charge(parameters = {}) {
|
|
|
322
321
|
html: html
|
|
323
322
|
? {
|
|
324
323
|
config: {},
|
|
325
|
-
content:
|
|
324
|
+
content: async () => (await import('./internal/html.gen.js')).html,
|
|
326
325
|
formatAmount: async (request) => {
|
|
327
326
|
try {
|
|
328
327
|
const chainId = request.methodDetails?.chainId;
|
|
@@ -481,6 +480,7 @@ export function charge(parameters = {}) {
|
|
|
481
480
|
if (feePayerAccount && methodDetails?.feePayer !== false) {
|
|
482
481
|
const selectableFeeTokens = allowedFeeTokens ?? FeePayer.defaultAllowedFeeTokens(chainId);
|
|
483
482
|
const completed = await FeePayer.preflightSponsorship({
|
|
483
|
+
policy: transactionFeePayerPolicy,
|
|
484
484
|
transaction,
|
|
485
485
|
simulate: (request) => viem_call(client, request),
|
|
486
486
|
async complete() {
|
|
@@ -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;
|
|
@@ -713,6 +713,7 @@ async function submitSubscriptionPayment(parameters) {
|
|
|
713
713
|
const userSerialized = await signTransaction(client, prepared);
|
|
714
714
|
const userTransaction = Transaction.deserialize(userSerialized);
|
|
715
715
|
const completed = await FeePayer.preflightSponsorship({
|
|
716
|
+
policy: feePayerPolicy,
|
|
716
717
|
transaction: userTransaction,
|
|
717
718
|
simulate: (request) => viem_call(client, request),
|
|
718
719
|
async complete() {
|