@etherkit/viem-tx-tracker 0.0.4 → 0.0.6
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/dist/TrackedWalletClient.d.ts +34 -3
- package/dist/TrackedWalletClient.d.ts.map +1 -1
- package/dist/TrackedWalletClient.js +433 -97
- package/dist/TrackedWalletClient.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +257 -38
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/TrackedWalletClient.ts +785 -112
- package/src/index.ts +1 -0
- package/src/types.ts +407 -38
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseTransaction,
|
|
3
|
+
ParseTransactionReturnType,
|
|
3
4
|
recoverTransactionAddress,
|
|
4
5
|
type Abi,
|
|
5
6
|
type Account,
|
|
@@ -9,6 +10,7 @@ import {
|
|
|
9
10
|
type ContractFunctionName,
|
|
10
11
|
type Hash,
|
|
11
12
|
type PublicClient,
|
|
13
|
+
type Transaction,
|
|
12
14
|
type TransactionReceipt,
|
|
13
15
|
type TransactionSerialized,
|
|
14
16
|
type Transport,
|
|
@@ -16,14 +18,20 @@ import {
|
|
|
16
18
|
} from 'viem';
|
|
17
19
|
import {Emitter} from 'radiate';
|
|
18
20
|
import type {
|
|
21
|
+
AccessList,
|
|
19
22
|
BlockTag,
|
|
20
|
-
|
|
23
|
+
CreateTrackedWalletClientOptions,
|
|
24
|
+
KnownTrackedTransaction,
|
|
21
25
|
NonceOption,
|
|
26
|
+
PopulatedMetadata,
|
|
22
27
|
TrackedRawTransactionParameters,
|
|
23
28
|
TrackedSendTransactionParameters,
|
|
24
29
|
TrackedTransaction,
|
|
25
30
|
TrackedWalletClient,
|
|
31
|
+
TrackedWalletClientAutoPopulate,
|
|
32
|
+
TrackedWriteContractAutoPopulateParameters,
|
|
26
33
|
TrackedWriteContractParameters,
|
|
34
|
+
UnknownTrackedTransaction,
|
|
27
35
|
} from './types.js';
|
|
28
36
|
|
|
29
37
|
/**
|
|
@@ -56,13 +64,6 @@ function normalizeAccount(
|
|
|
56
64
|
return account === null ? undefined : account;
|
|
57
65
|
}
|
|
58
66
|
|
|
59
|
-
/**
|
|
60
|
-
* Generate a unique tracking ID if not provided in metadata
|
|
61
|
-
*/
|
|
62
|
-
function generateTrackingId(): string {
|
|
63
|
-
return crypto.randomUUID();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
67
|
/**
|
|
67
68
|
* Context for transaction tracking - common data extracted from request
|
|
68
69
|
*/
|
|
@@ -71,6 +72,253 @@ interface TransactionContext {
|
|
|
71
72
|
intendedNonce: number;
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Parameters we can extract from writeContract/sendTransaction calls.
|
|
77
|
+
* These are the intended values - wallet may modify them.
|
|
78
|
+
*/
|
|
79
|
+
interface IntendedTransactionParams {
|
|
80
|
+
to?: Address | null;
|
|
81
|
+
value?: bigint;
|
|
82
|
+
data?: `0x${string}`;
|
|
83
|
+
gas?: bigint;
|
|
84
|
+
gasPrice?: bigint;
|
|
85
|
+
maxFeePerGas?: bigint;
|
|
86
|
+
maxPriorityFeePerGas?: bigint;
|
|
87
|
+
accessList?: AccessList;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Infer transaction type from provided params.
|
|
92
|
+
* Returns undefined if can't be determined (wallet will decide).
|
|
93
|
+
*/
|
|
94
|
+
function inferTxType(
|
|
95
|
+
params: IntendedTransactionParams,
|
|
96
|
+
): 'eip1559' | 'legacy' | 'eip2930' | undefined {
|
|
97
|
+
if (params.maxFeePerGas !== undefined) {
|
|
98
|
+
return 'eip1559';
|
|
99
|
+
}
|
|
100
|
+
if (params.gasPrice !== undefined && params.accessList !== undefined) {
|
|
101
|
+
return 'eip2930';
|
|
102
|
+
}
|
|
103
|
+
if (params.gasPrice !== undefined) {
|
|
104
|
+
return 'legacy';
|
|
105
|
+
}
|
|
106
|
+
return undefined; // Wallet will determine
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Create an UnknownTrackedTransaction for immediate emission.
|
|
111
|
+
* Populates all known intended values from the transaction parameters.
|
|
112
|
+
*/
|
|
113
|
+
function createUnknownTrackedTransaction<TMetadata>(
|
|
114
|
+
hash: Hash,
|
|
115
|
+
from: Address,
|
|
116
|
+
nonce: number | undefined,
|
|
117
|
+
chainId: number | undefined,
|
|
118
|
+
metadata: TMetadata,
|
|
119
|
+
broadcastTimestampMs: number,
|
|
120
|
+
params: IntendedTransactionParams,
|
|
121
|
+
): UnknownTrackedTransaction<TMetadata> {
|
|
122
|
+
const txType = inferTxType(params);
|
|
123
|
+
return {
|
|
124
|
+
known: false,
|
|
125
|
+
chainId,
|
|
126
|
+
hash,
|
|
127
|
+
from,
|
|
128
|
+
nonce,
|
|
129
|
+
broadcastTimestampMs,
|
|
130
|
+
metadata,
|
|
131
|
+
...(txType !== undefined && {txType}),
|
|
132
|
+
...(params.to !== undefined && {to: params.to}),
|
|
133
|
+
...(params.value !== undefined && {value: params.value}),
|
|
134
|
+
...(params.data !== undefined && {data: params.data}),
|
|
135
|
+
...(params.gas !== undefined && {gas: params.gas}),
|
|
136
|
+
...(params.gasPrice !== undefined && {gasPrice: params.gasPrice}),
|
|
137
|
+
...(params.maxFeePerGas !== undefined && {
|
|
138
|
+
maxFeePerGas: params.maxFeePerGas,
|
|
139
|
+
}),
|
|
140
|
+
...(params.maxPriorityFeePerGas !== undefined && {
|
|
141
|
+
maxPriorityFeePerGas: params.maxPriorityFeePerGas,
|
|
142
|
+
}),
|
|
143
|
+
...(params.accessList !== undefined && {accessList: params.accessList}),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Extract transaction type-specific fields from a fetched transaction.
|
|
149
|
+
*/
|
|
150
|
+
function extractTransactionTypeFields(tx: Transaction): {
|
|
151
|
+
txType: 'eip1559' | 'legacy' | 'eip2930';
|
|
152
|
+
gasPrice?: bigint;
|
|
153
|
+
maxFeePerGas?: bigint;
|
|
154
|
+
maxPriorityFeePerGas?: bigint;
|
|
155
|
+
accessList?: AccessList;
|
|
156
|
+
} {
|
|
157
|
+
if (tx.type === 'eip1559') {
|
|
158
|
+
return {
|
|
159
|
+
txType: 'eip1559',
|
|
160
|
+
maxFeePerGas: tx.maxFeePerGas!,
|
|
161
|
+
maxPriorityFeePerGas: tx.maxPriorityFeePerGas!,
|
|
162
|
+
...(tx.accessList && {accessList: tx.accessList as AccessList}),
|
|
163
|
+
};
|
|
164
|
+
} else if (tx.type === 'eip2930') {
|
|
165
|
+
return {
|
|
166
|
+
txType: 'eip2930',
|
|
167
|
+
gasPrice: tx.gasPrice!,
|
|
168
|
+
accessList: (tx.accessList ?? []) as AccessList,
|
|
169
|
+
};
|
|
170
|
+
} else {
|
|
171
|
+
// Legacy or unknown - treat as legacy
|
|
172
|
+
return {
|
|
173
|
+
txType: 'legacy',
|
|
174
|
+
gasPrice: tx.gasPrice!,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Create a KnownTrackedTransaction from a fetched transaction.
|
|
181
|
+
*/
|
|
182
|
+
function createKnownTrackedTransaction<TMetadata>(
|
|
183
|
+
tx: Transaction,
|
|
184
|
+
metadata: TMetadata,
|
|
185
|
+
broadcastTimestampMs: number,
|
|
186
|
+
): KnownTrackedTransaction<TMetadata> {
|
|
187
|
+
const typeFields = extractTransactionTypeFields(tx);
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
known: true,
|
|
191
|
+
chainId: tx.chainId!,
|
|
192
|
+
hash: tx.hash,
|
|
193
|
+
from: tx.from,
|
|
194
|
+
to: tx.to,
|
|
195
|
+
nonce: tx.nonce,
|
|
196
|
+
value: tx.value,
|
|
197
|
+
data: tx.input,
|
|
198
|
+
gas: tx.gas,
|
|
199
|
+
broadcastTimestampMs,
|
|
200
|
+
metadata,
|
|
201
|
+
...typeFields,
|
|
202
|
+
} as KnownTrackedTransaction<TMetadata>;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Create a KnownTrackedTransaction from a parsed raw transaction.
|
|
207
|
+
*/
|
|
208
|
+
function createKnownTrackedTransactionFromRaw<TMetadata>(
|
|
209
|
+
parsedTx: ParseTransactionReturnType<`0x${string}`>,
|
|
210
|
+
from: `0x${string}`,
|
|
211
|
+
hash: Hash,
|
|
212
|
+
metadata: TMetadata,
|
|
213
|
+
chainId: number | undefined,
|
|
214
|
+
broadcastTimestampMs: number,
|
|
215
|
+
): KnownTrackedTransaction<TMetadata> {
|
|
216
|
+
// Determine transaction type from parsed tx
|
|
217
|
+
let typeFields: {
|
|
218
|
+
txType: 'eip1559' | 'legacy' | 'eip2930';
|
|
219
|
+
gasPrice?: bigint;
|
|
220
|
+
maxFeePerGas?: bigint;
|
|
221
|
+
maxPriorityFeePerGas?: bigint;
|
|
222
|
+
accessList?: AccessList;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
if ('maxFeePerGas' in parsedTx && parsedTx.maxFeePerGas !== undefined) {
|
|
226
|
+
typeFields = {
|
|
227
|
+
txType: 'eip1559',
|
|
228
|
+
maxFeePerGas: parsedTx.maxFeePerGas,
|
|
229
|
+
maxPriorityFeePerGas: parsedTx.maxPriorityFeePerGas!,
|
|
230
|
+
...('accessList' in parsedTx &&
|
|
231
|
+
parsedTx.accessList && {
|
|
232
|
+
accessList: parsedTx.accessList as AccessList,
|
|
233
|
+
}),
|
|
234
|
+
};
|
|
235
|
+
} else if ('accessList' in parsedTx && parsedTx.accessList) {
|
|
236
|
+
typeFields = {
|
|
237
|
+
txType: 'eip2930',
|
|
238
|
+
gasPrice: parsedTx.gasPrice!,
|
|
239
|
+
accessList: parsedTx.accessList as AccessList,
|
|
240
|
+
};
|
|
241
|
+
} else {
|
|
242
|
+
typeFields = {
|
|
243
|
+
txType: 'legacy',
|
|
244
|
+
gasPrice: parsedTx.gasPrice!,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
known: true,
|
|
250
|
+
chainId: parsedTx.chainId ?? chainId!,
|
|
251
|
+
hash,
|
|
252
|
+
from,
|
|
253
|
+
to: parsedTx.to ?? null,
|
|
254
|
+
nonce: parsedTx.nonce!,
|
|
255
|
+
value: parsedTx.value ?? 0n,
|
|
256
|
+
data: parsedTx.data ?? '0x',
|
|
257
|
+
gas: parsedTx.gas!,
|
|
258
|
+
broadcastTimestampMs,
|
|
259
|
+
metadata,
|
|
260
|
+
...typeFields,
|
|
261
|
+
} as KnownTrackedTransaction<TMetadata>;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Extract intended params from sendTransaction args.
|
|
266
|
+
* Uses 'unknown' for 'to' since viem's SendTransactionParameters has a complex type for it.
|
|
267
|
+
*/
|
|
268
|
+
function extractIntendedParamsFromSendTransaction(args: {
|
|
269
|
+
to?: unknown;
|
|
270
|
+
value?: bigint;
|
|
271
|
+
data?: `0x${string}`;
|
|
272
|
+
gas?: bigint;
|
|
273
|
+
gasPrice?: bigint;
|
|
274
|
+
maxFeePerGas?: bigint;
|
|
275
|
+
maxPriorityFeePerGas?: bigint;
|
|
276
|
+
accessList?: AccessList;
|
|
277
|
+
}): IntendedTransactionParams {
|
|
278
|
+
// Normalize 'to' to either a hex address, null, or undefined
|
|
279
|
+
const to =
|
|
280
|
+
args.to === null || args.to === undefined
|
|
281
|
+
? (args.to as null | undefined)
|
|
282
|
+
: typeof args.to === 'string'
|
|
283
|
+
? (args.to as Address)
|
|
284
|
+
: undefined;
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
to,
|
|
288
|
+
value: args.value ?? 0n,
|
|
289
|
+
data: args.data,
|
|
290
|
+
gas: args.gas,
|
|
291
|
+
gasPrice: args.gasPrice,
|
|
292
|
+
maxFeePerGas: args.maxFeePerGas,
|
|
293
|
+
maxPriorityFeePerGas: args.maxPriorityFeePerGas,
|
|
294
|
+
accessList: args.accessList,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Extract intended params from writeContract args.
|
|
300
|
+
*/
|
|
301
|
+
function extractIntendedParamsFromWriteContract(args: {
|
|
302
|
+
address: Address;
|
|
303
|
+
value?: bigint;
|
|
304
|
+
gas?: bigint;
|
|
305
|
+
gasPrice?: bigint;
|
|
306
|
+
maxFeePerGas?: bigint;
|
|
307
|
+
maxPriorityFeePerGas?: bigint;
|
|
308
|
+
accessList?: AccessList;
|
|
309
|
+
}): IntendedTransactionParams {
|
|
310
|
+
return {
|
|
311
|
+
to: args.address,
|
|
312
|
+
value: args.value ?? 0n,
|
|
313
|
+
// Note: data could be encoded using encodeFunctionData(args) if desired
|
|
314
|
+
gas: args.gas,
|
|
315
|
+
gasPrice: args.gasPrice,
|
|
316
|
+
maxFeePerGas: args.maxFeePerGas,
|
|
317
|
+
maxPriorityFeePerGas: args.maxPriorityFeePerGas,
|
|
318
|
+
accessList: args.accessList,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
74
322
|
/**
|
|
75
323
|
* Infer transport type from WalletClient
|
|
76
324
|
*/
|
|
@@ -113,6 +361,31 @@ export interface TrackedWalletClientBuilder<TMetadata> {
|
|
|
113
361
|
>;
|
|
114
362
|
}
|
|
115
363
|
|
|
364
|
+
/**
|
|
365
|
+
* Builder interface returned by createTrackedWalletClient with populateMetadata: true.
|
|
366
|
+
* This builder returns a TrackedWalletClientAutoPopulate that auto-populates operation, functionName and args.
|
|
367
|
+
* TMetadata must be a type where FunctionCallMetadata is assignable to it.
|
|
368
|
+
*/
|
|
369
|
+
export interface TrackedWalletClientAutoPopulateBuilder<TMetadata> {
|
|
370
|
+
/**
|
|
371
|
+
* Create the tracked wallet client using the provided wallet and public clients.
|
|
372
|
+
* writeContract and writeContractSync will automatically populate operation, functionName and args.
|
|
373
|
+
*
|
|
374
|
+
* @param walletClient - The underlying viem WalletClient
|
|
375
|
+
* @param publicClient - A PublicClient for nonce fetching and tx verification
|
|
376
|
+
* @returns A TrackedWalletClientAutoPopulate instance
|
|
377
|
+
*/
|
|
378
|
+
using<TClient extends WalletClient>(
|
|
379
|
+
walletClient: TClient,
|
|
380
|
+
publicClient: PublicClient,
|
|
381
|
+
): TrackedWalletClientAutoPopulate<
|
|
382
|
+
TMetadata,
|
|
383
|
+
InferTransport<TClient>,
|
|
384
|
+
InferChain<TClient>,
|
|
385
|
+
InferAccount<TClient>
|
|
386
|
+
>;
|
|
387
|
+
}
|
|
388
|
+
|
|
116
389
|
/**
|
|
117
390
|
* Create a tracked wallet client that wraps a viem WalletClient.
|
|
118
391
|
*
|
|
@@ -127,18 +400,54 @@ export interface TrackedWalletClientBuilder<TMetadata> {
|
|
|
127
400
|
*
|
|
128
401
|
* @example
|
|
129
402
|
* ```typescript
|
|
130
|
-
* //
|
|
403
|
+
* // Standard mode with required metadata
|
|
131
404
|
* const tracked = createTrackedWalletClient<{purpose: string}>()
|
|
132
405
|
* .using(walletClient, publicClient);
|
|
133
406
|
*
|
|
134
|
-
* //
|
|
407
|
+
* // Standard mode with optional metadata
|
|
135
408
|
* const tracked = createTrackedWalletClient<{purpose: string} | undefined>()
|
|
136
409
|
* .using(walletClient, publicClient);
|
|
410
|
+
*
|
|
411
|
+
* // Auto-populate mode - functionName and args are auto-populated
|
|
412
|
+
* const tracked = createTrackedWalletClient({ populateMetadata: true })
|
|
413
|
+
* .using(walletClient, publicClient);
|
|
414
|
+
*
|
|
415
|
+
* // Auto-populate mode with extended metadata
|
|
416
|
+
* type MyMetadata = OperationMetadata & { purpose: string };
|
|
417
|
+
* const tracked = createTrackedWalletClient<MyMetadata>({ populateMetadata: true })
|
|
418
|
+
* .using(walletClient, publicClient);
|
|
137
419
|
* ```
|
|
138
420
|
*/
|
|
421
|
+
// Overload 1: Standard mode, no options
|
|
139
422
|
export function createTrackedWalletClient<
|
|
140
423
|
TMetadata,
|
|
141
|
-
>(): TrackedWalletClientBuilder<TMetadata
|
|
424
|
+
>(): TrackedWalletClientBuilder<TMetadata>;
|
|
425
|
+
|
|
426
|
+
// Overload 2: Auto-populate mode with default PopulatedMetadata
|
|
427
|
+
export function createTrackedWalletClient(options: {
|
|
428
|
+
populateMetadata: true;
|
|
429
|
+
}): TrackedWalletClientAutoPopulateBuilder<PopulatedMetadata>;
|
|
430
|
+
|
|
431
|
+
// Overload 3: Auto-populate mode with custom metadata (must allow FunctionCallMetadata)
|
|
432
|
+
export function createTrackedWalletClient<TMetadata>(options: {
|
|
433
|
+
populateMetadata: true;
|
|
434
|
+
}): TrackedWalletClientAutoPopulateBuilder<TMetadata>;
|
|
435
|
+
|
|
436
|
+
// Implementation
|
|
437
|
+
export function createTrackedWalletClient<TMetadata>(
|
|
438
|
+
options?: CreateTrackedWalletClientOptions<boolean>,
|
|
439
|
+
):
|
|
440
|
+
| TrackedWalletClientBuilder<TMetadata>
|
|
441
|
+
| TrackedWalletClientAutoPopulateBuilder<TMetadata> {
|
|
442
|
+
const populateMetadata = options?.populateMetadata ?? false;
|
|
443
|
+
const clock = options?.clock ?? Date.now;
|
|
444
|
+
|
|
445
|
+
if (populateMetadata) {
|
|
446
|
+
return createAutoPopulateBuilder<TMetadata>(
|
|
447
|
+
clock,
|
|
448
|
+
) as TrackedWalletClientAutoPopulateBuilder<TMetadata>;
|
|
449
|
+
}
|
|
450
|
+
|
|
142
451
|
return {
|
|
143
452
|
using<TClient extends WalletClient>(
|
|
144
453
|
walletClient: TClient,
|
|
@@ -154,9 +463,10 @@ export function createTrackedWalletClient<
|
|
|
154
463
|
type TChain = InferChain<TClient>;
|
|
155
464
|
type TAccount = InferAccount<TClient>;
|
|
156
465
|
|
|
157
|
-
// Create emitter for transaction
|
|
466
|
+
// Create emitter for transaction events
|
|
158
467
|
const emitter = new Emitter<{
|
|
159
468
|
'transaction:broadcasted': TrackedTransaction<TMetadata>;
|
|
469
|
+
'transaction:fetched': KnownTrackedTransaction<TMetadata>;
|
|
160
470
|
}>();
|
|
161
471
|
|
|
162
472
|
/**
|
|
@@ -212,106 +522,440 @@ export function createTrackedWalletClient<
|
|
|
212
522
|
}
|
|
213
523
|
|
|
214
524
|
/**
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
* @param serializedTransaction - The RLP-encoded signed transaction
|
|
219
|
-
* @returns TransactionContext with from address and nonce
|
|
525
|
+
* Fetch full transaction data and emit transaction:fetched event.
|
|
526
|
+
* Non-blocking, runs in background. Does not throw.
|
|
220
527
|
*/
|
|
221
|
-
async function
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
528
|
+
async function fetchAndEmitFullData(
|
|
529
|
+
hash: Hash,
|
|
530
|
+
metadata: TMetadata,
|
|
531
|
+
broadcastTimestampMs: number,
|
|
532
|
+
): Promise<void> {
|
|
533
|
+
try {
|
|
534
|
+
const tx = await publicClient.getTransaction({hash});
|
|
535
|
+
const knownTx = createKnownTrackedTransaction(
|
|
536
|
+
tx,
|
|
537
|
+
metadata,
|
|
538
|
+
broadcastTimestampMs,
|
|
539
|
+
);
|
|
540
|
+
emitter.emit('transaction:fetched', knownTx);
|
|
541
|
+
} catch (error) {
|
|
542
|
+
// Log but don't throw - transaction:fetched simply won't fire
|
|
543
|
+
console.warn(
|
|
544
|
+
`[TrackedWalletClient] Could not fetch tx ${hash}. ` +
|
|
545
|
+
`transaction:fetched event will not be emitted. Error: ${error}`,
|
|
230
546
|
);
|
|
231
547
|
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Common wrapper for transaction methods that broadcast (sendTransaction, writeContract).
|
|
552
|
+
* Emits transaction:broadcasted immediately with intended values,
|
|
553
|
+
* then fetches and emits transaction:fetched with actual values.
|
|
554
|
+
*/
|
|
555
|
+
async function executeTrackedTransaction<T, R>(args: {
|
|
556
|
+
account?: Account | Address;
|
|
557
|
+
nonce?: NonceOption;
|
|
558
|
+
metadata: TMetadata;
|
|
559
|
+
restArgs: T;
|
|
560
|
+
intendedParams: IntendedTransactionParams;
|
|
561
|
+
execute: (argsWithNonce: T & {nonce: number}) => Promise<R>;
|
|
562
|
+
extractHash: (result: R) => Hash;
|
|
563
|
+
}): Promise<R> {
|
|
564
|
+
const {metadata, restArgs, intendedParams, execute, extractHash} = args;
|
|
565
|
+
const broadcastTimestampMs = clock();
|
|
232
566
|
|
|
233
|
-
//
|
|
234
|
-
const from = await
|
|
235
|
-
|
|
567
|
+
// Extract common context
|
|
568
|
+
const {from, intendedNonce} = await extractTransactionContext(args);
|
|
569
|
+
|
|
570
|
+
// Execute the underlying transaction with nonce injected
|
|
571
|
+
const result = await execute({
|
|
572
|
+
...restArgs,
|
|
573
|
+
nonce: intendedNonce,
|
|
574
|
+
} as T & {
|
|
575
|
+
nonce: number;
|
|
236
576
|
});
|
|
577
|
+
const hash = extractHash(result);
|
|
237
578
|
|
|
238
|
-
|
|
579
|
+
// Emit transaction:broadcasted immediately with intended values
|
|
580
|
+
const unknownTx = createUnknownTrackedTransaction(
|
|
581
|
+
hash,
|
|
239
582
|
from,
|
|
240
|
-
intendedNonce
|
|
241
|
-
|
|
583
|
+
intendedNonce,
|
|
584
|
+
walletClient.chain?.id,
|
|
585
|
+
metadata,
|
|
586
|
+
broadcastTimestampMs,
|
|
587
|
+
intendedParams,
|
|
588
|
+
);
|
|
589
|
+
emitter.emit('transaction:broadcasted', unknownTx);
|
|
590
|
+
|
|
591
|
+
// Fire-and-forget: fetch full data and emit transaction:fetched
|
|
592
|
+
fetchAndEmitFullData(hash, metadata, broadcastTimestampMs);
|
|
593
|
+
|
|
594
|
+
return result;
|
|
242
595
|
}
|
|
243
596
|
|
|
244
597
|
/**
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
* @param hash - The transaction hash
|
|
249
|
-
* @param intendedNonce - The nonce we intended to use
|
|
250
|
-
* @returns The actual nonce, or the intended nonce if fetch failed
|
|
598
|
+
* Common wrapper for raw transaction broadcasts (sendRawTransaction).
|
|
599
|
+
* For raw transactions, we can parse full data immediately.
|
|
600
|
+
* Emits KnownTrackedTransaction directly to transaction:broadcasted.
|
|
251
601
|
*/
|
|
252
|
-
async function
|
|
253
|
-
|
|
254
|
-
|
|
602
|
+
async function executeTrackedRawTransaction<R>(args: {
|
|
603
|
+
serializedTransaction: TransactionSerialized;
|
|
604
|
+
metadata: TMetadata;
|
|
605
|
+
execute: () => Promise<R>;
|
|
606
|
+
extractHash: (result: R) => Hash;
|
|
607
|
+
}): Promise<R> {
|
|
608
|
+
const {serializedTransaction, metadata, execute, extractHash} = args;
|
|
609
|
+
const broadcastTimestampMs = clock();
|
|
610
|
+
|
|
611
|
+
const from = await recoverTransactionAddress({serializedTransaction});
|
|
612
|
+
|
|
613
|
+
const parsedTx = parseTransaction(serializedTransaction);
|
|
614
|
+
|
|
615
|
+
// Execute the broadcast
|
|
616
|
+
const result = await execute();
|
|
617
|
+
const hash = extractHash(result);
|
|
618
|
+
|
|
619
|
+
// For raw transactions, we can parse full data immediately
|
|
620
|
+
const knownTx = createKnownTrackedTransactionFromRaw(
|
|
621
|
+
parsedTx,
|
|
622
|
+
from,
|
|
623
|
+
hash,
|
|
624
|
+
metadata,
|
|
625
|
+
walletClient.chain?.id,
|
|
626
|
+
broadcastTimestampMs,
|
|
627
|
+
);
|
|
628
|
+
|
|
629
|
+
// Emit as KnownTrackedTransaction since we have all data
|
|
630
|
+
emitter.emit('transaction:broadcasted', knownTx);
|
|
631
|
+
|
|
632
|
+
// Also emit to transaction:fetched for consistency
|
|
633
|
+
emitter.emit('transaction:fetched', knownTx);
|
|
634
|
+
|
|
635
|
+
return result;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
return {
|
|
639
|
+
walletClient: walletClient as unknown as WalletClient<
|
|
640
|
+
TTransport,
|
|
641
|
+
TChain,
|
|
642
|
+
TAccount
|
|
643
|
+
>,
|
|
644
|
+
publicClient,
|
|
645
|
+
|
|
646
|
+
// ============================================
|
|
647
|
+
// Async methods (return hash)
|
|
648
|
+
// ============================================
|
|
649
|
+
|
|
650
|
+
async writeContract<
|
|
651
|
+
const TAbi extends Abi | readonly unknown[],
|
|
652
|
+
TFunctionName extends ContractFunctionName<
|
|
653
|
+
TAbi,
|
|
654
|
+
'nonpayable' | 'payable'
|
|
655
|
+
>,
|
|
656
|
+
TArgs extends ContractFunctionArgs<
|
|
657
|
+
TAbi,
|
|
658
|
+
'nonpayable' | 'payable',
|
|
659
|
+
TFunctionName
|
|
660
|
+
>,
|
|
661
|
+
TChainOverride extends Chain | undefined = undefined,
|
|
662
|
+
>(
|
|
663
|
+
args: TrackedWriteContractParameters<
|
|
664
|
+
TMetadata,
|
|
665
|
+
TAbi,
|
|
666
|
+
TFunctionName,
|
|
667
|
+
TArgs,
|
|
668
|
+
TChain,
|
|
669
|
+
TAccount,
|
|
670
|
+
TChainOverride
|
|
671
|
+
>,
|
|
672
|
+
): Promise<Hash> {
|
|
673
|
+
const {metadata, nonce, ...writeArgs} = args;
|
|
674
|
+
const intendedParams = extractIntendedParamsFromWriteContract(args);
|
|
675
|
+
|
|
676
|
+
return executeTrackedTransaction({
|
|
677
|
+
account: normalizeAccount(args.account),
|
|
678
|
+
nonce,
|
|
679
|
+
metadata: metadata as TMetadata,
|
|
680
|
+
restArgs: writeArgs,
|
|
681
|
+
intendedParams,
|
|
682
|
+
execute: (argsWithNonce) =>
|
|
683
|
+
walletClient.writeContract(argsWithNonce as any),
|
|
684
|
+
extractHash: (hash) => hash,
|
|
685
|
+
});
|
|
686
|
+
},
|
|
687
|
+
|
|
688
|
+
async sendTransaction<
|
|
689
|
+
TChainOverride extends Chain | undefined = undefined,
|
|
690
|
+
>(
|
|
691
|
+
args: TrackedSendTransactionParameters<
|
|
692
|
+
TMetadata,
|
|
693
|
+
TChain,
|
|
694
|
+
TAccount,
|
|
695
|
+
TChainOverride
|
|
696
|
+
>,
|
|
697
|
+
): Promise<Hash> {
|
|
698
|
+
const {metadata, nonce, ...sendArgs} = args;
|
|
699
|
+
const intendedParams = extractIntendedParamsFromSendTransaction(args);
|
|
700
|
+
|
|
701
|
+
return executeTrackedTransaction({
|
|
702
|
+
account: normalizeAccount(args.account),
|
|
703
|
+
nonce,
|
|
704
|
+
metadata: metadata as TMetadata,
|
|
705
|
+
restArgs: sendArgs,
|
|
706
|
+
intendedParams,
|
|
707
|
+
execute: (argsWithNonce) =>
|
|
708
|
+
walletClient.sendTransaction(argsWithNonce as any),
|
|
709
|
+
extractHash: (hash) => hash,
|
|
710
|
+
});
|
|
711
|
+
},
|
|
712
|
+
|
|
713
|
+
async sendRawTransaction(
|
|
714
|
+
args: TrackedRawTransactionParameters<TMetadata>,
|
|
715
|
+
): Promise<Hash> {
|
|
716
|
+
const {metadata, serializedTransaction} = args;
|
|
717
|
+
|
|
718
|
+
return executeTrackedRawTransaction({
|
|
719
|
+
serializedTransaction,
|
|
720
|
+
metadata: metadata as TMetadata,
|
|
721
|
+
execute: () =>
|
|
722
|
+
walletClient.sendRawTransaction({serializedTransaction}),
|
|
723
|
+
extractHash: (hash) => hash,
|
|
724
|
+
});
|
|
725
|
+
},
|
|
726
|
+
|
|
727
|
+
// ============================================
|
|
728
|
+
// Sync methods (return receipt, wait for confirmation)
|
|
729
|
+
// ============================================
|
|
730
|
+
|
|
731
|
+
async writeContractSync<
|
|
732
|
+
const TAbi extends Abi | readonly unknown[],
|
|
733
|
+
TFunctionName extends ContractFunctionName<
|
|
734
|
+
TAbi,
|
|
735
|
+
'nonpayable' | 'payable'
|
|
736
|
+
>,
|
|
737
|
+
TArgs extends ContractFunctionArgs<
|
|
738
|
+
TAbi,
|
|
739
|
+
'nonpayable' | 'payable',
|
|
740
|
+
TFunctionName
|
|
741
|
+
>,
|
|
742
|
+
TChainOverride extends Chain | undefined = undefined,
|
|
743
|
+
>(
|
|
744
|
+
args: TrackedWriteContractParameters<
|
|
745
|
+
TMetadata,
|
|
746
|
+
TAbi,
|
|
747
|
+
TFunctionName,
|
|
748
|
+
TArgs,
|
|
749
|
+
TChain,
|
|
750
|
+
TAccount,
|
|
751
|
+
TChainOverride
|
|
752
|
+
>,
|
|
753
|
+
): Promise<TransactionReceipt> {
|
|
754
|
+
const {metadata, nonce, ...writeArgs} = args;
|
|
755
|
+
const intendedParams = extractIntendedParamsFromWriteContract(args);
|
|
756
|
+
|
|
757
|
+
return executeTrackedTransaction({
|
|
758
|
+
account: normalizeAccount(args.account),
|
|
759
|
+
nonce,
|
|
760
|
+
metadata: metadata as TMetadata,
|
|
761
|
+
restArgs: writeArgs,
|
|
762
|
+
intendedParams,
|
|
763
|
+
execute: (argsWithNonce) =>
|
|
764
|
+
walletClient.writeContractSync(argsWithNonce as any),
|
|
765
|
+
extractHash: (receipt) => receipt.transactionHash,
|
|
766
|
+
});
|
|
767
|
+
},
|
|
768
|
+
|
|
769
|
+
async sendTransactionSync<
|
|
770
|
+
TChainOverride extends Chain | undefined = undefined,
|
|
771
|
+
>(
|
|
772
|
+
args: TrackedSendTransactionParameters<
|
|
773
|
+
TMetadata,
|
|
774
|
+
TChain,
|
|
775
|
+
TAccount,
|
|
776
|
+
TChainOverride
|
|
777
|
+
>,
|
|
778
|
+
): Promise<TransactionReceipt> {
|
|
779
|
+
const {metadata, nonce, ...sendArgs} = args;
|
|
780
|
+
const intendedParams = extractIntendedParamsFromSendTransaction(args);
|
|
781
|
+
|
|
782
|
+
return executeTrackedTransaction({
|
|
783
|
+
account: normalizeAccount(args.account),
|
|
784
|
+
nonce,
|
|
785
|
+
metadata: metadata as TMetadata,
|
|
786
|
+
restArgs: sendArgs,
|
|
787
|
+
intendedParams,
|
|
788
|
+
execute: (argsWithNonce) =>
|
|
789
|
+
walletClient.sendTransactionSync(argsWithNonce as any),
|
|
790
|
+
extractHash: (receipt) => receipt.transactionHash,
|
|
791
|
+
});
|
|
792
|
+
},
|
|
793
|
+
|
|
794
|
+
async sendRawTransactionSync(
|
|
795
|
+
args: TrackedRawTransactionParameters<TMetadata>,
|
|
796
|
+
): Promise<TransactionReceipt> {
|
|
797
|
+
const {metadata, serializedTransaction} = args;
|
|
798
|
+
|
|
799
|
+
return executeTrackedRawTransaction({
|
|
800
|
+
serializedTransaction,
|
|
801
|
+
metadata: metadata as TMetadata,
|
|
802
|
+
execute: () =>
|
|
803
|
+
walletClient.sendRawTransactionSync({serializedTransaction}),
|
|
804
|
+
extractHash: (receipt) => receipt.transactionHash,
|
|
805
|
+
});
|
|
806
|
+
},
|
|
807
|
+
|
|
808
|
+
// ============================================
|
|
809
|
+
// Event subscription methods
|
|
810
|
+
// ============================================
|
|
811
|
+
|
|
812
|
+
on: emitter.on.bind(emitter),
|
|
813
|
+
off: emitter.off.bind(emitter),
|
|
814
|
+
};
|
|
815
|
+
},
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* Create an auto-populate builder for TrackedWalletClient.
|
|
821
|
+
* This builder auto-populates operation, functionName and args in writeContract metadata.
|
|
822
|
+
*/
|
|
823
|
+
function createAutoPopulateBuilder<TMetadata>(
|
|
824
|
+
clock: () => number,
|
|
825
|
+
): TrackedWalletClientAutoPopulateBuilder<TMetadata> {
|
|
826
|
+
return {
|
|
827
|
+
using<TClient extends WalletClient>(
|
|
828
|
+
walletClient: TClient,
|
|
829
|
+
publicClient: PublicClient,
|
|
830
|
+
): TrackedWalletClientAutoPopulate<
|
|
831
|
+
TMetadata,
|
|
832
|
+
InferTransport<TClient>,
|
|
833
|
+
InferChain<TClient>,
|
|
834
|
+
InferAccount<TClient>
|
|
835
|
+
> {
|
|
836
|
+
// Type aliases for internal use
|
|
837
|
+
type TTransport = InferTransport<TClient>;
|
|
838
|
+
type TChain = InferChain<TClient>;
|
|
839
|
+
type TAccount = InferAccount<TClient>;
|
|
840
|
+
|
|
841
|
+
// Create emitter for transaction events
|
|
842
|
+
const emitter = new Emitter<{
|
|
843
|
+
'transaction:broadcasted': TrackedTransaction<TMetadata>;
|
|
844
|
+
'transaction:fetched': KnownTrackedTransaction<TMetadata>;
|
|
845
|
+
}>();
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* Resolve the nonce to use for a transaction.
|
|
849
|
+
*/
|
|
850
|
+
async function resolveNonce(
|
|
851
|
+
nonceOption: NonceOption | undefined,
|
|
852
|
+
from: Address,
|
|
255
853
|
): Promise<number> {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
854
|
+
if (typeof nonceOption === 'number') {
|
|
855
|
+
return nonceOption;
|
|
856
|
+
}
|
|
857
|
+
const blockTag = isBlockTag(nonceOption) ? nonceOption : 'pending';
|
|
858
|
+
return await publicClient.getTransactionCount({
|
|
859
|
+
address: from,
|
|
860
|
+
blockTag,
|
|
861
|
+
});
|
|
862
|
+
}
|
|
259
863
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
864
|
+
/**
|
|
865
|
+
* Extract common transaction context (account, nonce) from request args.
|
|
866
|
+
*/
|
|
867
|
+
async function extractTransactionContext(args: {
|
|
868
|
+
account?: Account | Address;
|
|
869
|
+
nonce?: NonceOption;
|
|
870
|
+
}): Promise<TransactionContext> {
|
|
871
|
+
const account = args.account ?? walletClient.account;
|
|
872
|
+
const from = resolveAccountAddress(account);
|
|
266
873
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
`[TrackedWalletClient] Could not fetch tx ${hash} after broadcast. ` +
|
|
272
|
-
`It may not be in the mempool yet.`,
|
|
874
|
+
if (!from) {
|
|
875
|
+
throw new Error(
|
|
876
|
+
'[TrackedWalletClient] No account available. ' +
|
|
877
|
+
'Provide an account in the request or configure the wallet client with an account.',
|
|
273
878
|
);
|
|
274
|
-
return intendedNonce;
|
|
275
879
|
}
|
|
880
|
+
|
|
881
|
+
const intendedNonce = await resolveNonce(args.nonce, from);
|
|
882
|
+
return {from, intendedNonce};
|
|
276
883
|
}
|
|
277
884
|
|
|
278
885
|
/**
|
|
279
|
-
*
|
|
886
|
+
* Fetch full transaction data and emit transaction:fetched event.
|
|
887
|
+
* Non-blocking, runs in background. Does not throw.
|
|
280
888
|
*/
|
|
281
|
-
function
|
|
282
|
-
|
|
283
|
-
from: Address,
|
|
284
|
-
nonce: number,
|
|
889
|
+
async function fetchAndEmitFullData(
|
|
890
|
+
hash: Hash,
|
|
285
891
|
metadata: TMetadata,
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
892
|
+
broadcastTimestampMs: number,
|
|
893
|
+
): Promise<void> {
|
|
894
|
+
try {
|
|
895
|
+
const tx = await publicClient.getTransaction({hash});
|
|
896
|
+
const knownTx = createKnownTrackedTransaction(
|
|
897
|
+
tx,
|
|
898
|
+
metadata,
|
|
899
|
+
broadcastTimestampMs,
|
|
900
|
+
);
|
|
901
|
+
emitter.emit('transaction:fetched', knownTx);
|
|
902
|
+
} catch (error) {
|
|
903
|
+
// Log but don't throw - transaction:fetched simply won't fire
|
|
904
|
+
console.warn(
|
|
905
|
+
`[TrackedWalletClient] Could not fetch tx ${hash}. ` +
|
|
906
|
+
`transaction:fetched event will not be emitted. Error: ${error}`,
|
|
907
|
+
);
|
|
908
|
+
}
|
|
295
909
|
}
|
|
296
910
|
|
|
297
911
|
/**
|
|
298
|
-
*
|
|
299
|
-
*
|
|
912
|
+
* Validate that user didn't provide operation, functionName or args in metadata
|
|
913
|
+
* when populateMetadata is enabled.
|
|
914
|
+
*/
|
|
915
|
+
function validateNoAutoPopulatedFieldsInMetadata(
|
|
916
|
+
userMetadata: unknown,
|
|
917
|
+
): void {
|
|
918
|
+
if (userMetadata && typeof userMetadata === 'object') {
|
|
919
|
+
if ('type' in userMetadata) {
|
|
920
|
+
throw new Error(
|
|
921
|
+
'[TrackedWalletClient] Cannot specify type in metadata when populateMetadata is enabled. ' +
|
|
922
|
+
'The type is automatically populated from the contract call.',
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
if ('functionName' in userMetadata) {
|
|
926
|
+
throw new Error(
|
|
927
|
+
'[TrackedWalletClient] Cannot specify functionName in metadata when populateMetadata is enabled. ' +
|
|
928
|
+
'The functionName is automatically populated from the contract call.',
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
if ('args' in userMetadata) {
|
|
932
|
+
throw new Error(
|
|
933
|
+
'[TrackedWalletClient] Cannot specify args in metadata when populateMetadata is enabled. ' +
|
|
934
|
+
'The args are automatically populated from the contract call.',
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* Common wrapper for transaction methods that broadcast.
|
|
942
|
+
* Emits transaction:broadcasted immediately with intended values,
|
|
943
|
+
* then fetches and emits transaction:fetched with actual values.
|
|
300
944
|
*/
|
|
301
945
|
async function executeTrackedTransaction<T, R>(args: {
|
|
302
946
|
account?: Account | Address;
|
|
303
947
|
nonce?: NonceOption;
|
|
304
948
|
metadata: TMetadata;
|
|
305
949
|
restArgs: T;
|
|
950
|
+
intendedParams: IntendedTransactionParams;
|
|
306
951
|
execute: (argsWithNonce: T & {nonce: number}) => Promise<R>;
|
|
307
952
|
extractHash: (result: R) => Hash;
|
|
308
953
|
}): Promise<R> {
|
|
309
|
-
const {metadata, restArgs, execute, extractHash} = args;
|
|
954
|
+
const {metadata, restArgs, intendedParams, execute, extractHash} = args;
|
|
955
|
+
const broadcastTimestampMs = clock();
|
|
310
956
|
|
|
311
|
-
// Extract common context
|
|
312
957
|
const {from, intendedNonce} = await extractTransactionContext(args);
|
|
313
958
|
|
|
314
|
-
// Execute the underlying transaction with nonce injected
|
|
315
959
|
const result = await execute({
|
|
316
960
|
...restArgs,
|
|
317
961
|
nonce: intendedNonce,
|
|
@@ -320,26 +964,28 @@ export function createTrackedWalletClient<
|
|
|
320
964
|
});
|
|
321
965
|
const hash = extractHash(result);
|
|
322
966
|
|
|
323
|
-
//
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
// Create tracked transaction record
|
|
327
|
-
const trackedTx = createTrackedTransactionRecord(
|
|
967
|
+
// Emit transaction:broadcasted immediately with intended values
|
|
968
|
+
const unknownTx = createUnknownTrackedTransaction(
|
|
328
969
|
hash,
|
|
329
970
|
from,
|
|
330
|
-
|
|
971
|
+
intendedNonce,
|
|
972
|
+
walletClient.chain?.id,
|
|
331
973
|
metadata,
|
|
974
|
+
broadcastTimestampMs,
|
|
975
|
+
intendedParams,
|
|
332
976
|
);
|
|
977
|
+
emitter.emit('transaction:broadcasted', unknownTx);
|
|
333
978
|
|
|
334
|
-
//
|
|
335
|
-
|
|
979
|
+
// Fire-and-forget: fetch full data and emit transaction:fetched
|
|
980
|
+
fetchAndEmitFullData(hash, metadata, broadcastTimestampMs);
|
|
336
981
|
|
|
337
982
|
return result;
|
|
338
983
|
}
|
|
339
984
|
|
|
340
985
|
/**
|
|
341
|
-
* Common wrapper for raw transaction broadcasts
|
|
342
|
-
*
|
|
986
|
+
* Common wrapper for raw transaction broadcasts.
|
|
987
|
+
* For raw transactions, we can parse full data immediately.
|
|
988
|
+
* Emits KnownTrackedTransaction directly to transaction:broadcasted.
|
|
343
989
|
*/
|
|
344
990
|
async function executeTrackedRawTransaction<R>(args: {
|
|
345
991
|
serializedTransaction: TransactionSerialized;
|
|
@@ -348,29 +994,29 @@ export function createTrackedWalletClient<
|
|
|
348
994
|
extractHash: (result: R) => Hash;
|
|
349
995
|
}): Promise<R> {
|
|
350
996
|
const {serializedTransaction, metadata, execute, extractHash} = args;
|
|
997
|
+
const broadcastTimestampMs = clock();
|
|
351
998
|
|
|
352
|
-
|
|
353
|
-
const
|
|
354
|
-
serializedTransaction,
|
|
355
|
-
);
|
|
999
|
+
const from = await recoverTransactionAddress({serializedTransaction});
|
|
1000
|
+
const parsedTx = parseTransaction(serializedTransaction);
|
|
356
1001
|
|
|
357
1002
|
// Execute the broadcast
|
|
358
1003
|
const result = await execute();
|
|
359
1004
|
const hash = extractHash(result);
|
|
360
1005
|
|
|
361
|
-
// For raw transactions,
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
// Create tracked transaction record
|
|
365
|
-
const trackedTx = createTrackedTransactionRecord(
|
|
366
|
-
hash,
|
|
1006
|
+
// For raw transactions, we can parse full data immediately
|
|
1007
|
+
const knownTx = createKnownTrackedTransactionFromRaw(
|
|
1008
|
+
parsedTx,
|
|
367
1009
|
from,
|
|
368
|
-
|
|
1010
|
+
hash,
|
|
369
1011
|
metadata,
|
|
1012
|
+
walletClient.chain?.id,
|
|
1013
|
+
broadcastTimestampMs,
|
|
370
1014
|
);
|
|
371
1015
|
|
|
372
|
-
// Emit
|
|
373
|
-
emitter.emit('transaction:broadcasted',
|
|
1016
|
+
// Emit as KnownTrackedTransaction since we have all data
|
|
1017
|
+
emitter.emit('transaction:broadcasted', knownTx);
|
|
1018
|
+
|
|
1019
|
+
// We do not emit fetched as the tx is already known
|
|
374
1020
|
|
|
375
1021
|
return result;
|
|
376
1022
|
}
|
|
@@ -400,7 +1046,7 @@ export function createTrackedWalletClient<
|
|
|
400
1046
|
>,
|
|
401
1047
|
TChainOverride extends Chain | undefined = undefined,
|
|
402
1048
|
>(
|
|
403
|
-
args:
|
|
1049
|
+
args: TrackedWriteContractAutoPopulateParameters<
|
|
404
1050
|
TMetadata,
|
|
405
1051
|
TAbi,
|
|
406
1052
|
TFunctionName,
|
|
@@ -410,13 +1056,27 @@ export function createTrackedWalletClient<
|
|
|
410
1056
|
TChainOverride
|
|
411
1057
|
>,
|
|
412
1058
|
): Promise<Hash> {
|
|
413
|
-
const {metadata, nonce, ...writeArgs} = args;
|
|
1059
|
+
const {metadata: userMetadata, nonce, ...writeArgs} = args;
|
|
1060
|
+
|
|
1061
|
+
// Validate that user didn't provide operation, functionName or args
|
|
1062
|
+
validateNoAutoPopulatedFieldsInMetadata(userMetadata);
|
|
1063
|
+
|
|
1064
|
+
// Auto-populate type, functionName and args
|
|
1065
|
+
const finalMetadata = {
|
|
1066
|
+
...(userMetadata ?? {}),
|
|
1067
|
+
type: 'functionCall' as const,
|
|
1068
|
+
functionName: args.functionName as string,
|
|
1069
|
+
args: args.args as readonly unknown[],
|
|
1070
|
+
} as TMetadata;
|
|
1071
|
+
|
|
1072
|
+
const intendedParams = extractIntendedParamsFromWriteContract(args);
|
|
414
1073
|
|
|
415
1074
|
return executeTrackedTransaction({
|
|
416
1075
|
account: normalizeAccount(args.account),
|
|
417
1076
|
nonce,
|
|
418
|
-
metadata:
|
|
1077
|
+
metadata: finalMetadata,
|
|
419
1078
|
restArgs: writeArgs,
|
|
1079
|
+
intendedParams,
|
|
420
1080
|
execute: (argsWithNonce) =>
|
|
421
1081
|
walletClient.writeContract(argsWithNonce as any),
|
|
422
1082
|
extractHash: (hash) => hash,
|
|
@@ -434,12 +1094,14 @@ export function createTrackedWalletClient<
|
|
|
434
1094
|
>,
|
|
435
1095
|
): Promise<Hash> {
|
|
436
1096
|
const {metadata, nonce, ...sendArgs} = args;
|
|
1097
|
+
const intendedParams = extractIntendedParamsFromSendTransaction(args);
|
|
437
1098
|
|
|
438
1099
|
return executeTrackedTransaction({
|
|
439
1100
|
account: normalizeAccount(args.account),
|
|
440
1101
|
nonce,
|
|
441
1102
|
metadata: metadata as TMetadata,
|
|
442
1103
|
restArgs: sendArgs,
|
|
1104
|
+
intendedParams,
|
|
443
1105
|
execute: (argsWithNonce) =>
|
|
444
1106
|
walletClient.sendTransaction(argsWithNonce as any),
|
|
445
1107
|
extractHash: (hash) => hash,
|
|
@@ -477,7 +1139,7 @@ export function createTrackedWalletClient<
|
|
|
477
1139
|
>,
|
|
478
1140
|
TChainOverride extends Chain | undefined = undefined,
|
|
479
1141
|
>(
|
|
480
|
-
args:
|
|
1142
|
+
args: TrackedWriteContractAutoPopulateParameters<
|
|
481
1143
|
TMetadata,
|
|
482
1144
|
TAbi,
|
|
483
1145
|
TFunctionName,
|
|
@@ -487,13 +1149,27 @@ export function createTrackedWalletClient<
|
|
|
487
1149
|
TChainOverride
|
|
488
1150
|
>,
|
|
489
1151
|
): Promise<TransactionReceipt> {
|
|
490
|
-
const {metadata, nonce, ...writeArgs} = args;
|
|
1152
|
+
const {metadata: userMetadata, nonce, ...writeArgs} = args;
|
|
1153
|
+
|
|
1154
|
+
// Validate that user didn't provide operation, functionName or args
|
|
1155
|
+
validateNoAutoPopulatedFieldsInMetadata(userMetadata);
|
|
1156
|
+
|
|
1157
|
+
// Auto-populate type, functionName and args
|
|
1158
|
+
const finalMetadata = {
|
|
1159
|
+
...(userMetadata ?? {}),
|
|
1160
|
+
type: 'functionCall' as const,
|
|
1161
|
+
functionName: args.functionName as string,
|
|
1162
|
+
args: args.args as readonly unknown[],
|
|
1163
|
+
} as TMetadata;
|
|
1164
|
+
|
|
1165
|
+
const intendedParams = extractIntendedParamsFromWriteContract(args);
|
|
491
1166
|
|
|
492
1167
|
return executeTrackedTransaction({
|
|
493
1168
|
account: normalizeAccount(args.account),
|
|
494
1169
|
nonce,
|
|
495
|
-
metadata:
|
|
1170
|
+
metadata: finalMetadata,
|
|
496
1171
|
restArgs: writeArgs,
|
|
1172
|
+
intendedParams,
|
|
497
1173
|
execute: (argsWithNonce) =>
|
|
498
1174
|
walletClient.writeContractSync(argsWithNonce as any),
|
|
499
1175
|
extractHash: (receipt) => receipt.transactionHash,
|
|
@@ -511,12 +1187,14 @@ export function createTrackedWalletClient<
|
|
|
511
1187
|
>,
|
|
512
1188
|
): Promise<TransactionReceipt> {
|
|
513
1189
|
const {metadata, nonce, ...sendArgs} = args;
|
|
1190
|
+
const intendedParams = extractIntendedParamsFromSendTransaction(args);
|
|
514
1191
|
|
|
515
1192
|
return executeTrackedTransaction({
|
|
516
1193
|
account: normalizeAccount(args.account),
|
|
517
1194
|
nonce,
|
|
518
1195
|
metadata: metadata as TMetadata,
|
|
519
1196
|
restArgs: sendArgs,
|
|
1197
|
+
intendedParams,
|
|
520
1198
|
execute: (argsWithNonce) =>
|
|
521
1199
|
walletClient.sendTransactionSync(argsWithNonce as any),
|
|
522
1200
|
extractHash: (receipt) => receipt.transactionHash,
|
|
@@ -541,13 +1219,8 @@ export function createTrackedWalletClient<
|
|
|
541
1219
|
// Event subscription methods
|
|
542
1220
|
// ============================================
|
|
543
1221
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
) => emitter.on('transaction:broadcasted', listener),
|
|
547
|
-
|
|
548
|
-
offTransactionBroadcasted: (
|
|
549
|
-
listener: (event: TrackedTransaction<TMetadata>) => void,
|
|
550
|
-
) => emitter.off('transaction:broadcasted', listener),
|
|
1222
|
+
on: emitter.on.bind(emitter),
|
|
1223
|
+
off: emitter.off.bind(emitter),
|
|
551
1224
|
};
|
|
552
1225
|
},
|
|
553
1226
|
};
|