mppx 0.3.15 → 0.4.0

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 (45) hide show
  1. package/README.md +1 -0
  2. package/dist/Challenge.d.ts +38 -0
  3. package/dist/Challenge.d.ts.map +1 -1
  4. package/dist/Challenge.js +62 -0
  5. package/dist/Challenge.js.map +1 -1
  6. package/dist/client/internal/Fetch.d.ts.map +1 -1
  7. package/dist/client/internal/Fetch.js +16 -4
  8. package/dist/client/internal/Fetch.js.map +1 -1
  9. package/dist/middlewares/internal/mppx.d.ts +6 -1
  10. package/dist/middlewares/internal/mppx.d.ts.map +1 -1
  11. package/dist/middlewares/internal/mppx.js +6 -1
  12. package/dist/middlewares/internal/mppx.js.map +1 -1
  13. package/dist/proxy/Service.js +1 -1
  14. package/dist/proxy/Service.js.map +1 -1
  15. package/dist/server/Mppx.d.ts +92 -2
  16. package/dist/server/Mppx.d.ts.map +1 -1
  17. package/dist/server/Mppx.js +155 -10
  18. package/dist/server/Mppx.js.map +1 -1
  19. package/dist/tempo/client/ChannelOps.d.ts.map +1 -1
  20. package/dist/tempo/client/ChannelOps.js +1 -0
  21. package/dist/tempo/client/ChannelOps.js.map +1 -1
  22. package/dist/tempo/server/Charge.d.ts.map +1 -1
  23. package/dist/tempo/server/Charge.js +6 -4
  24. package/dist/tempo/server/Charge.js.map +1 -1
  25. package/dist/tempo/session/Chain.d.ts.map +1 -1
  26. package/dist/tempo/session/Chain.js +13 -6
  27. package/dist/tempo/session/Chain.js.map +1 -1
  28. package/package.json +1 -1
  29. package/src/Challenge.ts +72 -0
  30. package/src/client/internal/Fetch.test.ts +1 -1
  31. package/src/client/internal/Fetch.ts +18 -6
  32. package/src/middlewares/internal/mppx.test.ts +152 -0
  33. package/src/middlewares/internal/mppx.ts +27 -4
  34. package/src/proxy/Service.ts +2 -1
  35. package/src/server/Mppx.test-d.ts +94 -299
  36. package/src/server/Mppx.test.ts +694 -0
  37. package/src/server/Mppx.ts +256 -14
  38. package/src/tempo/client/ChannelOps.ts +1 -0
  39. package/src/tempo/server/Charge.ts +9 -4
  40. package/src/tempo/session/Chain.ts +15 -5
  41. package/dist/tempo/internal/simulate.d.ts +0 -21
  42. package/dist/tempo/internal/simulate.d.ts.map +0 -1
  43. package/dist/tempo/internal/simulate.js +0 -31
  44. package/dist/tempo/internal/simulate.js.map +0 -1
  45. package/src/tempo/internal/simulate.ts +0 -49
@@ -1,10 +1,11 @@
1
1
  import type { IncomingMessage, ServerResponse } from 'node:http'
2
2
  import * as Challenge from '../Challenge.js'
3
- import type * as Credential from '../Credential.js'
3
+ import * as Credential from '../Credential.js'
4
4
  import * as Errors from '../Errors.js'
5
5
  import * as Expires from '../Expires.js'
6
6
  import * as Env from '../internal/env.js'
7
7
  import type * as Method from '../Method.js'
8
+ import * as PaymentRequest from '../PaymentRequest.js'
8
9
  import type * as Receipt from '../Receipt.js'
9
10
  import type * as z from '../zod.js'
10
11
  import * as NodeListener from './NodeListener.js'
@@ -26,7 +27,45 @@ export type Mppx<
26
27
  realm: string
27
28
  /** The transport used. */
28
29
  transport: transport
