@etherkit/viem-tx-tracker 0.0.3 → 0.0.5

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/src/index.ts CHANGED
@@ -2,4 +2,8 @@
2
2
  export type * from './types.js';
3
3
 
4
4
  // Factory
5
- export {createTrackedWalletClient} from './TrackedWalletClient.js';
5
+ export {
6
+ createTrackedWalletClient,
7
+ type TrackedWalletClientBuilder,
8
+ type TrackedWalletClientAutoPopulateBuilder,
9
+ } from './TrackedWalletClient.js';
package/src/types.ts CHANGED
@@ -15,6 +15,53 @@ import type {
15
15
  WriteContractParameters,
16
16
  } from 'viem';
17
17
 
18
+ /**
19
+ * Metadata for contract function calls.
20
+ * Auto-populated by writeContract when populateMetadata: true.
21
+ */
22
+ export type FunctionCallMetadata = {
23
+ type: 'functionCall';
24
+ functionName: string;
25
+ args?: readonly unknown[];
26
+ };
27
+
28
+ /**
29
+ * Metadata for unknown/untyped operations.
30
+ * Used as a fallback for sendTransaction/sendRawTransaction
31
+ * when using the default PopulatedMetadata type.
32
+ */
33
+ export type UnknownTypeMetadata = {
34
+ type: 'unknown';
35
+ name: string;
36
+ data: any[];
37
+ };
38
+
39
+ /**
40
+ * Default metadata type when using populateMetadata: true.
41
+ * A discriminated union that allows either:
42
+ * - FunctionCallMetadata (auto-populated by writeContract)
43
+ * - UnknownTypeMetadata (for sendTransaction/sendRawTransaction)
44
+ *
45
+ * Users can provide their own TMetadata type that excludes 'unknown'
46
+ * if they want to enforce specific operation types.
47
+ */
48
+ export type PopulatedMetadata = FunctionCallMetadata | UnknownTypeMetadata;
49
+
50
+ /**
51
+ * Options for creating a tracked wallet client.
52
+ */
53
+ export interface CreateTrackedWalletClientOptions<
54
+ TPopulate extends boolean = false,
55
+ > {
56
+ /**
57
+ * When true, writeContract and writeContractSync automatically populate
58
+ * operation, functionName and args in the metadata from the contract call parameters.
59
+ * TMetadata must be a type where FunctionCallMetadata is assignable to it
60
+ * (e.g., PopulatedMetadata, FunctionCallMetadata, or a union including FunctionCallMetadata).
61
+ */
62
+ populateMetadata?: TPopulate;
63
+ }
64
+
18
65
  /**
19
66
  * Block tags that can be used to specify nonce fetching strategy
20
67
  */
@@ -28,47 +75,34 @@ export type BlockTag = 'latest' | 'pending' | 'earliest' | 'safe' | 'finalized';
28
75
  */
29
76
  export type NonceOption = number | BlockTag;
30
77
 
31
- export type ExpectedUpdate =
32
- | {
33
- address: `0x${string}`;
34
- event: {topics: `0x${string}`[]};
35
- }
36
- | {
37
- address: `0x${string}`;
38
- call: {data: `0x${string}`; result: `0x${string}`};
39
- };
40
-
41
78
  /**
42
- * Metadata that can be attached to a transaction for tracking purposes.
43
- * All fields are optional and extensible.
79
+ * Conditional type that makes metadata required or optional based on TMetadata.
80
+ * If TMetadata includes undefined (e.g., `MyMeta | undefined`), metadata is optional.
81
+ * Otherwise, metadata is required.
44
82
  */
45
- export interface TransactionMetadata {
46
- id?: string;
47
- name?: string;
48
- args?: any[];
49
- description?: string;
50
- expectedUpdate?: ExpectedUpdate;
51
- [key: string]: unknown;
52
- }
83
+ export type MetadataField<TMetadata> = undefined extends TMetadata
84
+ ? {metadata?: TMetadata}
85
+ : {metadata: TMetadata};
53
86
 
54
87
  /**
55
88
  * A tracked transaction record with all relevant information for tracking.
89
+ * The metadata field type matches what was provided to the TrackedWalletClient.
56
90
  */
