mppx 0.3.14 → 0.3.16
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/README.md +1 -0
- package/dist/Challenge.d.ts +38 -0
- package/dist/Challenge.d.ts.map +1 -1
- package/dist/Challenge.js +62 -0
- package/dist/Challenge.js.map +1 -1
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +4 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli.d.ts +26 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1478 -915
- package/dist/cli.js.map +1 -1
- package/dist/client/Mppx.d.ts +2 -0
- package/dist/client/Mppx.d.ts.map +1 -1
- package/dist/client/Mppx.js +2 -0
- package/dist/client/Mppx.js.map +1 -1
- package/dist/client/internal/Fetch.d.ts.map +1 -1
- package/dist/client/internal/Fetch.js +16 -4
- package/dist/client/internal/Fetch.js.map +1 -1
- package/dist/middlewares/internal/mppx.d.ts +6 -1
- package/dist/middlewares/internal/mppx.d.ts.map +1 -1
- package/dist/middlewares/internal/mppx.js +4 -0
- package/dist/middlewares/internal/mppx.js.map +1 -1
- package/dist/server/Mppx.d.ts +79 -1
- package/dist/server/Mppx.d.ts.map +1 -1
- package/dist/server/Mppx.js +135 -7
- package/dist/server/Mppx.js.map +1 -1
- package/dist/tempo/client/ChannelOps.d.ts.map +1 -1
- package/dist/tempo/client/ChannelOps.js +1 -0
- package/dist/tempo/client/ChannelOps.js.map +1 -1
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js +4 -4
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/session/Chain.d.ts.map +1 -1
- package/dist/tempo/session/Chain.js +9 -6
- package/dist/tempo/session/Chain.js.map +1 -1
- package/package.json +4 -4
- package/src/Challenge.ts +72 -0
- package/src/bin.ts +4 -0
- package/src/cli.test.ts +180 -252
- package/src/cli.ts +1085 -485
- package/src/client/Mppx.test-d.ts +9 -0
- package/src/client/Mppx.test.ts +78 -0
- package/src/client/Mppx.ts +5 -0
- package/src/client/internal/Fetch.test.ts +1 -1
- package/src/client/internal/Fetch.ts +18 -6
- package/src/middlewares/internal/mppx.test.ts +152 -0
- package/src/middlewares/internal/mppx.ts +22 -3
- package/src/server/Mppx.test-d.ts +94 -299
- package/src/server/Mppx.test.ts +650 -0
- package/src/server/Mppx.ts +213 -9
- package/src/tempo/client/ChannelOps.ts +1 -0
- package/src/tempo/server/Charge.ts +4 -3
- package/src/tempo/session/Chain.ts +8 -5
- package/dist/tempo/internal/simulate.d.ts +0 -21
- package/dist/tempo/internal/simulate.d.ts.map +0 -1
- package/dist/tempo/internal/simulate.js +0 -31
- package/dist/tempo/internal/simulate.js.map +0 -1
- package/src/tempo/internal/simulate.ts +0 -49
package/src/server/Mppx.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
2
2
|
import * as Challenge from '../Challenge.js'
|
|
3
|
-
import
|
|
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'
|
|
@@ -22,6 +22,38 @@ export type Mppx<
|
|
|
22
22
|
> = {
|
|
23
23
|
/** Methods to configure. */
|
|
24
24
|
methods: FlattenMethods<methods>
|
|
25
|
+
/**
|
|
26
|
+
* Combines multiple method handlers into a single route handler that presents
|
|
27
|
+
* all methods to the client via multiple `WWW-Authenticate` headers.
|
|
28
|
+
*
|
|
29
|
+
* Each entry is a `[method, options]` tuple where `method` is one of the
|
|
30
|
+
* server methods passed to `Mppx.create()`, looked up by `name`+`intent`.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* import { Mppx, tempo, stripe } from 'mppx/server'
|
|
35
|
+
*
|
|
36
|
+
* const mppx = Mppx.create({
|
|
37
|
+
* methods: [
|
|
38
|
+
* tempo.charge({ currency: USDC, recipient: '0x...' }),
|
|
39
|
+
* stripe.charge({ currency: 'usd' }),
|
|
40
|
+
* ],
|
|
41
|
+
* secretKey,
|
|
42
|
+
* })
|
|
43
|
+
*
|
|
44
|
+
* app.get('/api/resource', async (req) => {
|
|
45
|
+
* const result = await mppx.compose(
|
|
46
|
+
* mppx.tempo.charge({ amount: '100' }),
|
|
47
|
+
* mppx.stripe.charge({ amount: '100' }),
|
|
48
|
+
* )(req)
|
|
49
|
+
* if (result.status === 402) return result.challenge
|
|
50
|
+
* return result.withReceipt(new Response('OK'))
|
|
51
|
+
* })
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
compose(
|
|
55
|
+
...entries: ComposeEntry<FlattenMethods<methods>>[]
|
|
56
|
+
): (input: Request) => Promise<MethodFn.Response<Transport.Http>>
|
|
25
57
|
/** Server realm (e.g., hostname). */
|
|
26
58
|
realm: string
|
|
27
59
|
/** The transport used. */
|
|
@@ -68,6 +100,20 @@ type UniqueIntentHandlers<
|
|
|
68
100
|
>
|
|
69
101
|
}
|
|
70
102
|
|
|
103
|
+
/** Nested handlers: `mppx.tempo.charge(...)`, grouped by method name then intent. */
|
|
104
|
+
type NestedHandlers<
|
|
105
|
+
methods extends readonly Method.AnyServer[],
|
|
106
|
+
transport extends Transport.AnyTransport,
|
|
107
|
+
> = {
|
|
108
|
+
[name in methods[number]['name']]: {
|
|
109
|
+
[mi in Extract<methods[number], { name: name }> as mi['intent']]: MethodFn<
|
|
110
|
+
mi,
|
|
111
|
+
EffectiveTransportOf<mi, transport>,
|
|
112
|
+
NonNullable<mi['defaults']>
|
|
113
|
+
>
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
71
117
|
type Handlers<
|
|
72
118
|
methods extends readonly Method.AnyServer[],
|
|
73
119
|
transport extends Transport.AnyTransport,
|
|
@@ -77,7 +123,8 @@ type Handlers<
|
|
|
77
123
|
EffectiveTransportOf<mi, transport>,
|
|
78
124
|
NonNullable<mi['defaults']>
|
|
79
125
|
>
|
|
80
|
-
} & UniqueIntentHandlers<methods, transport>
|
|
126
|
+
} & UniqueIntentHandlers<methods, transport> &
|
|
127
|
+
NestedHandlers<methods, transport>
|
|
81
128
|
|
|
82
129
|
/**
|
|
83
130
|
* Creates a server-side payment handler from methods.
|
|
@@ -135,7 +182,32 @@ export function create<
|
|
|
135
182
|
if (intentCount[mi.intent] === 1) handlers[mi.intent] = handlers[`${mi.name}/${mi.intent}`]
|
|
136
183
|
}
|
|
137
184
|
|
|
138
|
-
|
|
185
|
+
// Build nested handlers: mppx.tempo.charge(...)
|
|
186
|
+
for (const mi of methods) {
|
|
187
|
+
if (!handlers[mi.name]) handlers[mi.name] = {}
|
|
188
|
+
;(handlers[mi.name] as Record<string, unknown>)[mi.intent] = handlers[`${mi.name}/${mi.intent}`]
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function composeFn(...entries: readonly [Method.AnyServer | string, Record<string, unknown>][]) {
|
|
192
|
+
if (entries.length === 0) throw new Error('compose() requires at least one entry')
|
|
193
|
+
const configured = entries.map(([methodOrKey, options]) => {
|
|
194
|
+
const key =
|
|
195
|
+
typeof methodOrKey === 'string' ? methodOrKey : `${methodOrKey.name}/${methodOrKey.intent}`
|
|
196
|
+
const handlerFn = handlers[key] as AnyMethodFn | undefined
|
|
197
|
+
if (!handlerFn)
|
|
198
|
+
throw new Error(`No handler for "${key}". Is this method in your methods array?`)
|
|
199
|
+
return handlerFn(options)
|
|
200
|
+
})
|
|
201
|
+
return compose(...(configured as ConfiguredHandler[]))
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
methods,
|
|
206
|
+
compose: composeFn,
|
|
207
|
+
realm: realm as string,
|
|
208
|
+
transport,
|
|
209
|
+
...handlers,
|
|
210
|
+
} as never
|
|
139
211
|
}
|
|
140
212
|
|
|
141
213
|
export declare namespace create {
|
|
@@ -166,11 +238,6 @@ function createMethodFn(parameters: createMethodFn.Parameters): createMethodFn.R
|
|
|
166
238
|
const { defaults, method, realm, respond, secretKey, transport, verify } = parameters
|
|
167
239
|
|
|
168
240
|
return (options) => {
|
|
169
|
-
const methodMeta = {
|
|
170
|
-
...method,
|
|
171
|
-
...defaults,
|
|
172
|
-
...options,
|
|
173
|
-
}
|
|
174
241
|
return Object.assign(
|
|
175
242
|
async (input: Transport.InputOf): Promise<MethodFn.Response> => {
|
|
176
243
|
const { description, meta, ...rest } = options
|
|
@@ -248,6 +315,9 @@ function createMethodFn(parameters: createMethodFn.Parameters): createMethodFn.R
|
|
|
248
315
|
// Verify the credential's challenge matches this route's configured
|
|
249
316
|
// request. Prevents cross-route scope confusion where a credential
|
|
250
317
|
// issued for a cheap route is presented at an expensive route.
|
|
318
|
+
// Note: we compare specific payment parameters rather than the full
|
|
319
|
+
// request because the `request` hook may produce credential-dependent
|
|
320
|
+
// output (e.g. `feePayer` differs between 402 and credential calls).
|
|
251
321
|
{
|
|
252
322
|
const routeReq = challenge.request as Record<string, unknown>
|
|
253
323
|
const echoedReq = credential.challenge.request as Record<string, unknown>
|
|
@@ -339,7 +409,9 @@ function createMethodFn(parameters: createMethodFn.Parameters): createMethodFn.R
|
|
|
339
409
|
},
|
|
340
410
|
}
|
|
341
411
|
},
|
|
342
|
-
{
|
|
412
|
+
{
|
|
413
|
+
_internal: { ...method, ...defaults, ...options, name: method.name, intent: method.intent },
|
|
414
|
+
},
|
|
343
415
|
)
|
|
344
416
|
}
|
|
345
417
|
}
|
|
@@ -402,6 +474,138 @@ declare namespace MethodFn {
|
|
|
402
474
|
}
|
|
403
475
|
}
|
|
404
476
|
|
|
477
|
+
/** A configured handler — the return value of e.g. `mppx.charge({ ... })`. @internal */
|
|
478
|
+
type ConfiguredHandler = ((input: Request) => Promise<MethodFn.Response<Transport.Http>>) & {
|
|
479
|
+
_internal: { name: string; intent: string }
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/** An entry for `compose()`: a method reference (or string key) paired with its options. */
|
|
483
|
+
type ComposeEntry<methods extends readonly Method.AnyServer[]> =
|
|
484
|
+
| {
|
|
485
|
+
[i in keyof methods]: readonly [
|
|
486
|
+
methods[i],
|
|
487
|
+
MethodFn.Options<methods[i], NonNullable<methods[i]['defaults']>>,
|
|
488
|
+
]
|
|
489
|
+
}[number]
|
|
490
|
+
| {
|
|
491
|
+
[i in keyof methods]: readonly [
|
|
492
|
+
`${methods[i]['name']}/${methods[i]['intent']}`,
|
|
493
|
+
MethodFn.Options<methods[i], NonNullable<methods[i]['defaults']>>,
|
|
494
|
+
]
|
|
495
|
+
}[number]
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Combines multiple configured payment handlers into a single route handler
|
|
499
|
+
* that presents all methods to the client via multiple `WWW-Authenticate` headers.
|
|
500
|
+
*
|
|
501
|
+
* When no credential is present, all handlers are called and their challenges
|
|
502
|
+
* are merged into a single 402 response. When a credential is present, it is
|
|
503
|
+
* dispatched to the handler matching the credential's `method`+`intent`.
|
|
504
|
+
*
|
|
505
|
+
* @example
|
|
506
|
+
* ```ts
|
|
507
|
+
* import { Mppx, tempo, stripe } from 'mppx/server'
|
|
508
|
+
*
|
|
509
|
+
* const mppx = Mppx.create({
|
|
510
|
+
* methods: [tempo(), stripe()],
|
|
511
|
+
* secretKey: process.env.PAYMENT_SECRET_KEY,
|
|
512
|
+
* })
|
|
513
|
+
*
|
|
514
|
+
* app.get('/api/resource', async (req) => {
|
|
515
|
+
* const result = await Mppx.compose(
|
|
516
|
+
* mppx['tempo/charge']({ amount: '100', currency: USDC, recipient: '0x...' }),
|
|
517
|
+
* mppx['stripe/charge']({ amount: '100', currency: 'usd' }),
|
|
518
|
+
* )(req)
|
|
519
|
+
* if (result.status === 402) return result.challenge
|
|
520
|
+
* return result.withReceipt(new Response('OK'))
|
|
521
|
+
* })
|
|
522
|
+
* ```
|
|
523
|
+
*/
|
|
524
|
+
export function compose(
|
|
525
|
+
...handlers: readonly ((input: Request) => Promise<MethodFn.Response<Transport.Http>>)[]
|
|
526
|
+
): (input: Request) => Promise<MethodFn.Response<Transport.Http>> {
|
|
527
|
+
if (handlers.length === 0) throw new Error('compose() requires at least one handler')
|
|
528
|
+
|
|
529
|
+
return async (input: Request) => {
|
|
530
|
+
// Try to extract a Payment credential to decide whether to dispatch or challenge.
|
|
531
|
+
// Only gate on the Payment scheme — other auth schemes (Bearer, Basic, etc.)
|
|
532
|
+
// should fall through to the merged-402 path so all offers are presented.
|
|
533
|
+
const header = input.headers.get('Authorization')
|
|
534
|
+
const paymentHeader = header ? Credential.extractPaymentScheme(header) : null
|
|
535
|
+
|
|
536
|
+
if (paymentHeader) {
|
|
537
|
+
// Parse the credential to find method+intent for dispatch.
|
|
538
|
+
let credential: Credential.Credential | undefined
|
|
539
|
+
try {
|
|
540
|
+
credential = Credential.deserialize(paymentHeader)
|
|
541
|
+
} catch {}
|
|
542
|
+
|
|
543
|
+
if (credential) {
|
|
544
|
+
const { method: credMethod, intent: credIntent } = credential.challenge
|
|
545
|
+
const credReq = credential.challenge.request as Record<string, unknown>
|
|
546
|
+
|
|
547
|
+
// Filter by name+intent, then narrow by comparing stable request fields
|
|
548
|
+
// from the echoed challenge against each handler's configured options.
|
|
549
|
+
// This disambiguates handlers with the same name/intent but different
|
|
550
|
+
// amounts, currencies, recipients, etc. without invoking any handler.
|
|
551
|
+
const candidates = handlers.filter((h) => {
|
|
552
|
+
const meta = (h as ConfiguredHandler)._internal
|
|
553
|
+
if (!meta || meta.name !== credMethod || meta.intent !== credIntent) return false
|
|
554
|
+
// Compare stable fields that don't change between 402 and credential calls.
|
|
555
|
+
for (const field of ['amount', 'currency', 'recipient'] as const) {
|
|
556
|
+
const metaVal = (meta as Record<string, unknown>)[field]
|
|
557
|
+
if (
|
|
558
|
+
metaVal !== undefined &&
|
|
559
|
+
credReq[field] !== undefined &&
|
|
560
|
+
String(metaVal) !== String(credReq[field])
|
|
561
|
+
)
|
|
562
|
+
return false
|
|
563
|
+
}
|
|
564
|
+
return true
|
|
565
|
+
})
|
|
566
|
+
|
|
567
|
+
const match =
|
|
568
|
+
candidates[0] ??
|
|
569
|
+
handlers.find((h) => {
|
|
570
|
+
const meta = (h as ConfiguredHandler)._internal
|
|
571
|
+
return meta?.name === credMethod && meta?.intent === credIntent
|
|
572
|
+
})
|
|
573
|
+
if (match) return match(input)
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// Payment credential present but no matching handler — dispatch to first
|
|
577
|
+
// handler which will reject with an appropriate error (invalid challenge, etc.).
|
|
578
|
+
return handlers[0]!(input)
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// No credential — call all handlers and merge 402 challenges.
|
|
582
|
+
const results = await Promise.all(handlers.map((h) => h(input)))
|
|
583
|
+
|
|
584
|
+
// Merge WWW-Authenticate headers from all 402 responses.
|
|
585
|
+
const mergedHeaders = new Headers()
|
|
586
|
+
mergedHeaders.set('Cache-Control', 'no-store')
|
|
587
|
+
|
|
588
|
+
let body: string | null = null
|
|
589
|
+
for (const result of results) {
|
|
590
|
+
if (result.status !== 402) continue
|
|
591
|
+
const response = result.challenge as Response
|
|
592
|
+
const wwwAuth = response.headers.get('WWW-Authenticate')
|
|
593
|
+
if (wwwAuth) mergedHeaders.append('WWW-Authenticate', wwwAuth)
|
|
594
|
+
// Use the first handler's body for the problem details response.
|
|
595
|
+
if (!body) {
|
|
596
|
+
const contentType = response.headers.get('Content-Type')
|
|
597
|
+
if (contentType) mergedHeaders.set('Content-Type', contentType)
|
|
598
|
+
body = await response.text()
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return {
|
|
603
|
+
status: 402,
|
|
604
|
+
challenge: new Response(body, { status: 402, headers: mergedHeaders }),
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
405
609
|
/**
|
|
406
610
|
* Wraps a payment handler to create a Node.js HTTP listener.
|
|
407
611
|
*
|
|
@@ -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
|
|
|
@@ -258,9 +258,10 @@ export function charge<const parameters extends charge.Parameters>(
|
|
|
258
258
|
// Optimistic path: simulate to catch obvious reverts, then broadcast
|
|
259
259
|
// without waiting for on-chain confirmation. The returned receipt
|
|
260
260
|
// assumes success — callers opt into this risk via waitForConfirmation: false.
|
|
261
|
-
await
|
|
261
|
+
await viem_call(client, {
|
|
262
262
|
...transaction,
|
|
263
|
-
|
|
263
|
+
account: transaction.from,
|
|
264
|
+
// @ts-expect-error
|
|
264
265
|
calls,
|
|
265
266
|
})
|
|
266
267
|
const hash = await sendRawTransaction(client, {
|
|
@@ -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
|
|
|
@@ -289,8 +288,12 @@ export async function broadcastOpenTransaction(parameters: {
|
|
|
289
288
|
})()
|
|
290
289
|
|
|
291
290
|
if (!waitForConfirmation) {
|
|
292
|
-
|
|
293
|
-
|
|
291
|
+
await call(client, {
|
|
292
|
+
...transaction,
|
|
293
|
+
account: transaction.from,
|
|
294
|
+
// @ts-expect-error
|
|
295
|
+
calls,
|
|
296
|
+
})
|
|
294
297
|
const txHash = await sendRawTransaction(client, {
|
|
295
298
|
serializedTransaction: serializedTransaction_final as Transaction.TransactionSerializedTempo,
|
|
296
299
|
})
|
|
@@ -298,7 +301,7 @@ export async function broadcastOpenTransaction(parameters: {
|
|
|
298
301
|
return {
|
|
299
302
|
txHash,
|
|
300
303
|
onChain: {
|
|
301
|
-
payer: from,
|
|
304
|
+
payer: transaction.from,
|
|
302
305
|
payee,
|
|
303
306
|
token,
|
|
304
307
|
authorizedSigner,
|
|
@@ -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
|
-
}
|