29
- } & Handlers<FlattenMethods<methods>, transport>
30
+ } & (transport extends Transport.Http
31
+ ? {
32
+ /**
33
+ * Combines multiple method handlers into a single route handler that presents
34
+ * all methods to the client via multiple `WWW-Authenticate` headers.
35
+ *
36
+ * Each entry is a `[method, options]` tuple where `method` is one of the
37
+ * server methods passed to `Mppx.create()`, looked up by `name`+`intent`.
38
+ *
39
+ * Only available on HTTP transports.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * import { Mppx, tempo, stripe } from 'mppx/server'
44
+ *
45
+ * const mppx = Mppx.create({
46
+ * methods: [
47
+ * tempo.charge({ currency: USDC, recipient: '0x...' }),
48
+ * stripe.charge({ currency: 'usd' }),
49
+ * ],
50
+ * secretKey,
51
+ * })
52
+ *
53
+ * app.get('/api/resource', async (req) => {
54
+ * const result = await mppx.compose(
55
+ * mppx.tempo.charge({ amount: '100' }),
56
+ * mppx.stripe.charge({ amount: '100' }),
57
+ * )(req)
58
+ * if (result.status === 402) return result.challenge
59
+ * return result.withReceipt(new Response('OK'))
60
+ * })
61
+ * ```
62
+ */
63
+ compose(
64
+ ...entries: ComposeEntry<FlattenMethods<methods>>[]
65
+ ): (input: Request) => Promise<MethodFn.Response<Transport.Http>>
66
+ }
67
+ : {}) &
68
+ Handlers<FlattenMethods<methods>, transport>
30
69
 
31
70
  /** Extracts the transport override from a method, if any. */
32
71
  type TransportOverrideOf<mi> = mi extends { transport?: infer transport }
@@ -68,6 +107,20 @@ type UniqueIntentHandlers<
68
107
  >
69
108
  }
70
109
 
110
+ /** Nested handlers: `mppx.tempo.charge(...)`, grouped by method name then intent. */
111
+ type NestedHandlers<
112
+ methods extends readonly Method.AnyServer[],
113
+ transport extends Transport.AnyTransport,
114
+ > = {
115
+ [name in methods[number]['name']]: {
116
+ [mi in Extract<methods[number], { name: name }> as mi['intent']]: MethodFn<
117
+ mi,
118
+ EffectiveTransportOf<mi, transport>,
119
+ NonNullable<mi['defaults']>
120
+ > & { _method: mi }
121
+ }
122
+ }
123
+
71
124
  type Handlers<
72
125
  methods extends readonly Method.AnyServer[],
73
126
  transport extends Transport.AnyTransport,
@@ -77,7 +130,8 @@ type Handlers<
77
130
  EffectiveTransportOf<mi, transport>,
78
131
  NonNullable<mi['defaults']>
79
132
  >
80
- } & UniqueIntentHandlers<methods, transport>
133
+ } & UniqueIntentHandlers<methods, transport> &
134
+ NestedHandlers<methods, transport>
81
135
 