57
- export interface TrackedTransaction<
58
- M extends TransactionMetadata = TransactionMetadata,
59
- > {
91
+ export interface TrackedTransaction<TMetadata> {
60
92
  chainId?: number;
61
93
  readonly hash: `0x${string}`;
62
94
  readonly from: `0x${string}`;
63
95
  nonce?: number;
64
96
  readonly broadcastTimestampMs: number;
65
- readonly metadata: M;
97
+ readonly metadata: TMetadata;
66
98
  }
67
99
 
68
100
  /**
69
- * Extended WriteContractParameters with optional metadata and flexible nonce.
101
+ * Extended WriteContractParameters with metadata and flexible nonce.
102
+ * Metadata is required unless TMetadata includes undefined.
70
103
  */
71
104
  export type TrackedWriteContractParameters<
105
+ TMetadata,
72
106
  TAbi extends Abi | readonly unknown[] = Abi,
73
107
  TFunctionName extends ContractFunctionName<TAbi, 'nonpayable' | 'payable'> =
74
108
  ContractFunctionName<TAbi, 'nonpayable' | 'payable'>,
@@ -92,10 +126,97 @@ export type TrackedWriteContractParameters<
92
126
  'nonce'
93
127
  > & {
94
128
  /**
95
- * Optional metadata to attach to the transaction for tracking.
129
+ * Nonce option:
130
+ * - number: exact nonce to use
131
+ * - BlockTag ('latest', 'pending', etc.): fetch nonce using this block tag
132
+ * - undefined: fetch nonce using 'pending' (default)
96
133
  */
97
- metadata?: TransactionMetadata;
134
+ nonce?: NonceOption;
135
+ } & MetadataField<TMetadata>;
98
136
 
137
+ /**
138
+ * The fields that are auto-populated by writeContract.
139
+ * These are excluded from user-provided metadata for writeContract.
140
+ */
141
+ export type AutoPopulatedFields = 'type' | 'functionName' | 'args';
142
+
143
+ /**
144
+ * Type that explicitly forbids auto-populated fields.
145
+ * Uses never to make TypeScript error when these properties are provided.
146
+ */
147
+ export type ForbiddenAutoPopulateFields = {
148
+ type?: never;
149
+ functionName?: never;
150
+ args?: never;
151
+ };
152
+
153
+ /**
154
+ * Metadata type for writeContract when auto-population is enabled.
155
+ * Excludes operation, functionName and args since they will be auto-populated,
156
+ * and explicitly forbids them to cause TypeScript errors if provided.
157
+ *
158
+ * User provides the remaining fields (e.g., purpose, priority), and the
159
+ * auto-populated fields (operation, functionName, args) are added at runtime.
160
+ */
161
+ export type WriteContractAutoPopulateMetadata<TMetadata> = Omit<
162
+ TMetadata,
163
+ AutoPopulatedFields
164
+ > &
165
+ ForbiddenAutoPopulateFields;
166
+
167
+ /**
168
+ * Metadata field for writeContract when auto-population is enabled.
169
+ * Excludes operation, functionName and args since they will be auto-populated.
170
+ *
171
+ * If the remaining fields (after removing auto-populated fields) are all optional,
172
+ * the metadata field itself becomes optional.
173
+ */
174
+ export type WriteContractAutoPopulateMetadataField<TMetadata> =
175
+ // Check if there are any remaining required keys after removing auto-populated fields
176
+ keyof Omit<TMetadata, AutoPopulatedFields> extends never
177
+ ? // No other fields - metadata is optional (can be omitted entirely)
178
+ {metadata?: WriteContractAutoPopulateMetadata<TMetadata>}
179
+ : // Check if all remaining fields are optional
180
+ Partial<Omit<TMetadata, AutoPopulatedFields>> extends Omit<
181
+ TMetadata,
182
+ AutoPopulatedFields
183
+ >
184
+ ? // All remaining fields are optional - metadata field is optional
185
+ {metadata?: WriteContractAutoPopulateMetadata<TMetadata>}
186
+ : // Some fields are required - metadata field is required
187
+ {metadata: WriteContractAutoPopulateMetadata<TMetadata>};
188
+
189
+ /**
190
+ * Extended WriteContractParameters with auto-populated metadata.
191
+ * operation, functionName and args are excluded from the metadata since they're auto-populated.
192
+ *
193
+ * User provides any additional fields required by their TMetadata (e.g., purpose, priority),
194
+ * and the auto-populated fields are merged at runtime.
195
+ */
196
+ export type TrackedWriteContractAutoPopulateParameters<
197
+ TMetadata,
198
+ TAbi extends Abi | readonly unknown[] = Abi,
199
+ TFunctionName extends ContractFunctionName<TAbi, 'nonpayable' | 'payable'> =
200
+ ContractFunctionName<TAbi, 'nonpayable' | 'payable'>,
201
+ TArgs extends ContractFunctionArgs<
202
+ TAbi,
203
+ 'nonpayable' | 'payable',
204
+ TFunctionName
205
+ > = ContractFunctionArgs<TAbi, 'nonpayable' | 'payable', TFunctionName>,
206
+ TChain extends Chain | undefined = Chain | undefined,
207
+ TAccount extends Account | undefined = Account | undefined,
208
+ TChainOverride extends Chain | undefined = Chain | undefined,
209
+ > = Omit<
210
+ WriteContractParameters<
211
+ TAbi,
212
+ TFunctionName,
213
+ TArgs,
214
+ TChain,
215
+ TAccount,
216
+ TChainOverride
217
+ >,
218
+ 'nonce'
219
+ > & {
99
220
  /**
100
221
  * Nonce option:
101
222
  * - number: exact nonce to use
@@ -103,12 +224,14 @@ export type TrackedWriteContractParameters<
103
224
  * - undefined: fetch nonce using 'pending' (default)
104
225
  */
105
226
  nonce?: NonceOption;
106
- };
227
+ } & WriteContractAutoPopulateMetadataField<TMetadata>;
107
228
 
