@etherkit/viem-tx-tracker 0.0.9 → 0.2.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.
- package/README.md +196 -222
- package/dist/TrackedWalletClient.d.ts +38 -6
- package/dist/TrackedWalletClient.d.ts.map +1 -1
- package/dist/TrackedWalletClient.js +123 -49
- package/dist/TrackedWalletClient.js.map +1 -1
- package/dist/types.d.ts +142 -26
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/TrackedWalletClient.ts +294 -82
- package/src/types.ts +248 -106
|
@@ -109,19 +109,72 @@ function inferTxType(
|
|
|
109
109
|
return undefined; // Wallet will determine
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Blocks inference of TSource from the `source` option value.
|
|
114
|
+
*
|
|
115
|
+
* Without this, a call that passes no explicit type arguments would infer
|
|
116
|
+
* TSource from the value (widening `'local-signer'` to `string`) and silently
|
|
117
|
+
* collapse TMetadata to `unknown`, which turns metadata typing off. TSource is
|
|
118
|
+
* meant to be declared, never guessed.
|
|
119
|
+
*
|
|
120
|
+
* This is TypeScript 5.4's built-in `NoInfer`, spelled out so the package keeps
|
|
121
|
+
* working for consumers on older TypeScript versions.
|
|
122
|
+
*/
|
|
123
|
+
type NoInferSource<T> = [T][T extends any ? 0 : never];
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Resolve the configured source option into a value.
|
|
127
|
+
*
|
|
128
|
+
* A thunk is called every time (at broadcast), never memoized: one client can
|
|
129
|
+
* serve different wallets/accounts over its lifetime.
|
|
130
|
+
*/
|
|
131
|
+
function resolveSource<TSource>(
|
|
132
|
+
source: TSource | (() => TSource) | undefined,
|
|
133
|
+
): TSource {
|
|
134
|
+
return typeof source === 'function'
|
|
135
|
+
? (source as () => TSource)()
|
|
136
|
+
: (source as TSource);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The values that are carried onto a tracked transaction rather than observed:
|
|
141
|
+
* they exist only because the caller (or the client construction) supplied
|
|
142
|
+
* them, so every emission path must thread them explicitly.
|
|
143
|
+
*
|
|
144
|
+
* They travel as one object so that adding a carried field cannot silently
|
|
145
|
+
* miss a path, and so that two same-typed markers cannot be swapped by a
|
|
146
|
+
* positional-argument mistake.
|
|
147
|
+
*
|
|
148
|
+
* The tracker never inspects any of them.
|
|
149
|
+
*/
|
|
150
|
+
interface CarriedFields<TMetadata, TSource> {
|
|
151
|
+
/** What the application says the transaction MEANS. Persisted by consumers. */
|
|
152
|
+
metadata: TMetadata;
|
|
153
|
+
/** WHICH SIGNING ROUTE produced it. Observed at dispatch, persisted. */
|
|
154
|
+
source: TSource;
|
|
155
|
+
/**
|
|
156
|
+
* Which CALLER-SIDE REQUEST this send answers. Ephemeral plumbing.
|
|
157
|
+
*
|
|
158
|
+
* Deliberately a required property of type `string | undefined` rather than
|
|
159
|
+
* an optional one: a new call site must state that it has no correlation,
|
|
160
|
+
* instead of getting `undefined` by forgetting the field.
|
|
161
|
+
*/
|
|
162
|
+
correlation: string | undefined;
|
|
163
|
+
}
|
|
164
|
+
|
|
112
165
|
/**
|
|
113
166
|
* Create an UnknownTrackedTransaction for immediate emission.
|
|
114
167
|
* Populates all known intended values from the transaction parameters.
|
|
115
168
|
*/
|
|
116
|
-
function createUnknownTrackedTransaction<TMetadata>(
|
|
169
|
+
function createUnknownTrackedTransaction<TMetadata, TSource>(
|
|
117
170
|
hash: Hash,
|
|
118
171
|
from: Address,
|
|
119
172
|
nonce: number,
|
|
120
173
|
chainId: number | undefined,
|
|
121
|
-
|
|
174
|
+
carried: CarriedFields<TMetadata, TSource>,
|
|
122
175
|
broadcastTimestampMs: number,
|
|
123
176
|
params: IntendedTransactionParams,
|
|
124
|
-
): UnknownTrackedTransaction<TMetadata> {
|
|
177
|
+
): UnknownTrackedTransaction<TMetadata, TSource> {
|
|
125
178
|
const base = {
|
|
126
179
|
known: false as const,
|
|
127
180
|
chainId,
|
|
@@ -129,7 +182,7 @@ function createUnknownTrackedTransaction<TMetadata>(
|
|
|
129
182
|
from,
|
|
130
183
|
nonce,
|
|
131
184
|
broadcastTimestampMs,
|
|
132
|
-
|
|
185
|
+
...carried,
|
|
133
186
|
to: params.to,
|
|
134
187
|
value: params.value,
|
|
135
188
|
data: params.data,
|
|
@@ -247,12 +300,16 @@ function extractTransactionTypeFields(tx: Transaction):
|
|
|
247
300
|
|
|
248
301
|
/**
|
|
249
302
|
* Create a KnownTrackedTransaction from a fetched transaction.
|
|
303
|
+
*
|
|
304
|
+
* Note: this object is rebuilt from the chain fetch, not copied from the
|
|
305
|
+
* broadcasted one, so every carried-but-not-observed field (metadata, source,
|
|
306
|
+
* correlation) must be threaded in explicitly.
|
|
250
307
|
*/
|
|
251
|
-
function createKnownTrackedTransaction<TMetadata>(
|
|
308
|
+
function createKnownTrackedTransaction<TMetadata, TSource>(
|
|
252
309
|
tx: Transaction,
|
|
253
|
-
|
|
310
|
+
carried: CarriedFields<TMetadata, TSource>,
|
|
254
311
|
broadcastTimestampMs: number,
|
|
255
|
-
): KnownTrackedTransaction<TMetadata> {
|
|
312
|
+
): KnownTrackedTransaction<TMetadata, TSource> {
|
|
256
313
|
const base = {
|
|
257
314
|
known: true as const,
|
|
258
315
|
hash: tx.hash,
|
|
@@ -262,7 +319,7 @@ function createKnownTrackedTransaction<TMetadata>(
|
|
|
262
319
|
value: tx.value,
|
|
263
320
|
data: tx.input,
|
|
264
321
|
broadcastTimestampMs,
|
|
265
|
-
|
|
322
|
+
...carried,
|
|
266
323
|
};
|
|
267
324
|
|
|
268
325
|
const typeFields = extractTransactionTypeFields(tx);
|
|
@@ -270,20 +327,20 @@ function createKnownTrackedTransaction<TMetadata>(
|
|
|
270
327
|
return {
|
|
271
328
|
...base,
|
|
272
329
|
...typeFields,
|
|
273
|
-
} as KnownTrackedTransaction<TMetadata>;
|
|
330
|
+
} as KnownTrackedTransaction<TMetadata, TSource>;
|
|
274
331
|
}
|
|
275
332
|
|
|
276
333
|
/**
|
|
277
334
|
* Create a KnownTrackedTransaction from a parsed raw transaction.
|
|
278
335
|
*/
|
|
279
|
-
function createKnownTrackedTransactionFromRaw<TMetadata>(
|
|
336
|
+
function createKnownTrackedTransactionFromRaw<TMetadata, TSource>(
|
|
280
337
|
parsedTx: ParseTransactionReturnType<`0x${string}`>,
|
|
281
338
|
from: `0x${string}`,
|
|
282
339
|
hash: Hash,
|
|
283
|
-
|
|
340
|
+
carried: CarriedFields<TMetadata, TSource>,
|
|
284
341
|
chainId: number | undefined,
|
|
285
342
|
broadcastTimestampMs: number,
|
|
286
|
-
): KnownTrackedTransaction<TMetadata> {
|
|
343
|
+
): KnownTrackedTransaction<TMetadata, TSource> {
|
|
287
344
|
const base = {
|
|
288
345
|
known: true as const,
|
|
289
346
|
hash,
|
|
@@ -293,7 +350,7 @@ function createKnownTrackedTransactionFromRaw<TMetadata>(
|
|
|
293
350
|
value: parsedTx.value ?? 0n,
|
|
294
351
|
data: parsedTx.data ?? '0x',
|
|
295
352
|
broadcastTimestampMs,
|
|
296
|
-
|
|
353
|
+
...carried,
|
|
297
354
|
};
|
|
298
355
|
|
|
299
356
|
// Determine transaction type from parsed tx
|
|
@@ -428,7 +485,7 @@ type InferAccount<T> =
|
|
|
428
485
|
/**
|
|
429
486
|
* Builder interface returned by createTrackedWalletClient for the curried API.
|
|
430
487
|
*/
|
|
431
|
-
export interface TrackedWalletClientBuilder<TMetadata> {
|
|
488
|
+
export interface TrackedWalletClientBuilder<TMetadata, TSource = undefined> {
|
|
432
489
|
/**
|
|
433
490
|
* Create the tracked wallet client using the provided wallet and public clients.
|
|
434
491
|
*
|
|
@@ -443,7 +500,8 @@ export interface TrackedWalletClientBuilder<TMetadata> {
|
|
|
443
500
|
TMetadata,
|
|
444
501
|
InferTransport<TClient>,
|
|
445
502
|
InferChain<TClient>,
|
|
446
|
-
InferAccount<TClient
|
|
503
|
+
InferAccount<TClient>,
|
|
504
|
+
TSource
|
|
447
505
|
>;
|
|
448
506
|
}
|
|
449
507
|
|
|
@@ -452,7 +510,10 @@ export interface TrackedWalletClientBuilder<TMetadata> {
|
|
|
452
510
|
* This builder returns a TrackedWalletClientAutoPopulate that auto-populates operation, functionName and args.
|
|
453
511
|
* TMetadata must be a type where FunctionCallMetadata is assignable to it.
|
|
454
512
|
*/
|
|
455
|
-
export interface TrackedWalletClientAutoPopulateBuilder<
|
|
513
|
+
export interface TrackedWalletClientAutoPopulateBuilder<
|
|
514
|
+
TMetadata,
|
|
515
|
+
TSource = undefined,
|
|
516
|
+
> {
|
|
456
517
|
/**
|
|
457
518
|
* Create the tracked wallet client using the provided wallet and public clients.
|
|
458
519
|
* writeContract and writeContractSync will automatically populate operation, functionName and args.
|
|
@@ -468,7 +529,8 @@ export interface TrackedWalletClientAutoPopulateBuilder<TMetadata> {
|
|
|
468
529
|
TMetadata,
|
|
469
530
|
InferTransport<TClient>,
|
|
470
531
|
InferChain<TClient>,
|
|
471
|
-
InferAccount<TClient
|
|
532
|
+
InferAccount<TClient>,
|
|
533
|
+
TSource
|
|
472
534
|
>;
|
|
473
535
|
}
|
|
474
536
|
|
|
@@ -482,6 +544,12 @@ export interface TrackedWalletClientAutoPopulateBuilder<TMetadata> {
|
|
|
482
544
|
* - Event emission for tracking
|
|
483
545
|
*
|
|
484
546
|
* @typeParam TMetadata - The metadata type. Use `MyMeta | undefined` to make metadata optional.
|
|
547
|
+
* @typeParam TSource - An opaque marker of which signing route this client is.
|
|
548
|
+
* Defaults to `undefined` (no source). When it does not include `undefined`,
|
|
549
|
+
* the `source` option is mandatory. It must always be passed explicitly:
|
|
550
|
+
* inference from the option value is deliberately blocked, so passing a
|
|
551
|
+
* `source` without also declaring TSource is an error rather than a silently
|
|
552
|
+
* widened type.
|
|
485
553
|
* @returns A builder with a `.using()` method to provide the wallet and public clients
|
|
486
554
|
*
|
|
487
555
|
* @example
|
|
@@ -502,12 +570,32 @@ export interface TrackedWalletClientAutoPopulateBuilder<TMetadata> {
|
|
|
502
570
|
* type MyMetadata = OperationMetadata & { purpose: string };
|
|
503
571
|
* const tracked = createTrackedWalletClient<MyMetadata>({ populateMetadata: true })
|
|
504
572
|
* .using(walletClient, publicClient);
|
|
573
|
+
*
|
|
574
|
+
* // With an opaque source marking which signing route this client is.
|
|
575
|
+
* // Non-undefined TSource makes the `source` option mandatory.
|
|
576
|
+
* type MySource = 'connected-wallet' | 'local-signer' | 'payer-wallet';
|
|
577
|
+
* const tracked = createTrackedWalletClient<{purpose: string}, MySource>({
|
|
578
|
+
* source: 'local-signer',
|
|
579
|
+
* }).using(walletClient, publicClient);
|
|
580
|
+
*
|
|
581
|
+
* // ...or as a thunk, re-evaluated at every broadcast
|
|
582
|
+
* const tracked = createTrackedWalletClient<{purpose: string}, MySource>({
|
|
583
|
+
* source: () => currentRoute(),
|
|
584
|
+
* }).using(walletClient, publicClient);
|
|
505
585
|
* ```
|
|
506
586
|
*/
|
|
507
|
-
// Overload 1: Standard mode
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
587
|
+
// Overload 1: Standard mode. The options argument is required only when
|
|
588
|
+
// TSource does not include undefined.
|
|
589
|
+
export function createTrackedWalletClient<TMetadata, TSource = undefined>(
|
|
590
|
+
...args: undefined extends TSource
|
|
591
|
+
? [
|
|
592
|
+
options?: CreateTrackedWalletClientOptions<
|
|
593
|
+
false,
|
|
594
|
+
NoInferSource<TSource>
|
|
595
|
+
>,
|
|
596
|
+
]
|
|
597
|
+
: [options: CreateTrackedWalletClientOptions<false, NoInferSource<TSource>>]
|
|
598
|
+
): TrackedWalletClientBuilder<TMetadata, TSource>;
|
|
511
599
|
|
|
512
600
|
// Overload 2: Auto-populate mode with default PopulatedMetadata
|
|
513
601
|
export function createTrackedWalletClient(
|
|
@@ -515,23 +603,31 @@ export function createTrackedWalletClient(
|
|
|
515
603
|
): TrackedWalletClientAutoPopulateBuilder<PopulatedMetadata>;
|
|
516
604
|
|
|
517
605
|
// Overload 3: Auto-populate mode with custom metadata (must allow FunctionCallMetadata)
|
|
518
|
-
export function createTrackedWalletClient<TMetadata>(
|
|
519
|
-
options: CreateTrackedWalletClientOptions<true
|
|
520
|
-
): TrackedWalletClientAutoPopulateBuilder<TMetadata>;
|
|
606
|
+
export function createTrackedWalletClient<TMetadata, TSource = undefined>(
|
|
607
|
+
options: CreateTrackedWalletClientOptions<true, NoInferSource<TSource>>,
|
|
608
|
+
): TrackedWalletClientAutoPopulateBuilder<TMetadata, TSource>;
|
|
521
609
|
|
|
522
610
|
// Implementation
|
|
523
|
-
export function createTrackedWalletClient<
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
611
|
+
export function createTrackedWalletClient<
|
|
612
|
+
TMetadata,
|
|
613
|
+
TSource = undefined,
|
|
614
|
+
>(options?: {
|
|
615
|
+
populateMetadata?: boolean;
|
|
616
|
+
clock?: () => number;
|
|
617
|
+
source?: TSource | (() => TSource);
|
|
618
|
+
}):
|
|
619
|
+
| TrackedWalletClientBuilder<TMetadata, TSource>
|
|
620
|
+
| TrackedWalletClientAutoPopulateBuilder<TMetadata, TSource> {
|
|
528
621
|
const populateMetadata = options?.populateMetadata ?? false;
|
|
529
622
|
const clock = options?.clock ?? Date.now;
|
|
623
|
+
// Kept unresolved: a thunk is evaluated at each broadcast, not here.
|
|
624
|
+
const sourceOption = options?.source;
|
|
530
625
|
|
|
531
626
|
if (populateMetadata) {
|
|
532
|
-
return createAutoPopulateBuilder<TMetadata>(
|
|
627
|
+
return createAutoPopulateBuilder<TMetadata, TSource>(
|
|
533
628
|
clock,
|
|
534
|
-
|
|
629
|
+
sourceOption,
|
|
630
|
+
) as TrackedWalletClientAutoPopulateBuilder<TMetadata, TSource>;
|
|
535
631
|
}
|
|
536
632
|
|
|
537
633
|
return {
|
|
@@ -542,7 +638,8 @@ export function createTrackedWalletClient<TMetadata>(
|
|
|
542
638
|
TMetadata,
|
|
543
639
|
InferTransport<TClient>,
|
|
544
640
|
InferChain<TClient>,
|
|
545
|
-
InferAccount<TClient
|
|
641
|
+
InferAccount<TClient>,
|
|
642
|
+
TSource
|
|
546
643
|
> {
|
|
547
644
|
// Type aliases for internal use
|
|
548
645
|
type TTransport = InferTransport<TClient>;
|
|
@@ -551,8 +648,8 @@ export function createTrackedWalletClient<TMetadata>(
|
|
|
551
648
|
|
|
552
649
|
// Create emitter for transaction events
|
|
553
650
|
const emitter = new Emitter<{
|
|
554
|
-
'transaction:broadcasted': TrackedTransaction<TMetadata>;
|
|
555
|
-
'transaction:
|
|
651
|
+
'transaction:broadcasted': TrackedTransaction<TMetadata, TSource>;
|
|
652
|
+
'transaction:known': KnownTrackedTransaction<TMetadata, TSource>;
|
|
556
653
|
}>();
|
|
557
654
|
|
|
558
655
|
/**
|
|
@@ -608,27 +705,27 @@ export function createTrackedWalletClient<TMetadata>(
|
|
|
608
705
|
}
|
|
609
706
|
|
|
610
707
|
/**
|
|
611
|
-
* Fetch full transaction data and emit transaction:
|
|
708
|
+
* Fetch full transaction data and emit transaction:known event.
|
|
612
709
|
* Non-blocking, runs in background. Does not throw.
|
|
613
710
|
*/
|
|
614
711
|
async function fetchAndEmitFullData(
|
|
615
712
|
hash: Hash,
|
|
616
|
-
|
|
713
|
+
carried: CarriedFields<TMetadata, TSource>,
|
|
617
714
|
broadcastTimestampMs: number,
|
|
618
715
|
): Promise<void> {
|
|
619
716
|
try {
|
|
620
717
|
const tx = await publicClient.getTransaction({hash});
|
|
621
718
|
const knownTx = createKnownTrackedTransaction(
|
|
622
719
|
tx,
|
|
623
|
-
|
|
720
|
+
carried,
|
|
624
721
|
broadcastTimestampMs,
|
|
625
722
|
);
|
|
626
|
-
emitter.emit('transaction:
|
|
723
|
+
emitter.emit('transaction:known', knownTx);
|
|
627
724
|
} catch (error) {
|
|
628
|
-
// Log but don't throw - transaction:
|
|
725
|
+
// Log but don't throw - transaction:known simply won't fire
|
|
629
726
|
console.warn(
|
|
630
727
|
`[TrackedWalletClient] Could not fetch tx ${hash}. ` +
|
|
631
|
-
`transaction:
|
|
728
|
+
`transaction:known event will not be emitted. Error: ${error}`,
|
|
632
729
|
);
|
|
633
730
|
}
|
|
634
731
|
}
|
|
@@ -636,19 +733,46 @@ export function createTrackedWalletClient<TMetadata>(
|
|
|
636
733
|
/**
|
|
637
734
|
* Common wrapper for transaction methods that broadcast (sendTransaction, writeContract).
|
|
638
735
|
* Emits transaction:broadcasted immediately with intended values,
|
|
639
|
-
* then fetches and emits transaction:
|
|
736
|
+
* then fetches and emits transaction:known with actual values.
|
|
640
737
|
*/
|
|
641
738
|
async function executeTrackedTransaction<T, R>(args: {
|
|
642
739
|
account?: Account | Address;
|
|
643
740
|
nonce?: NonceOption;
|
|
644
741
|
metadata: TMetadata;
|
|
645
|
-
|
|
742
|
+
correlation: string | undefined;
|
|
743
|
+
/**
|
|
744
|
+
* The caller's remaining args, spread verbatim into viem's own call.
|
|
745
|
+
*
|
|
746
|
+
* `{correlation?: never}` is the compile-time half of the no-forwarding
|
|
747
|
+
* rule: a wrapper that forgets to destructure `correlation` out fails
|
|
748
|
+
* here instead of handing the wallet an unrecognised parameter. Nothing
|
|
749
|
+
* downstream would catch it, since the viem call is behind an `as any`.
|
|
750
|
+
*/
|
|
751
|
+
restArgs: T & {correlation?: never};
|
|
646
752
|
intendedParams: IntendedTransactionParams;
|
|
647
753
|
execute: (argsWithNonce: T & {nonce: number}) => Promise<R>;
|
|
648
754
|
extractHash: (result: R) => Hash;
|
|
649
755
|
}): Promise<R> {
|
|
650
|
-
const {
|
|
756
|
+
const {
|
|
757
|
+
metadata,
|
|
758
|
+
correlation,
|
|
759
|
+
restArgs,
|
|
760
|
+
intendedParams,
|
|
761
|
+
execute,
|
|
762
|
+
extractHash,
|
|
763
|
+
} = args;
|
|
651
764
|
const broadcastTimestampMs = clock();
|
|
765
|
+
// Stamped here, in the same step as from/nonce/broadcastTimestampMs.
|
|
766
|
+
// A thunk is re-evaluated on every send, never captured once.
|
|
767
|
+
const source = resolveSource(sourceOption);
|
|
768
|
+
// The carried values travel as one bundle from here on: metadata (what
|
|
769
|
+
// the tx means), source (which route signed) and correlation (which
|
|
770
|
+
// caller-side request this send answers). None is interpreted.
|
|
771
|
+
const carried: CarriedFields<TMetadata, TSource> = {
|
|
772
|
+
metadata,
|
|
773
|
+
source,
|
|
774
|
+
correlation,
|
|
775
|
+
};
|
|
652
776
|
|
|
653
777
|
// Extract common context
|
|
654
778
|
const {from, intendedNonce} = await extractTransactionContext(args);
|
|
@@ -668,14 +792,14 @@ export function createTrackedWalletClient<TMetadata>(
|
|
|
668
792
|
from,
|
|
669
793
|
intendedNonce,
|
|
670
794
|
walletClient.chain?.id,
|
|
671
|
-
|
|
795
|
+
carried,
|
|
672
796
|
broadcastTimestampMs,
|
|
673
797
|
intendedParams,
|
|
674
798
|
);
|
|
675
799
|
emitter.emit('transaction:broadcasted', unknownTx);
|
|
676
800
|
|
|
677
|
-
// Fire-and-forget: fetch full data and emit transaction:
|
|
678
|
-
fetchAndEmitFullData(hash,
|
|
801
|
+
// Fire-and-forget: fetch full data and emit transaction:known
|
|
802
|
+
fetchAndEmitFullData(hash, carried, broadcastTimestampMs);
|
|
679
803
|
|
|
680
804
|
return result;
|
|
681
805
|
}
|
|
@@ -688,11 +812,26 @@ export function createTrackedWalletClient<TMetadata>(
|
|
|
688
812
|
async function executeTrackedRawTransaction<R>(args: {
|
|
689
813
|
serializedTransaction: TransactionSerialized;
|
|
690
814
|
metadata: TMetadata;
|
|
815
|
+
correlation: string | undefined;
|
|
691
816
|
execute: () => Promise<R>;
|
|
692
817
|
extractHash: (result: R) => Hash;
|
|
693
818
|
}): Promise<R> {
|
|
694
|
-
const {
|
|
819
|
+
const {
|
|
820
|
+
serializedTransaction,
|
|
821
|
+
metadata,
|
|
822
|
+
correlation,
|
|
823
|
+
execute,
|
|
824
|
+
extractHash,
|
|
825
|
+
} = args;
|
|
695
826
|
const broadcastTimestampMs = clock();
|
|
827
|
+
// Stamped here, in the same step as from/nonce/broadcastTimestampMs.
|
|
828
|
+
// A thunk is re-evaluated on every send, never captured once.
|
|
829
|
+
const source = resolveSource(sourceOption);
|
|
830
|
+
const carried: CarriedFields<TMetadata, TSource> = {
|
|
831
|
+
metadata,
|
|
832
|
+
source,
|
|
833
|
+
correlation,
|
|
834
|
+
};
|
|
696
835
|
|
|
697
836
|
const from = await recoverTransactionAddress({serializedTransaction});
|
|
698
837
|
|
|
@@ -707,7 +846,7 @@ export function createTrackedWalletClient<TMetadata>(
|
|
|
707
846
|
parsedTx,
|
|
708
847
|
from,
|
|
709
848
|
hash,
|
|
710
|
-
|
|
849
|
+
carried,
|
|
711
850
|
walletClient.chain?.id,
|
|
712
851
|
broadcastTimestampMs,
|
|
713
852
|
);
|
|
@@ -715,8 +854,11 @@ export function createTrackedWalletClient<TMetadata>(
|
|
|
715
854
|
// Emit as KnownTrackedTransaction since we have all data
|
|
716
855
|
emitter.emit('transaction:broadcasted', knownTx);
|
|
717
856
|
|
|
718
|
-
//
|
|
719
|
-
|
|
857
|
+
// The values are final (parsed from the signed payload, not merely
|
|
858
|
+
// intended), which is exactly what transaction:known promises, so it
|
|
859
|
+
// is emitted here too: every tracked transaction reaches that event,
|
|
860
|
+
// and a consumer can persist on it alone.
|
|
861
|
+
emitter.emit('transaction:known', knownTx);
|
|
720
862
|
|
|
721
863
|
return result;
|
|
722
864
|
}
|
|
@@ -756,13 +898,14 @@ export function createTrackedWalletClient<TMetadata>(
|
|
|
756
898
|
TChainOverride
|
|
757
899
|
>,
|
|
758
900
|
): Promise<Hash> {
|
|
759
|
-
const {metadata, nonce, ...writeArgs} = args;
|
|
901
|
+
const {metadata, correlation, nonce, ...writeArgs} = args;
|
|
760
902
|
const intendedParams = extractIntendedParamsFromWriteContract(args);
|
|
761
903
|
|
|
762
904
|
return executeTrackedTransaction({
|
|
763
905
|
account: normalizeAccount(args.account),
|
|
764
906
|
nonce,
|
|
765
907
|
metadata: metadata as TMetadata,
|
|
908
|
+
correlation,
|
|
766
909
|
restArgs: writeArgs,
|
|
767
910
|
intendedParams,
|
|
768
911
|
execute: (argsWithNonce) =>
|
|
@@ -781,13 +924,14 @@ export function createTrackedWalletClient<TMetadata>(
|
|
|
781
924
|
TChainOverride
|
|
782
925
|
>,
|
|
783
926
|
): Promise<Hash> {
|
|
784
|
-
const {metadata, nonce, ...sendArgs} = args;
|
|
927
|
+
const {metadata, correlation, nonce, ...sendArgs} = args;
|
|
785
928
|
const intendedParams = extractIntendedParamsFromSendTransaction(args);
|
|
786
929
|
|
|
787
930
|
return executeTrackedTransaction({
|
|
788
931
|
account: normalizeAccount(args.account),
|
|
789
932
|
nonce,
|
|
790
933
|
metadata: metadata as TMetadata,
|
|
934
|
+
correlation,
|
|
791
935
|
restArgs: sendArgs,
|
|
792
936
|
intendedParams,
|
|
793
937
|
execute: (argsWithNonce) =>
|
|
@@ -799,11 +943,12 @@ export function createTrackedWalletClient<TMetadata>(
|
|
|
799
943
|
async sendRawTransaction(
|
|
800
944
|
args: TrackedRawTransactionParameters<TMetadata>,
|
|
801
945
|
): Promise<Hash> {
|
|
802
|
-
const {metadata, serializedTransaction} = args;
|
|
946
|
+
const {metadata, correlation, serializedTransaction} = args;
|
|
803
947
|
|
|
804
948
|
return executeTrackedRawTransaction({
|
|
805
949
|
serializedTransaction,
|
|
806
950
|
metadata: metadata as TMetadata,
|
|
951
|
+
correlation,
|
|
807
952
|
execute: () =>
|
|
808
953
|
walletClient.sendRawTransaction({serializedTransaction}),
|
|
809
954
|
extractHash: (hash) => hash,
|
|
@@ -837,13 +982,14 @@ export function createTrackedWalletClient<TMetadata>(
|
|
|
837
982
|
TChainOverride
|
|
838
983
|
>,
|
|
839
984
|
): Promise<TransactionReceipt> {
|
|
840
|
-
const {metadata, nonce, ...writeArgs} = args;
|
|
985
|
+
const {metadata, correlation, nonce, ...writeArgs} = args;
|
|
841
986
|
const intendedParams = extractIntendedParamsFromWriteContract(args);
|
|
842
987
|
|
|
843
988
|
return executeTrackedTransaction({
|
|
844
989
|
account: normalizeAccount(args.account),
|
|
845
990
|
nonce,
|
|
846
991
|
metadata: metadata as TMetadata,
|
|
992
|
+
correlation,
|
|
847
993
|
restArgs: writeArgs,
|
|
848
994
|
intendedParams,
|
|
849
995
|
execute: (argsWithNonce) =>
|
|
@@ -862,13 +1008,14 @@ export function createTrackedWalletClient<TMetadata>(
|
|
|
862
1008
|
TChainOverride
|
|
863
1009
|
>,
|
|
864
1010
|
): Promise<TransactionReceipt> {
|
|
865
|
-
const {metadata, nonce, ...sendArgs} = args;
|
|
1011
|
+
const {metadata, correlation, nonce, ...sendArgs} = args;
|
|
866
1012
|
const intendedParams = extractIntendedParamsFromSendTransaction(args);
|
|
867
1013
|
|
|
868
1014
|
return executeTrackedTransaction({
|
|
869
1015
|
account: normalizeAccount(args.account),
|
|
870
1016
|
nonce,
|
|
871
1017
|
metadata: metadata as TMetadata,
|
|
1018
|
+
correlation,
|
|
872
1019
|
restArgs: sendArgs,
|
|
873
1020
|
intendedParams,
|
|
874
1021
|
execute: (argsWithNonce) =>
|
|
@@ -880,11 +1027,12 @@ export function createTrackedWalletClient<TMetadata>(
|
|
|
880
1027
|
async sendRawTransactionSync(
|
|
881
1028
|
args: TrackedRawTransactionParameters<TMetadata>,
|
|
882
1029
|
): Promise<TransactionReceipt> {
|
|
883
|
-
const {metadata, serializedTransaction} = args;
|
|
1030
|
+
const {metadata, correlation, serializedTransaction} = args;
|
|
884
1031
|
|
|
885
1032
|
return executeTrackedRawTransaction({
|
|
886
1033
|
serializedTransaction,
|
|
887
1034
|
metadata: metadata as TMetadata,
|
|
1035
|
+
correlation,
|
|
888
1036
|
execute: () =>
|
|
889
1037
|
walletClient.sendRawTransactionSync({serializedTransaction}),
|
|
890
1038
|
extractHash: (receipt) => receipt.transactionHash,
|
|
@@ -906,9 +1054,10 @@ export function createTrackedWalletClient<TMetadata>(
|
|
|
906
1054
|
* Create an auto-populate builder for TrackedWalletClient.
|
|
907
1055
|
* This builder auto-populates operation, functionName and args in writeContract metadata.
|
|
908
1056
|
*/
|
|
909
|
-
function createAutoPopulateBuilder<TMetadata>(
|
|
1057
|
+
function createAutoPopulateBuilder<TMetadata, TSource>(
|
|
910
1058
|
clock: () => number,
|
|
911
|
-
|
|
1059
|
+
sourceOption: TSource | (() => TSource) | undefined,
|
|
1060
|
+
): TrackedWalletClientAutoPopulateBuilder<TMetadata, TSource> {
|
|
912
1061
|
return {
|
|
913
1062
|
using<TClient extends WalletClient>(
|
|
914
1063
|
walletClient: TClient,
|
|
@@ -917,7 +1066,8 @@ function createAutoPopulateBuilder<TMetadata>(
|
|
|
917
1066
|
TMetadata,
|
|
918
1067
|
InferTransport<TClient>,
|
|
919
1068
|
InferChain<TClient>,
|
|
920
|
-
InferAccount<TClient
|
|
1069
|
+
InferAccount<TClient>,
|
|
1070
|
+
TSource
|
|
921
1071
|
> {
|
|
922
1072
|
// Type aliases for internal use
|
|
923
1073
|
type TTransport = InferTransport<TClient>;
|
|
@@ -926,8 +1076,8 @@ function createAutoPopulateBuilder<TMetadata>(
|
|
|
926
1076
|
|
|
927
1077
|
// Create emitter for transaction events
|
|
928
1078
|
const emitter = new Emitter<{
|
|
929
|
-
'transaction:broadcasted': TrackedTransaction<TMetadata>;
|
|
930
|
-
'transaction:
|
|
1079
|
+
'transaction:broadcasted': TrackedTransaction<TMetadata, TSource>;
|
|
1080
|
+
'transaction:known': KnownTrackedTransaction<TMetadata, TSource>;
|
|
931
1081
|
}>();
|
|
932
1082
|
|
|
933
1083
|
/**
|
|
@@ -969,27 +1119,27 @@ function createAutoPopulateBuilder<TMetadata>(
|
|
|
969
1119
|
}
|
|
970
1120
|
|
|
971
1121
|
/**
|
|
972
|
-
* Fetch full transaction data and emit transaction:
|
|
1122
|
+
* Fetch full transaction data and emit transaction:known event.
|
|
973
1123
|
* Non-blocking, runs in background. Does not throw.
|
|
974
1124
|
*/
|
|
975
1125
|
async function fetchAndEmitFullData(
|
|
976
1126
|
hash: Hash,
|
|
977
|
-
|
|
1127
|
+
carried: CarriedFields<TMetadata, TSource>,
|
|
978
1128
|
broadcastTimestampMs: number,
|
|
979
1129
|
): Promise<void> {
|
|
980
1130
|
try {
|
|
981
1131
|
const tx = await publicClient.getTransaction({hash});
|
|
982
1132
|
const knownTx = createKnownTrackedTransaction(
|
|
983
1133
|
tx,
|
|
984
|
-
|
|
1134
|
+
carried,
|
|
985
1135
|
broadcastTimestampMs,
|
|
986
1136
|
);
|
|
987
|
-
emitter.emit('transaction:
|
|
1137
|
+
emitter.emit('transaction:known', knownTx);
|
|
988
1138
|
} catch (error) {
|
|
989
|
-
// Log but don't throw - transaction:
|
|
1139
|
+
// Log but don't throw - transaction:known simply won't fire
|
|
990
1140
|
console.warn(
|
|
991
1141
|
`[TrackedWalletClient] Could not fetch tx ${hash}. ` +
|
|
992
|
-
`transaction:
|
|
1142
|
+
`transaction:known event will not be emitted. Error: ${error}`,
|
|
993
1143
|
);
|
|
994
1144
|
}
|
|
995
1145
|
}
|
|
@@ -1026,19 +1176,46 @@ function createAutoPopulateBuilder<TMetadata>(
|
|
|
1026
1176
|
/**
|
|
1027
1177
|
* Common wrapper for transaction methods that broadcast.
|
|
1028
1178
|
* Emits transaction:broadcasted immediately with intended values,
|
|
1029
|
-
* then fetches and emits transaction:
|
|
1179
|
+
* then fetches and emits transaction:known with actual values.
|
|
1030
1180
|
*/
|
|
1031
1181
|
async function executeTrackedTransaction<T, R>(args: {
|
|
1032
1182
|
account?: Account | Address;
|
|
1033
1183
|
nonce?: NonceOption;
|
|
1034
1184
|
metadata: TMetadata;
|
|
1035
|
-
|
|
1185
|
+
correlation: string | undefined;
|
|
1186
|
+
/**
|
|
1187
|
+
* The caller's remaining args, spread verbatim into viem's own call.
|
|
1188
|
+
*
|
|
1189
|
+
* `{correlation?: never}` is the compile-time half of the no-forwarding
|
|
1190
|
+
* rule: a wrapper that forgets to destructure `correlation` out fails
|
|
1191
|
+
* here instead of handing the wallet an unrecognised parameter. Nothing
|
|
1192
|
+
* downstream would catch it, since the viem call is behind an `as any`.
|
|
1193
|
+
*/
|
|
1194
|
+
restArgs: T & {correlation?: never};
|
|
1036
1195
|
intendedParams: IntendedTransactionParams;
|
|
1037
1196
|
execute: (argsWithNonce: T & {nonce: number}) => Promise<R>;
|
|
1038
1197
|
extractHash: (result: R) => Hash;
|
|
1039
1198
|
}): Promise<R> {
|
|
1040
|
-
const {
|
|
1199
|
+
const {
|
|
1200
|
+
metadata,
|
|
1201
|
+
correlation,
|
|
1202
|
+
restArgs,
|
|
1203
|
+
intendedParams,
|
|
1204
|
+
execute,
|
|
1205
|
+
extractHash,
|
|
1206
|
+
} = args;
|
|
1041
1207
|
const broadcastTimestampMs = clock();
|
|
1208
|
+
// Stamped here, in the same step as from/nonce/broadcastTimestampMs.
|
|
1209
|
+
// A thunk is re-evaluated on every send, never captured once.
|
|
1210
|
+
const source = resolveSource(sourceOption);
|
|
1211
|
+
// The carried values travel as one bundle from here on: metadata (what
|
|
1212
|
+
// the tx means), source (which route signed) and correlation (which
|
|
1213
|
+
// caller-side request this send answers). None is interpreted.
|
|
1214
|
+
const carried: CarriedFields<TMetadata, TSource> = {
|
|
1215
|
+
metadata,
|
|
1216
|
+
source,
|
|
1217
|
+
correlation,
|
|
1218
|
+
};
|
|
1042
1219
|
|
|
1043
1220
|
const {from, intendedNonce} = await extractTransactionContext(args);
|
|
1044
1221
|
|
|
@@ -1056,14 +1233,14 @@ function createAutoPopulateBuilder<TMetadata>(
|
|
|
1056
1233
|
from,
|
|
1057
1234
|
intendedNonce,
|
|
1058
1235
|
walletClient.chain?.id,
|
|
1059
|
-
|
|
1236
|
+
carried,
|
|
1060
1237
|
broadcastTimestampMs,
|
|
1061
1238
|
intendedParams,
|
|
1062
1239
|
);
|
|
1063
1240
|
emitter.emit('transaction:broadcasted', unknownTx);
|
|
1064
1241
|
|
|
1065
|
-
// Fire-and-forget: fetch full data and emit transaction:
|
|
1066
|
-
fetchAndEmitFullData(hash,
|
|
1242
|
+
// Fire-and-forget: fetch full data and emit transaction:known
|
|
1243
|
+
fetchAndEmitFullData(hash, carried, broadcastTimestampMs);
|
|
1067
1244
|
|
|
1068
1245
|
return result;
|
|
1069
1246
|
}
|
|
@@ -1076,11 +1253,26 @@ function createAutoPopulateBuilder<TMetadata>(
|
|
|
1076
1253
|
async function executeTrackedRawTransaction<R>(args: {
|
|
1077
1254
|
serializedTransaction: TransactionSerialized;
|
|
1078
1255
|
metadata: TMetadata;
|
|
1256
|
+
correlation: string | undefined;
|
|
1079
1257
|
execute: () => Promise<R>;
|
|
1080
1258
|
extractHash: (result: R) => Hash;
|
|
1081
1259
|
}): Promise<R> {
|
|
1082
|
-
const {
|
|
1260
|
+
const {
|
|
1261
|
+
serializedTransaction,
|
|
1262
|
+
metadata,
|
|
1263
|
+
correlation,
|
|
1264
|
+
execute,
|
|
1265
|
+
extractHash,
|
|
1266
|
+
} = args;
|
|
1083
1267
|
const broadcastTimestampMs = clock();
|
|
1268
|
+
// Stamped here, in the same step as from/nonce/broadcastTimestampMs.
|
|
1269
|
+
// A thunk is re-evaluated on every send, never captured once.
|
|
1270
|
+
const source = resolveSource(sourceOption);
|
|
1271
|
+
const carried: CarriedFields<TMetadata, TSource> = {
|
|
1272
|
+
metadata,
|
|
1273
|
+
source,
|
|
1274
|
+
correlation,
|
|
1275
|
+
};
|
|
1084
1276
|
|
|
1085
1277
|
const from = await recoverTransactionAddress({serializedTransaction});
|
|
1086
1278
|
const parsedTx = parseTransaction(serializedTransaction);
|
|
@@ -1094,7 +1286,7 @@ function createAutoPopulateBuilder<TMetadata>(
|
|
|
1094
1286
|
parsedTx,
|
|
1095
1287
|
from,
|
|
1096
1288
|
hash,
|
|
1097
|
-
|
|
1289
|
+
carried,
|
|
1098
1290
|
walletClient.chain?.id,
|
|
1099
1291
|
broadcastTimestampMs,
|
|
1100
1292
|
);
|
|
@@ -1102,7 +1294,11 @@ function createAutoPopulateBuilder<TMetadata>(
|
|
|
1102
1294
|
// Emit as KnownTrackedTransaction since we have all data
|
|
1103
1295
|
emitter.emit('transaction:broadcasted', knownTx);
|
|
1104
1296
|
|
|
1105
|
-
//
|
|
1297
|
+
// The values are final (parsed from the signed payload, not merely
|
|
1298
|
+
// intended), which is exactly what transaction:known promises, so it
|
|
1299
|
+
// is emitted here too: every tracked transaction reaches that event,
|
|
1300
|
+
// and a consumer can persist on it alone.
|
|
1301
|
+
emitter.emit('transaction:known', knownTx);
|
|
1106
1302
|
|
|
1107
1303
|
return result;
|
|
1108
1304
|
}
|
|
@@ -1142,7 +1338,12 @@ function createAutoPopulateBuilder<TMetadata>(
|
|
|
1142
1338
|
TChainOverride
|
|
1143
1339
|
>,
|
|
1144
1340
|
): Promise<Hash> {
|
|
1145
|
-
const {
|
|
1341
|
+
const {
|
|
1342
|
+
metadata: userMetadata,
|
|
1343
|
+
correlation,
|
|
1344
|
+
nonce,
|
|
1345
|
+
...writeArgs
|
|
1346
|
+
} = args;
|
|
1146
1347
|
|
|
1147
1348
|
// Validate that user didn't provide operation, functionName or args
|
|
1148
1349
|
validateNoAutoPopulatedFieldsInMetadata(userMetadata);
|
|
@@ -1161,6 +1362,7 @@ function createAutoPopulateBuilder<TMetadata>(
|
|
|
1161
1362
|
account: normalizeAccount(args.account),
|
|
1162
1363
|
nonce,
|
|
1163
1364
|
metadata: finalMetadata,
|
|
1365
|
+
correlation,
|
|
1164
1366
|
restArgs: writeArgs,
|
|
1165
1367
|
intendedParams,
|
|
1166
1368
|
execute: (argsWithNonce) =>
|
|
@@ -1179,13 +1381,14 @@ function createAutoPopulateBuilder<TMetadata>(
|
|
|
1179
1381
|
TChainOverride
|
|
1180
1382
|
>,
|
|
1181
1383
|
): Promise<Hash> {
|
|
1182
|
-
const {metadata, nonce, ...sendArgs} = args;
|
|
1384
|
+
const {metadata, correlation, nonce, ...sendArgs} = args;
|
|
1183
1385
|
const intendedParams = extractIntendedParamsFromSendTransaction(args);
|
|
1184
1386
|
|
|
1185
1387
|
return executeTrackedTransaction({
|
|
1186
1388
|
account: normalizeAccount(args.account),
|
|
1187
1389
|
nonce,
|
|
1188
1390
|
metadata: metadata as TMetadata,
|
|
1391
|
+
correlation,
|
|
1189
1392
|
restArgs: sendArgs,
|
|
1190
1393
|
intendedParams,
|
|
1191
1394
|
execute: (argsWithNonce) =>
|
|
@@ -1197,11 +1400,12 @@ function createAutoPopulateBuilder<TMetadata>(
|
|
|
1197
1400
|
async sendRawTransaction(
|
|
1198
1401
|
args: TrackedRawTransactionParameters<TMetadata>,
|
|
1199
1402
|
): Promise<Hash> {
|
|
1200
|
-
const {metadata, serializedTransaction} = args;
|
|
1403
|
+
const {metadata, correlation, serializedTransaction} = args;
|
|
1201
1404
|
|
|
1202
1405
|
return executeTrackedRawTransaction({
|
|
1203
1406
|
serializedTransaction,
|
|
1204
1407
|
metadata: metadata as TMetadata,
|
|
1408
|
+
correlation,
|
|
1205
1409
|
execute: () =>
|
|
1206
1410
|
walletClient.sendRawTransaction({serializedTransaction}),
|
|
1207
1411
|
extractHash: (hash) => hash,
|
|
@@ -1235,7 +1439,12 @@ function createAutoPopulateBuilder<TMetadata>(
|
|
|
1235
1439
|
TChainOverride
|
|
1236
1440
|
>,
|
|
1237
1441
|
): Promise<TransactionReceipt> {
|
|
1238
|
-
const {
|
|
1442
|
+
const {
|
|
1443
|
+
metadata: userMetadata,
|
|
1444
|
+
correlation,
|
|
1445
|
+
nonce,
|
|
1446
|
+
...writeArgs
|
|
1447
|
+
} = args;
|
|
1239
1448
|
|
|
1240
1449
|
// Validate that user didn't provide operation, functionName or args
|
|
1241
1450
|
validateNoAutoPopulatedFieldsInMetadata(userMetadata);
|
|
@@ -1254,6 +1463,7 @@ function createAutoPopulateBuilder<TMetadata>(
|
|
|
1254
1463
|
account: normalizeAccount(args.account),
|
|
1255
1464
|
nonce,
|
|
1256
1465
|
metadata: finalMetadata,
|
|
1466
|
+
correlation,
|
|
1257
1467
|
restArgs: writeArgs,
|
|
1258
1468
|
intendedParams,
|
|
1259
1469
|
execute: (argsWithNonce) =>
|
|
@@ -1272,13 +1482,14 @@ function createAutoPopulateBuilder<TMetadata>(
|
|
|
1272
1482
|
TChainOverride
|
|
1273
1483
|
>,
|
|
1274
1484
|
): Promise<TransactionReceipt> {
|
|
1275
|
-
const {metadata, nonce, ...sendArgs} = args;
|
|
1485
|
+
const {metadata, correlation, nonce, ...sendArgs} = args;
|
|
1276
1486
|
const intendedParams = extractIntendedParamsFromSendTransaction(args);
|
|
1277
1487
|
|
|
1278
1488
|
return executeTrackedTransaction({
|
|
1279
1489
|
account: normalizeAccount(args.account),
|
|
1280
1490
|
nonce,
|
|
1281
1491
|
metadata: metadata as TMetadata,
|
|
1492
|
+
correlation,
|
|
1282
1493
|
restArgs: sendArgs,
|
|
1283
1494
|
intendedParams,
|
|
1284
1495
|
execute: (argsWithNonce) =>
|
|
@@ -1290,11 +1501,12 @@ function createAutoPopulateBuilder<TMetadata>(
|
|
|
1290
1501
|
async sendRawTransactionSync(
|
|
1291
1502
|
args: TrackedRawTransactionParameters<TMetadata>,
|
|
1292
1503
|
): Promise<TransactionReceipt> {
|
|
1293
|
-
const {metadata, serializedTransaction} = args;
|
|
1504
|
+
const {metadata, correlation, serializedTransaction} = args;
|
|
1294
1505
|
|
|
1295
1506
|
return executeTrackedRawTransaction({
|
|
1296
1507
|
serializedTransaction,
|
|
1297
1508
|
metadata: metadata as TMetadata,
|
|
1509
|
+
correlation,
|
|
1298
1510
|
execute: () =>
|
|
1299
1511
|
walletClient.sendRawTransactionSync({serializedTransaction}),
|
|
1300
1512
|
extractHash: (receipt) => receipt.transactionHash,
|