82
136
  /**
83
137
  * Creates a server-side payment handler from methods.
@@ -135,7 +189,44 @@ export function create<
135
189
  if (intentCount[mi.intent] === 1) handlers[mi.intent] = handlers[`${mi.name}/${mi.intent}`]
136
190
  }
137
191
 
138
- return { methods, realm: realm as string, transport, ...handlers } as never
192
+ // Build nested handlers: mppx.tempo.charge(...)
193
+ for (const mi of methods) {
194
+ if (!handlers[mi.name]) handlers[mi.name] = {}
195
+ const fn = handlers[`${mi.name}/${mi.intent}`] as AnyMethodFn & { _method?: Method.AnyServer }
196
+ fn._method = mi
197
+ ;(handlers[mi.name] as Record<string, unknown>)[mi.intent] = fn
198
+ }
199
+
200
+ function composeFn(
201
+ ...entries: readonly [
202
+ Method.AnyServer | AnyMethodFnWithMethod | string,
203
+ Record<string, unknown>,
204
+ ][]
205
+ ) {
206
+ if (transport.name !== 'http') throw new Error('compose() only supports HTTP transport')
207
+ if (entries.length === 0) throw new Error('compose() requires at least one entry')
208
+ const configured = entries.map(([methodOrKey, options]) => {
209
+ const key =
210
+ typeof methodOrKey === 'string'
211
+ ? methodOrKey
212
+ : typeof methodOrKey === 'function' && '_method' in methodOrKey
213
+ ? `${(methodOrKey._method as Method.AnyServer).name}/${(methodOrKey._method as Method.AnyServer).intent}`
214
+ : `${(methodOrKey as Method.AnyServer).name}/${(methodOrKey as Method.AnyServer).intent}`
215
+ const handlerFn = handlers[key] as AnyMethodFn | undefined
216
+ if (!handlerFn)
217
+ throw new Error(`No handler for "${key}". Is this method in your methods array?`)
218
+ return handlerFn(options)
219
+ })
220
+ return compose(...(configured as ConfiguredHandler[]))
221
+ }
222
+
223
+ return {
224
+ methods,
225
+ compose: composeFn,
226
+ realm: realm as string,
227
+ transport,
228
+ ...handlers,
229
+ } as never
139
230
  }
140
231
 
141
232
  export declare namespace create {
@@ -166,20 +257,14 @@ function createMethodFn(parameters: createMethodFn.Parameters): createMethodFn.R
166
257
  const { defaults, method, realm, respond, secretKey, transport, verify } = parameters
167
258
 
168
259
  return (options) => {
169
- const methodMeta = {
170
- ...method,
171
- ...defaults,
172
- ...options,
173
- }
260
+ const { description, meta, ...rest } = options
261
+ const merged = { ...defaults, ...rest }
262
+
174
263
  return Object.assign(
175
264
  async (input: Transport.InputOf): Promise<MethodFn.Response> => {
176
- const { description, meta, ...rest } = options
177
265
  const expires =
178
266
  'expires' in options ? (options.expires as string | undefined) : Expires.minutes(5)
179
267
 
180
- // Merge defaults with per-request options
181
- const merged = { ...defaults, ...rest }
182
-
183
268
  // Extract credential once — getCredential may have side effects (e.g. SSE transports).
184
269
  const [credential, credentialError] = (() => {
185
270
  try {
@@ -248,6 +333,9 @@ function createMethodFn(parameters: createMethodFn.Parameters): createMethodFn.R
248
333
  // Verify the credential's challenge matches this route's configured
249
334
  // request. Prevents cross-route scope confusion where a credential
250
335
  // issued for a cheap route is presented at an expensive route.
336
+ // Note: we compare specific payment parameters rather than the full
337
+ // request because the `request` hook may produce credential-dependent
338
+ // output (e.g. `feePayer` differs between 402 and credential calls).
251
339
  {
252
340
  const routeReq = challenge.request as Record<string, unknown>
253
341
  const echoedReq = credential.challenge.request as Record<string, unknown>
@@ -339,7 +427,16 @@ function createMethodFn(parameters: createMethodFn.Parameters): createMethodFn.R
339
427
  },
340
428
  }
341
429
  },
342
- { _internal: methodMeta },
430
+ {
431
+ _internal: {
432
+ ...method,
433
+ ...defaults,
434
+ ...options,
435
+ name: method.name,
436
+ intent: method.intent,
437
+ _canonicalRequest: PaymentRequest.fromMethod(method, merged),
438
+ },
439
+ },
343
440
  )
344
441
  }
345
442
  }
@@ -376,6 +473,8 @@ export type MethodFn<
376
473
  ) => (input: Transport.InputOf<transport>) => Promise<MethodFn.Response<transport>>
377
474
  /** @internal */