108
229
  /**
109
- * Extended SendTransactionParameters with optional metadata and flexible nonce.
230
+ * Extended SendTransactionParameters with metadata and flexible nonce.
231
+ * Metadata is required unless TMetadata includes undefined.
110
232
  */
111
233
  export type TrackedSendTransactionParameters<
234
+ TMetadata,
112
235
  TChain extends Chain | undefined = Chain | undefined,
113
236
  TAccount extends Account | undefined = Account | undefined,
114
237
  TChainOverride extends Chain | undefined = Chain | undefined,
@@ -116,11 +239,6 @@ export type TrackedSendTransactionParameters<
116
239
  SendTransactionParameters<TChain, TAccount, TChainOverride>,
117
240
  'nonce'
118
241
  > & {
119
- /**
120
- * Optional metadata to attach to the transaction for tracking.
121
- */
122
- metadata?: TransactionMetadata;
123
-
124
242
  /**
125
243
  * Nonce option:
126
244
  * - number: exact nonce to use
@@ -128,28 +246,27 @@ export type TrackedSendTransactionParameters<
128
246
  * - undefined: fetch nonce using 'pending' (default)
129
247
  */
130
248
  nonce?: NonceOption;
131
- };
249
+ } & MetadataField<TMetadata>;
132
250
 
133
251
  /**
134
- * Parameters for sendRawTransaction with optional metadata.
252
+ * Parameters for sendRawTransaction with metadata.
135
253
  * The serialized transaction already contains from/nonce which will be decoded.
254
+ * Metadata is required unless TMetadata includes undefined.
136
255
  */
137
- export interface TrackedRawTransactionParameters {
256
+ export type TrackedRawTransactionParameters<TMetadata> = {
138
257
  /**
139
258
  * The RLP-encoded signed transaction.
140
259
  */
141
260
  serializedTransaction: TransactionSerialized;
142
-
143
- /**
144
- * Optional metadata to attach to the transaction for tracking.
145
- */
146
- metadata?: TransactionMetadata;
147
- }
261
+ } & MetadataField<TMetadata>;
148
262
 
149
263
  /**
150
264
  * A wallet client wrapper that tracks transactions with metadata.
265
+ * TMetadata is the first type parameter and is mandatory - it determines
266
+ * whether metadata is required or optional on transaction calls.
151
267
  */
152
268
  export interface TrackedWalletClient<
269
+ TMetadata,
153
270
  TTransport extends Transport = Transport,
154
271
  TChain extends Chain | undefined = Chain | undefined,
155
272
  TAccount extends Account | undefined = Account | undefined,
@@ -169,7 +286,7 @@ export interface TrackedWalletClient<
169
286
  // ============================================
170
287
 
171
288
  /**
172
- * Write to a contract with optional metadata tracking.
289
+ * Write to a contract with metadata tracking.
173
290
  * Returns immediately after broadcast with the transaction hash.
174
291
  */
175
292
  writeContract<
@@ -183,6 +300,7 @@ export interface TrackedWalletClient<
183
300
  TChainOverride extends Chain | undefined = undefined,
184
301
  >(
185
302
  args: TrackedWriteContractParameters<
303
+ TMetadata,
186
304
  TAbi,
187
305
  TFunctionName,
188
306
  TArgs,
@@ -193,19 +311,26 @@ export interface TrackedWalletClient<
193
311
  ): Promise<Hash>;
194
312
 
195
313
  /**
196
- * Send a transaction with optional metadata tracking.
314
+ * Send a transaction with metadata tracking.
197
315
  * Returns immediately after broadcast with the transaction hash.
198
316
  */