378
475
  export type AnyMethodFn = (options: any) => (input: any) => Promise<any>
476
+ /** A MethodFn tagged with its source Method (set by `create()`). @internal */
477
+ type AnyMethodFnWithMethod = AnyMethodFn & { _method: Method.AnyServer }
379
478
 
380
479
  /** @internal */
381
480
  declare namespace MethodFn {
@@ -402,6 +501,149 @@ declare namespace MethodFn {
402
501
  }
403
502
  }
404
503
 
504
+ /** A configured handler — the return value of e.g. `mppx.charge({ ... })`. @internal */
505
+ type ConfiguredHandler = ((input: Request) => Promise<MethodFn.Response<Transport.Http>>) & {
506
+ _internal: {
507
+ name: string
508
+ intent: string
509
+ _canonicalRequest: Record<string, unknown>
510
+ }
511
+ }
512
+
513
+ /** An entry for `compose()`: a method reference, handler function ref, or string key paired with its options. */
514
+ type ComposeEntry<methods extends readonly Method.AnyServer[]> =
515
+ | {
516
+ [i in keyof methods]: readonly [
517
+ methods[i],
518
+ MethodFn.Options<methods[i], NonNullable<methods[i]['defaults']>>,
519
+ ]
520
+ }[number]
521
+ | {
522
+ [i in keyof methods]: readonly [
523
+ `${methods[i]['name']}/${methods[i]['intent']}`,
524
+ MethodFn.Options<methods[i], NonNullable<methods[i]['defaults']>>,
525
+ ]
526
+ }[number]
527
+ | {
528
+ [i in keyof methods]: readonly [
529
+ MethodFn<methods[i], any, any> & { _method: methods[i] },
530
+ MethodFn.Options<methods[i], NonNullable<methods[i]['defaults']>>,
531
+ ]
532
+ }[number]
533
+
534
+ /**
535
+ * Combines multiple configured payment handlers into a single route handler
536
+ * that presents all methods to the client via multiple `WWW-Authenticate` headers.
537
+ *
538
+ * When no credential is present, all handlers are called and their challenges
539
+ * are merged into a single 402 response. When a credential is present, it is
540
+ * dispatched to the handler matching the credential's `method`+`intent`.
541
+ *
542
+ * @example
543
+ * ```ts
544
+ * import { Mppx, tempo, stripe } from 'mppx/server'
545
+ *
546
+ * const mppx = Mppx.create({
547
+ * methods: [tempo(), stripe()],
548
+ * secretKey: process.env.PAYMENT_SECRET_KEY,
549
+ * })
550
+ *
551
+ * app.get('/api/resource', async (req) => {
552
+ * const result = await Mppx.compose(
553
+ * mppx['tempo/charge']({ amount: '100', currency: USDC, recipient: '0x...' }),
554
+ * mppx['stripe/charge']({ amount: '100', currency: 'usd' }),
555
+ * )(req)
556
+ * if (result.status === 402) return result.challenge
557
+ * return result.withReceipt(new Response('OK'))
558
+ * })
559
+ * ```
560
+ */
561
+ export function compose(
562
+ ...handlers: readonly ((input: Request) => Promise<MethodFn.Response<Transport.Http>>)[]
563
+ ): (input: Request) => Promise<MethodFn.Response<Transport.Http>> {
564
+ if (handlers.length === 0) throw new Error('compose() requires at least one handler')
565
+
566
+ return async (input: Request) => {
567
+ // Try to extract a Payment credential to decide whether to dispatch or challenge.
568
+ // Only gate on the Payment scheme — other auth schemes (Bearer, Basic, etc.)
569
+ // should fall through to the merged-402 path so all offers are presented.
570
+ const header = input.headers.get('Authorization')
571
+ const paymentHeader = header ? Credential.extractPaymentScheme(header) : null
572
+
573
+ if (paymentHeader) {
574
+ // Parse the credential to find method+intent for dispatch.
575
+ let credential: Credential.Credential | undefined
576
+ try {
577
+ credential = Credential.deserialize(paymentHeader)
578
+ } catch {}
579
+
580
+ if (credential) {
581
+ const { method: credMethod, intent: credIntent } = credential.challenge
582
+ const credReq = credential.challenge.request as Record<string, unknown>
583
+
584
+ // Filter by name+intent, then narrow by comparing stable request fields
585
+ // from the echoed challenge against each handler's canonical request.
586
+ // Uses the schema-parsed canonical form (not raw options) so that
587
+ // transformed fields (e.g. amount with decimals) match correctly.
588
+ const candidates = handlers.filter((h) => {
589
+ const meta = (h as ConfiguredHandler)._internal
590
+ if (!meta || meta.name !== credMethod || meta.intent !== credIntent) return false
591
+ const canonical = meta._canonicalRequest
592
+ if (!canonical) return true
593
+ for (const field of ['amount', 'currency', 'recipient', 'chainId'] as const) {
594
+ const canonicalVal = canonical[field]
595
+ if (
596
+ canonicalVal !== undefined &&
597
+ credReq[field] !== undefined &&
598
+ String(canonicalVal) !== String(credReq[field])
599
+ )
600
+ return false
601
+ }
602
+ return true
603
+ })
604
+
605
+ const match =
606
+ candidates[0] ??
607
+ handlers.find((h) => {
608
+ const meta = (h as ConfiguredHandler)._internal
609
+ return meta?.name === credMethod && meta?.intent === credIntent
610
+ })
611
+ if (match) return match(input)
612
+ }
613
+
614
+ // Payment credential present but no matching handler — dispatch to first
615
+ // handler which will reject with an appropriate error (invalid challenge, etc.).
616
+ return handlers[0]!(input)
617
+ }
618
+
619
+ // No credential — call all handlers and merge 402 challenges.
620
+ const results = await Promise.all(handlers.map((h) => h(input)))
621
+
622
+ // Merge WWW-Authenticate headers from all 402 responses.
623
+ const mergedHeaders = new Headers()
624
+ mergedHeaders.set('Cache-Control', 'no-store')
625
+
626
+ let body: string | null = null
627
+ for (const result of results) {
628
+ if (result.status !== 402) continue
629
+ const response = result.challenge as Response
630
+ const wwwAuth = response.headers.get('WWW-Authenticate')
631
+ if (wwwAuth) mergedHeaders.append('WWW-Authenticate', wwwAuth)
632
+ // Use the first handler's body for the problem details response.
633
+ if (!body) {
634
+ const contentType = response.headers.get('Content-Type')
635
+ if (contentType) mergedHeaders.set('Content-Type', contentType)
636
+ body = await response.text()
637
+ }
638
+ }
639
+
640
+ return {
641
+ status: 402,
642
+ challenge: new Response(body, { status: 402, headers: mergedHeaders }),
643
+ }
644
+ }
645
+ }
646
+
405
647
  /**
406
648
  * Wraps a payment handler to create a Node.js HTTP listener.
407
649
  *
@@ -164,6 +164,7 @@ export async function createOpenPayload(
164
164
  { to: escrowContract, data: openData },
165
165
  ],
166
166
  ...(feePayer && { feePayer: true }),
167
+ feeToken: currency,
167
168
  } as never)
168
169
  prepared.gas = prepared.gas! + 5_000n
169
170
  const transaction = (await signTransaction(client, prepared as never)) as Hex.Hex
@@ -4,6 +4,7 @@ import {
4
4
  sendRawTransaction,
5
5
  sendRawTransactionSync,
6
6
  signTransaction,
7
+ call as viem_call,
7
8
  } from 'viem/actions'
8
9
  import { tempo as tempo_chain } from 'viem/chains'
9
10
  import { Abis, Transaction } from 'viem/tempo'
@@ -15,7 +16,6 @@ import * as Account from '../internal/account.js'
15
16
  import * as defaults from '../internal/defaults.js'
16
17
  import * as FeePayer from '../internal/fee-payer.js'
17
18
  import * as Selectors from '../internal/selectors.js'
18
- import { simulateTransaction } from '../internal/simulate.js'
19
19
  import type * as types from '../internal/types.js'
20
20
  import * as Methods from '../Methods.js'
21
21
 
@@ -238,12 +238,16 @@ export function charge<const parameters extends charge.Parameters>(
238
238
  if (feePayer && methodDetails?.feePayer !== false)
239
239
  FeePayer.validateCalls(calls, { amount, currency, recipient })
240
240
 
241
+ const resolvedFeeToken =
242
+ transaction.feeToken ?? defaults.currency[chainId as keyof typeof defaults.currency]
243
+
241
244
  const serializedTransaction_final = await (async () => {
242
245
  if (feePayer && methodDetails?.feePayer !== false) {
243
246
  return signTransaction(client, {
244
247
  ...transaction,
245
248
  account: feePayer,
246
249
  feePayer,
250
+ feeToken: resolvedFeeToken,
247
251
  } as never)
248
252
  }
249
253
  return serializedTransaction
@@ -258,11 +262,12 @@ export function charge<const parameters extends charge.Parameters>(
258
262
  // Optimistic path: simulate to catch obvious reverts, then broadcast
259
263
  // without waiting for on-chain confirmation. The returned receipt
260
264
  // assumes success — callers opt into this risk via waitForConfirmation: false.
261
- await simulateTransaction(client, {
265
+ await viem_call(client, {
262
266
  ...transaction,
263
- from: transaction.from as `0x${string}`,
267
+ account: transaction.from,
268
+ feeToken: resolvedFeeToken,
264
269
  calls,
265
- })
270
+ } as never)
266
271
  const hash = await sendRawTransaction(client, {
267
272
  serializedTransaction: serializedTransaction_final,
268
273
  })
@@ -5,13 +5,13 @@ import {
5
5
  decodeFunctionData,
6
6
  encodeFunctionData,
7
7
  getAbiItem,
8
- getAddress,
9
8
  type Hex,
10
9
  isAddressEqual,
11
10
  type ReadContractReturnType,
12
11
  toFunctionSelector,
13
12
  } from 'viem'
14
13
  import {
14
+ call,
15
15
  prepareTransactionRequest,
16
16
  readContract,
17
17
  sendRawTransaction,
@@ -22,7 +22,6 @@ import {
22
22
  import { Transaction } from 'viem/tempo'
23
23
  import { BadRequestError, ChannelClosedError, VerificationFailedError } from '../../Errors.js'
24
24
  import * as defaults from '../internal/defaults.js'
25
- import { simulateTransaction } from '../internal/simulate.js'
26
25
  import { escrowAbi } from './escrow.abi.js'
27
26
  import type { SignedVoucher } from './Types.js'
28
27
 
@@ -277,20 +276,28 @@ export async function broadcastOpenTransaction(parameters: {
277
276
  })
278
277
  }
279
278
 
279
+ const resolvedFeeToken =
280
+ transaction.feeToken ?? defaults.currency[client.chain?.id as keyof typeof defaults.currency]
281
+
280
282
  const serializedTransaction_final = await (async () => {
281
283
  if (feePayer) {
282
284
  return signTransaction(client, {
283
285
  ...transaction,
284
286
  account: feePayer,
285
287
  feePayer,
288
+ feeToken: resolvedFeeToken,
286
289
  } as never)
287
290
  }
288
291
  return serializedTransaction
289
292
  })()
290
293
 
291
294
  if (!waitForConfirmation) {
292
- const from = getAddress(transaction.from as Address)
293
- await simulateTransaction(client, { ...transaction, from, calls })
295
+ await call(client, {
296
+ ...transaction,
297
+ account: transaction.from,
298
+ feeToken: resolvedFeeToken,
299
+ calls,
300
+ } as never)
294
301
  const txHash = await sendRawTransaction(client, {
295
302
  serializedTransaction: serializedTransaction_final as Transaction.TransactionSerializedTempo,
296
303
  })
@@ -298,7 +305,7 @@ export async function broadcastOpenTransaction(parameters: {
298
305
  return {
299
306
  txHash,
300
307
  onChain: {
301
- payer: from,
308
+ payer: transaction.from,
302
309
  payee,
303
310
  token,
304
311
  authorizedSigner,
@@ -412,6 +419,9 @@ export async function broadcastTopUpTransaction(parameters: {
412
419
  ...transaction,
413
420
  account: feePayer,
414
421
  feePayer,
422
+ feeToken:
423
+ transaction.feeToken ??
424
+ defaults.currency[client.chain?.id as keyof typeof defaults.currency],
415
425
  } as never)
416
426
  }
417
427
  return serializedTransaction
@@ -1,21 +0,0 @@
1
- import type { Address, Client } from 'viem';
2
- /**
3
- * Simulate a Tempo transaction via `eth_estimateGas` to catch reverts
4
- * (e.g. insufficient balance, invalid calls) before broadcasting.
5
- */
6
- export declare function simulateTransaction(client: Client, transaction: {
7
- from: Address;
8
- chainId: number;
9
- nonce?: number | bigint | undefined;
10
- maxFeePerGas?: bigint | undefined;
11
- maxPriorityFeePerGas?: bigint | undefined;
12
- feeToken?: string | bigint | undefined;
13
- nonceKey?: bigint | undefined;
14
- validBefore?: number | undefined;
15
- calls?: readonly {
16
- to?: string | undefined;
17
- value?: bigint | undefined;
18
- data?: string | undefined;
19
- }[];
20
- }): Promise<void>;
21
- //# sourceMappingURL=simulate.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"simulate.d.ts","sourceRoot":"","sources":["../../../src/tempo/internal/simulate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAE3C;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE;IACX,IAAI,EAAE,OAAO,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;IACnC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,oBAAoB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACzC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;IACtC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAChC,KAAK,CAAC,EAAE,SAAS;QACf,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;QACvB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;QAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAC1B,EAAE,CAAA;CACJ,GACA,OAAO,CAAC,IAAI,CAAC,CAyBf"}
@@ -1,31 +0,0 @@
1
- /**
2
- * Simulate a Tempo transaction via `eth_estimateGas` to catch reverts
3
- * (e.g. insufficient balance, invalid calls) before broadcasting.
4
- */
5
- export async function simulateTransaction(client, transaction) {
6
- const simCalls = (transaction.calls ?? []).map((c) => ({
7
- to: c.to,
8
- value: c.value ? `0x${c.value.toString(16)}` : '0x0',
9
- input: c.data ?? '0x',
10
- }));
11
- await client.request({
12
- method: 'eth_estimateGas',
13
- params: [
14
- {
15
- from: transaction.from,
16
- chainId: `0x${transaction.chainId.toString(16)}`,
17
- nonce: `0x${BigInt(transaction.nonce ?? 0).toString(16)}`,
18
- gas: '0x2dc6c0', // 3M cap
19
- maxFeePerGas: `0x${(transaction.maxFeePerGas ?? 0n).toString(16)}`,
20
- maxPriorityFeePerGas: `0x${(transaction.maxPriorityFeePerGas ?? 0n).toString(16)}`,
21
- feeToken: transaction.feeToken,
22
- nonceKey: `0x${(transaction.nonceKey ?? 0n).toString(16)}`,
23
- calls: simCalls,
24
- ...(transaction.validBefore
25
- ? { validBefore: `0x${transaction.validBefore.toString(16)}` }
26
- : {}),
27
- },
28
- ],
29
- });
30
- }
31
- //# sourceMappingURL=simulate.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"simulate.js","sourceRoot":"","sources":["../../../src/tempo/internal/simulate.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,MAAc,EACd,WAcC;IAED,MAAM,QAAQ,GAAG,CAAC,WAAW,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACrD,EAAE,EAAE,CAAC,CAAC,EAAE;QACR,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK;QACpD,KAAK,EAAE,CAAC,CAAC,IAAI,IAAI,IAAI;KACtB,CAAC,CAAC,CAAA;IACH,MAAM,MAAM,CAAC,OAAO,CAAC;QACnB,MAAM,EAAE,iBAA0B;QAClC,MAAM,EAAE;YACN;gBACE,IAAI,EAAE,WAAW,CAAC,IAAI;gBACtB,OAAO,EAAE,KAAK,WAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE;gBAChD,KAAK,EAAE,KAAK,MAAM,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE;gBACzD,GAAG,EAAE,UAAU,EAAE,SAAS;gBAC1B,YAAY,EAAE,KAAK,CAAC,WAAW,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE;gBAClE,oBAAoB,EAAE,KAAK,CAAC,WAAW,CAAC,oBAAoB,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE;gBAClF,QAAQ,EAAE,WAAW,CAAC,QAAQ;gBAC9B,QAAQ,EAAE,KAAK,CAAC,WAAW,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE;gBAC1D,KAAK,EAAE,QAAQ;gBACf,GAAG,CAAC,WAAW,CAAC,WAAW;oBACzB,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,WAAW,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE;oBAC9D,CAAC,CAAC,EAAE,CAAC;aACR;SACO;KACX,CAAC,CAAA;AACJ,CAAC"}
@@ -1,49 +0,0 @@
1
- import type { Address, Client } from 'viem'
2
-
3
- /**
4
- * Simulate a Tempo transaction via `eth_estimateGas` to catch reverts
5
- * (e.g. insufficient balance, invalid calls) before broadcasting.
6
- */
7
- export async function simulateTransaction(
8
- client: Client,
9
- transaction: {
10
- from: Address
11
- chainId: number
12
- nonce?: number | bigint | undefined
13
- maxFeePerGas?: bigint | undefined
14
- maxPriorityFeePerGas?: bigint | undefined
15
- feeToken?: string | bigint | undefined
16
- nonceKey?: bigint | undefined
17
- validBefore?: number | undefined
18
- calls?: readonly {
19
- to?: string | undefined
20
- value?: bigint | undefined
21
- data?: string | undefined
22
- }[]
23
- },
24
- ): Promise<void> {
25
- const simCalls = (transaction.calls ?? []).map((c) => ({
26
- to: c.to,
27
- value: c.value ? `0x${c.value.toString(16)}` : '0x0',
28
- input: c.data ?? '0x',
29
- }))
30
- await client.request({
31
- method: 'eth_estimateGas' as never,
32
- params: [
33
- {
34
- from: transaction.from,
35
- chainId: `0x${transaction.chainId.toString(16)}`,
36
- nonce: `0x${BigInt(transaction.nonce ?? 0).toString(16)}`,
37
- gas: '0x2dc6c0', // 3M cap
38
- maxFeePerGas: `0x${(transaction.maxFeePerGas ?? 0n).toString(16)}`,
39
- maxPriorityFeePerGas: `0x${(transaction.maxPriorityFeePerGas ?? 0n).toString(16)}`,
40
- feeToken: transaction.feeToken,
41
- nonceKey: `0x${(transaction.nonceKey ?? 0n).toString(16)}`,
42
- calls: simCalls,
43
- ...(transaction.validBefore
44
- ? { validBefore: `0x${transaction.validBefore.toString(16)}` }
45
- : {}),
46
- },
47
- ] as never,
48
- })
49
- }