199
317
  sendTransaction<TChainOverride extends Chain | undefined = undefined>(
200
- args: TrackedSendTransactionParameters<TChain, TAccount, TChainOverride>,
318
+ args: TrackedSendTransactionParameters<
319
+ TMetadata,
320
+ TChain,
321
+ TAccount,
322
+ TChainOverride
323
+ >,
201
324
  ): Promise<Hash>;
202
325
 
203
326
  /**
204
- * Send a signed raw transaction with optional metadata tracking.
327
+ * Send a signed raw transaction with metadata tracking.
205
328
  * The nonce and from address are decoded from the serialized transaction.
206
329
  * Returns immediately after broadcast with the transaction hash.
207
330
  */
208
- sendRawTransaction(args: TrackedRawTransactionParameters): Promise<Hash>;
331
+ sendRawTransaction(
332
+ args: TrackedRawTransactionParameters<TMetadata>,
333
+ ): Promise<Hash>;
209
334
 
210
335
  // ============================================
211
336
  // Sync methods (wait for confirmation, return receipt)
@@ -226,6 +351,154 @@ export interface TrackedWalletClient<
226
351
  TChainOverride extends Chain | undefined = undefined,
227
352
  >(
228
353
  args: TrackedWriteContractParameters<
354
+ TMetadata,
355
+ TAbi,
356
+ TFunctionName,
357
+ TArgs,
358
+ TChain,
359
+ TAccount,
360
+ TChainOverride
361
+ >,
362
+ ): Promise<TransactionReceipt>;
363
+
364
+ /**
365
+ * Send a transaction and wait for confirmation.
366
+ * Returns the transaction receipt after the transaction is confirmed.
367
+ */
368
+ sendTransactionSync<TChainOverride extends Chain | undefined = undefined>(
369
+ args: TrackedSendTransactionParameters<
370
+ TMetadata,
371
+ TChain,
372
+ TAccount,
373
+ TChainOverride
374
+ >,
375
+ ): Promise<TransactionReceipt>;
376
+
377
+ /**
378
+ * Send a signed raw transaction and wait for confirmation.
379
+ * Returns the transaction receipt after the transaction is confirmed.
380
+ */
381
+ sendRawTransactionSync(
382
+ args: TrackedRawTransactionParameters<TMetadata>,
383
+ ): Promise<TransactionReceipt>;
384
+
385
+ // ============================================
386
+ // Event subscription methods
387
+ // ============================================
388
+
389
+ /**
390
+ * Subscribe to transaction broadcast events.
391
+ * Called immediately after a transaction is successfully broadcast.
392
+ * @param listener - Callback function receiving TrackedTransaction with TMetadata
393
+ * @returns Unsubscribe function
394
+ */
395
+ onTransactionBroadcasted(
396
+ listener: (event: TrackedTransaction<TMetadata>) => void,
397
+ ): () => void;
398
+
399
+ /**
400
+ * Unsubscribe from transaction broadcast events.
401
+ * @param listener - The same listener function passed to onTransactionBroadcasted
402
+ */
403
+ offTransactionBroadcasted(
404
+ listener: (event: TrackedTransaction<TMetadata>) => void,
405
+ ): void;
406
+ }
407
+
408
+ /**
409
+ * A wallet client wrapper that tracks transactions with auto-populated metadata.
410
+ * TMetadata must be a type where FunctionCallMetadata is assignable to it.
411
+ * writeContract and writeContractSync automatically populate operation, functionName and args.
412
+ */
413
+ export interface TrackedWalletClientAutoPopulate<
414
+ TMetadata,
415
+ TTransport extends Transport = Transport,
416
+ TChain extends Chain | undefined = Chain | undefined,
417
+ TAccount extends Account | undefined = Account | undefined,
418
+ > {
419
+ /**
420
+ * The underlying wallet client.
421
+ */
422
+ readonly walletClient: WalletClient<TTransport, TChain, TAccount>;
423
+
424
+ /**
425
+ * The public client used for nonce fetching and tx verification.
426
+ */
427
+ readonly publicClient: PublicClient;
428
+
429
+ // ============================================
430
+ // Async methods (return hash immediately after broadcast)
431
+ // ============================================
432
+
433
+ /**
434
+ * Write to a contract with metadata tracking.
435
+ * functionName and args are automatically populated from the contract call.
436
+ * Returns immediately after broadcast with the transaction hash.
437
+ */
438
+ writeContract<
439
+ const TAbi extends Abi | readonly unknown[],
440
+ TFunctionName extends ContractFunctionName<TAbi, 'nonpayable' | 'payable'>,
441
+ TArgs extends ContractFunctionArgs<
442
+ TAbi,
443
+ 'nonpayable' | 'payable',
444
+ TFunctionName
445
+ >,
446
+ TChainOverride extends Chain | undefined = undefined,
447
+ >(
448
+ args: TrackedWriteContractAutoPopulateParameters<
449
+ TMetadata,
450
+ TAbi,
451
+ TFunctionName,
452
+ TArgs,
453
+ TChain,
454
+ TAccount,
455
+ TChainOverride
456
+ >,
457
+ ): Promise<Hash>;
458
+
459
+ /**
460
+ * Send a transaction with metadata tracking.
461
+ * Returns immediately after broadcast with the transaction hash.
462
+ */
463
+ sendTransaction<TChainOverride extends Chain | undefined = undefined>(
464
+ args: TrackedSendTransactionParameters<
465
+ TMetadata,
466
+ TChain,
467
+ TAccount,
468
+ TChainOverride
469
+ >,
470
+ ): Promise<Hash>;
471
+
472
+ /**
473
+ * Send a signed raw transaction with metadata tracking.
474
+ * The nonce and from address are decoded from the serialized transaction.
475
+ * Returns immediately after broadcast with the transaction hash.
476
+ */
477
+ sendRawTransaction(
478
+ args: TrackedRawTransactionParameters<TMetadata>,
479
+ ): Promise<Hash>;
480
+
481
+ // ============================================
482
+ // Sync methods (wait for confirmation, return receipt)
483
+ // ============================================
484
+
485
+ /**
486
+ * Write to a contract and wait for confirmation.
487
+ * functionName and args are automatically populated from the contract call.
488
+ * Returns the transaction receipt after the transaction is confirmed.
489
+ */
490
+ writeContractSync<
491
+ const TAbi extends Abi | readonly unknown[],
492
+ TFunctionName extends ContractFunctionName<TAbi, 'nonpayable' | 'payable'>,
493
+ TArgs extends ContractFunctionArgs<
494
+ TAbi,
495
+ 'nonpayable' | 'payable',
496
+ TFunctionName
497
+ >,
498
+ TChainOverride extends Chain | undefined = undefined,
499
+ >(
500
+ args: TrackedWriteContractAutoPopulateParameters<
501
+ TMetadata,
229
502
  TAbi,
230
503
  TFunctionName,
231
504
  TArgs,
@@ -240,7 +513,12 @@ export interface TrackedWalletClient<
240
513
  * Returns the transaction receipt after the transaction is confirmed.
241
514
  */
242
515
  sendTransactionSync<TChainOverride extends Chain | undefined = undefined>(
243
- args: TrackedSendTransactionParameters<TChain, TAccount, TChainOverride>,
516
+ args: TrackedSendTransactionParameters<
517
+ TMetadata,
518
+ TChain,
519
+ TAccount,
520
+ TChainOverride
521
+ >,
244
522
  ): Promise<TransactionReceipt>;
245
523
 
246
524
  /**
@@ -248,7 +526,7 @@ export interface TrackedWalletClient<
248
526
  * Returns the transaction receipt after the transaction is confirmed.
249
527
  */
250
528
  sendRawTransactionSync(
251
- args: TrackedRawTransactionParameters,
529
+ args: TrackedRawTransactionParameters<TMetadata>,
252
530
  ): Promise<TransactionReceipt>;
253
531
 
254
532
  // ============================================
@@ -258,11 +536,11 @@ export interface TrackedWalletClient<
258
536
  /**
259
537
  * Subscribe to transaction broadcast events.
260
538
  * Called immediately after a transaction is successfully broadcast.
261
- * @param listener - Callback function receiving TrackedTransaction
539
+ * @param listener - Callback function receiving TrackedTransaction with TMetadata
262
540
  * @returns Unsubscribe function
263
541
  */
264
542
  onTransactionBroadcasted(
265
- listener: (event: TrackedTransaction) => void,
543
+ listener: (event: TrackedTransaction<TMetadata>) => void,
266
544
  ): () => void;
267
545
 
268
546
  /**
@@ -270,6 +548,6 @@ export interface TrackedWalletClient<
270
548
  * @param listener - The same listener function passed to onTransactionBroadcasted
271
549
  */
272
550
  offTransactionBroadcasted(
273
- listener: (event: TrackedTransaction) => void,
551
+ listener: (event: TrackedTransaction<TMetadata>) => void,
274
552
  ): void;
275
553
  }