@etherkit/viem-tx-tracker 0.0.5 → 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.map +1 -1
- package/dist/TrackedWalletClient.js +254 -129
- package/dist/TrackedWalletClient.js.map +1 -1
- package/dist/types.d.ts +110 -21
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/TrackedWalletClient.ts +378 -198
- package/src/types.ts +130 -24
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TrackedWalletClient.d.ts","sourceRoot":"","sources":["../src/TrackedWalletClient.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"TrackedWalletClient.d.ts","sourceRoot":"","sources":["../src/TrackedWalletClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAKN,KAAK,OAAO,EAEZ,KAAK,KAAK,EAIV,KAAK,YAAY,EAIjB,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,MAAM,MAAM,CAAC;AAEd,OAAO,KAAK,EAMX,iBAAiB,EAIjB,mBAAmB,EACnB,+BAA+B,EAI/B,MAAM,YAAY,CAAC;AA+RpB;;GAEG;AACH,KAAK,cAAc,CAAC,CAAC,IACpB,CAAC,SAAS,YAAY,CAAC,MAAM,UAAU,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,UAAU,GAAG,SAAS,CAAC;AAE7E;;GAEG;AACH,KAAK,UAAU,CAAC,CAAC,IAChB,CAAC,SAAS,YAAY,CAAC,GAAG,EAAE,MAAM,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,GAAG,KAAK,GAAG,SAAS,CAAC;AAE7E;;GAEG;AACH,KAAK,YAAY,CAAC,CAAC,IAClB,CAAC,SAAS,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC,GAC7C,QAAQ,GACR,OAAO,GAAG,SAAS,CAAC;AAExB;;GAEG;AACH,MAAM,WAAW,0BAA0B,CAAC,SAAS;IACpD;;;;;;OAMG;IACH,KAAK,CAAC,OAAO,SAAS,YAAY,EACjC,YAAY,EAAE,OAAO,EACrB,YAAY,EAAE,YAAY,GACxB,mBAAmB,CACrB,SAAS,EACT,cAAc,CAAC,OAAO,CAAC,EACvB,UAAU,CAAC,OAAO,CAAC,EACnB,YAAY,CAAC,OAAO,CAAC,CACrB,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,WAAW,sCAAsC,CAAC,SAAS;IAChE;;;;;;;OAOG;IACH,KAAK,CAAC,OAAO,SAAS,YAAY,EACjC,YAAY,EAAE,OAAO,EACrB,YAAY,EAAE,YAAY,GACxB,+BAA+B,CACjC,SAAS,EACT,cAAc,CAAC,OAAO,CAAC,EACvB,UAAU,CAAC,OAAO,CAAC,EACnB,YAAY,CAAC,OAAO,CAAC,CACrB,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,wBAAgB,yBAAyB,CACxC,SAAS,KACL,0BAA0B,CAAC,SAAS,CAAC,CAAC;AAG3C,wBAAgB,yBAAyB,CAAC,OAAO,EAAE;IAClD,gBAAgB,EAAE,IAAI,CAAC;CACvB,GAAG,sCAAsC,CAAC,iBAAiB,CAAC,CAAC;AAG9D,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,OAAO,EAAE;IAC7D,gBAAgB,EAAE,IAAI,CAAC;CACvB,GAAG,sCAAsC,CAAC,SAAS,CAAC,CAAC"}
|
|
@@ -24,20 +24,189 @@ function normalizeAccount(account) {
|
|
|
24
24
|
return account === null ? undefined : account;
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
27
|
+
* Infer transaction type from provided params.
|
|
28
|
+
* Returns undefined if can't be determined (wallet will decide).
|
|
28
29
|
*/
|
|
29
|
-
function
|
|
30
|
-
|
|
30
|
+
function inferTxType(params) {
|
|
31
|
+
if (params.maxFeePerGas !== undefined) {
|
|
32
|
+
return 'eip1559';
|
|
33
|
+
}
|
|
34
|
+
if (params.gasPrice !== undefined && params.accessList !== undefined) {
|
|
35
|
+
return 'eip2930';
|
|
36
|
+
}
|
|
37
|
+
if (params.gasPrice !== undefined) {
|
|
38
|
+
return 'legacy';
|
|
39
|
+
}
|
|
40
|
+
return undefined; // Wallet will determine
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Create an UnknownTrackedTransaction for immediate emission.
|
|
44
|
+
* Populates all known intended values from the transaction parameters.
|
|
45
|
+
*/
|
|
46
|
+
function createUnknownTrackedTransaction(hash, from, nonce, chainId, metadata, broadcastTimestampMs, params) {
|
|
47
|
+
const txType = inferTxType(params);
|
|
48
|
+
return {
|
|
49
|
+
known: false,
|
|
50
|
+
chainId,
|
|
51
|
+
hash,
|
|
52
|
+
from,
|
|
53
|
+
nonce,
|
|
54
|
+
broadcastTimestampMs,
|
|
55
|
+
metadata,
|
|
56
|
+
...(txType !== undefined && { txType }),
|
|
57
|
+
...(params.to !== undefined && { to: params.to }),
|
|
58
|
+
...(params.value !== undefined && { value: params.value }),
|
|
59
|
+
...(params.data !== undefined && { data: params.data }),
|
|
60
|
+
...(params.gas !== undefined && { gas: params.gas }),
|
|
61
|
+
...(params.gasPrice !== undefined && { gasPrice: params.gasPrice }),
|
|
62
|
+
...(params.maxFeePerGas !== undefined && {
|
|
63
|
+
maxFeePerGas: params.maxFeePerGas,
|
|
64
|
+
}),
|
|
65
|
+
...(params.maxPriorityFeePerGas !== undefined && {
|
|
66
|
+
maxPriorityFeePerGas: params.maxPriorityFeePerGas,
|
|
67
|
+
}),
|
|
68
|
+
...(params.accessList !== undefined && { accessList: params.accessList }),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Extract transaction type-specific fields from a fetched transaction.
|
|
73
|
+
*/
|
|
74
|
+
function extractTransactionTypeFields(tx) {
|
|
75
|
+
if (tx.type === 'eip1559') {
|
|
76
|
+
return {
|
|
77
|
+
txType: 'eip1559',
|
|
78
|
+
maxFeePerGas: tx.maxFeePerGas,
|
|
79
|
+
maxPriorityFeePerGas: tx.maxPriorityFeePerGas,
|
|
80
|
+
...(tx.accessList && { accessList: tx.accessList }),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
else if (tx.type === 'eip2930') {
|
|
84
|
+
return {
|
|
85
|
+
txType: 'eip2930',
|
|
86
|
+
gasPrice: tx.gasPrice,
|
|
87
|
+
accessList: (tx.accessList ?? []),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// Legacy or unknown - treat as legacy
|
|
92
|
+
return {
|
|
93
|
+
txType: 'legacy',
|
|
94
|
+
gasPrice: tx.gasPrice,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Create a KnownTrackedTransaction from a fetched transaction.
|
|
100
|
+
*/
|
|
101
|
+
function createKnownTrackedTransaction(tx, metadata, broadcastTimestampMs) {
|
|
102
|
+
const typeFields = extractTransactionTypeFields(tx);
|
|
103
|
+
return {
|
|
104
|
+
known: true,
|
|
105
|
+
chainId: tx.chainId,
|
|
106
|
+
hash: tx.hash,
|
|
107
|
+
from: tx.from,
|
|
108
|
+
to: tx.to,
|
|
109
|
+
nonce: tx.nonce,
|
|
110
|
+
value: tx.value,
|
|
111
|
+
data: tx.input,
|
|
112
|
+
gas: tx.gas,
|
|
113
|
+
broadcastTimestampMs,
|
|
114
|
+
metadata,
|
|
115
|
+
...typeFields,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Create a KnownTrackedTransaction from a parsed raw transaction.
|
|
120
|
+
*/
|
|
121
|
+
function createKnownTrackedTransactionFromRaw(parsedTx, from, hash, metadata, chainId, broadcastTimestampMs) {
|
|
122
|
+
// Determine transaction type from parsed tx
|
|
123
|
+
let typeFields;
|
|
124
|
+
if ('maxFeePerGas' in parsedTx && parsedTx.maxFeePerGas !== undefined) {
|
|
125
|
+
typeFields = {
|
|
126
|
+
txType: 'eip1559',
|
|
127
|
+
maxFeePerGas: parsedTx.maxFeePerGas,
|
|
128
|
+
maxPriorityFeePerGas: parsedTx.maxPriorityFeePerGas,
|
|
129
|
+
...('accessList' in parsedTx &&
|
|
130
|
+
parsedTx.accessList && {
|
|
131
|
+
accessList: parsedTx.accessList,
|
|
132
|
+
}),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
else if ('accessList' in parsedTx && parsedTx.accessList) {
|
|
136
|
+
typeFields = {
|
|
137
|
+
txType: 'eip2930',
|
|
138
|
+
gasPrice: parsedTx.gasPrice,
|
|
139
|
+
accessList: parsedTx.accessList,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
typeFields = {
|
|
144
|
+
txType: 'legacy',
|
|
145
|
+
gasPrice: parsedTx.gasPrice,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
known: true,
|
|
150
|
+
chainId: parsedTx.chainId ?? chainId,
|
|
151
|
+
hash,
|
|
152
|
+
from,
|
|
153
|
+
to: parsedTx.to ?? null,
|
|
154
|
+
nonce: parsedTx.nonce,
|
|
155
|
+
value: parsedTx.value ?? 0n,
|
|
156
|
+
data: parsedTx.data ?? '0x',
|
|
157
|
+
gas: parsedTx.gas,
|
|
158
|
+
broadcastTimestampMs,
|
|
159
|
+
metadata,
|
|
160
|
+
...typeFields,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Extract intended params from sendTransaction args.
|
|
165
|
+
* Uses 'unknown' for 'to' since viem's SendTransactionParameters has a complex type for it.
|
|
166
|
+
*/
|
|
167
|
+
function extractIntendedParamsFromSendTransaction(args) {
|
|
168
|
+
// Normalize 'to' to either a hex address, null, or undefined
|
|
169
|
+
const to = args.to === null || args.to === undefined
|
|
170
|
+
? args.to
|
|
171
|
+
: typeof args.to === 'string'
|
|
172
|
+
? args.to
|
|
173
|
+
: undefined;
|
|
174
|
+
return {
|
|
175
|
+
to,
|
|
176
|
+
value: args.value ?? 0n,
|
|
177
|
+
data: args.data,
|
|
178
|
+
gas: args.gas,
|
|
179
|
+
gasPrice: args.gasPrice,
|
|
180
|
+
maxFeePerGas: args.maxFeePerGas,
|
|
181
|
+
maxPriorityFeePerGas: args.maxPriorityFeePerGas,
|
|
182
|
+
accessList: args.accessList,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Extract intended params from writeContract args.
|
|
187
|
+
*/
|
|
188
|
+
function extractIntendedParamsFromWriteContract(args) {
|
|
189
|
+
return {
|
|
190
|
+
to: args.address,
|
|
191
|
+
value: args.value ?? 0n,
|
|
192
|
+
// Note: data could be encoded using encodeFunctionData(args) if desired
|
|
193
|
+
gas: args.gas,
|
|
194
|
+
gasPrice: args.gasPrice,
|
|
195
|
+
maxFeePerGas: args.maxFeePerGas,
|
|
196
|
+
maxPriorityFeePerGas: args.maxPriorityFeePerGas,
|
|
197
|
+
accessList: args.accessList,
|
|
198
|
+
};
|
|
31
199
|
}
|
|
32
200
|
// Implementation
|
|
33
201
|
export function createTrackedWalletClient(options) {
|
|
34
202
|
const populateMetadata = options?.populateMetadata ?? false;
|
|
203
|
+
const clock = options?.clock ?? Date.now;
|
|
35
204
|
if (populateMetadata) {
|
|
36
|
-
return createAutoPopulateBuilder();
|
|
205
|
+
return createAutoPopulateBuilder(clock);
|
|
37
206
|
}
|
|
38
207
|
return {
|
|
39
208
|
using(walletClient, publicClient) {
|
|
40
|
-
// Create emitter for transaction
|
|
209
|
+
// Create emitter for transaction events
|
|
41
210
|
const emitter = new Emitter();
|
|
42
211
|
/**
|
|
43
212
|
* Resolve the nonce to use for a transaction.
|
|
@@ -78,71 +247,29 @@ export function createTrackedWalletClient(options) {
|
|
|
78
247
|
return { from, intendedNonce };
|
|
79
248
|
}
|
|
80
249
|
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* @param serializedTransaction - The RLP-encoded signed transaction
|
|
85
|
-
* @returns TransactionContext with from address and nonce
|
|
86
|
-
*/
|
|
87
|
-
async function extractRawTransactionContext(serializedTransaction) {
|
|
88
|
-
// Parse the serialized transaction to get the nonce
|
|
89
|
-
const parsedTx = parseTransaction(serializedTransaction);
|
|
90
|
-
if (parsedTx.nonce === undefined) {
|
|
91
|
-
throw new Error('[TrackedWalletClient] Could not extract nonce from serialized transaction.');
|
|
92
|
-
}
|
|
93
|
-
// Recover the sender address from the signature
|
|
94
|
-
const from = await recoverTransactionAddress({
|
|
95
|
-
serializedTransaction,
|
|
96
|
-
});
|
|
97
|
-
return {
|
|
98
|
-
from,
|
|
99
|
-
intendedNonce: parsedTx.nonce,
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Fetch the transaction after broadcast to verify nonce.
|
|
104
|
-
* Logs a warning if the nonce was overridden or if tx cannot be found.
|
|
105
|
-
*
|
|
106
|
-
* @param hash - The transaction hash
|
|
107
|
-
* @param intendedNonce - The nonce we intended to use
|
|
108
|
-
* @returns The actual nonce, or the intended nonce if fetch failed
|
|
250
|
+
* Fetch full transaction data and emit transaction:fetched event.
|
|
251
|
+
* Non-blocking, runs in background. Does not throw.
|
|
109
252
|
*/
|
|
110
|
-
async function
|
|
253
|
+
async function fetchAndEmitFullData(hash, metadata, broadcastTimestampMs) {
|
|
111
254
|
try {
|
|
112
255
|
const tx = await publicClient.getTransaction({ hash });
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
console.warn(`[TrackedWalletClient] Nonce mismatch: intended ${intendedNonce}, actual ${actualNonce}. ` +
|
|
116
|
-
`Wallet may have overridden the nonce.`);
|
|
117
|
-
}
|
|
118
|
-
return actualNonce;
|
|
256
|
+
const knownTx = createKnownTrackedTransaction(tx, metadata, broadcastTimestampMs);
|
|
257
|
+
emitter.emit('transaction:fetched', knownTx);
|
|
119
258
|
}
|
|
120
|
-
catch (
|
|
121
|
-
//
|
|
122
|
-
console.warn(`[TrackedWalletClient] Could not fetch tx ${hash}
|
|
123
|
-
`
|
|
124
|
-
return intendedNonce;
|
|
259
|
+
catch (error) {
|
|
260
|
+
// Log but don't throw - transaction:fetched simply won't fire
|
|
261
|
+
console.warn(`[TrackedWalletClient] Could not fetch tx ${hash}. ` +
|
|
262
|
+
`transaction:fetched event will not be emitted. Error: ${error}`);
|
|
125
263
|
}
|
|
126
264
|
}
|
|
127
|
-
/**
|
|
128
|
-
* Create a tracked transaction record.
|
|
129
|
-
*/
|
|
130
|
-
function createTrackedTransactionRecord(txHash, from, nonce, metadata) {
|
|
131
|
-
return {
|
|
132
|
-
hash: txHash,
|
|
133
|
-
from,
|
|
134
|
-
nonce,
|
|
135
|
-
chainId: walletClient.chain?.id,
|
|
136
|
-
metadata,
|
|
137
|
-
broadcastTimestampMs: Date.now(),
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
265
|
/**
|
|
141
266
|
* Common wrapper for transaction methods that broadcast (sendTransaction, writeContract).
|
|
142
|
-
*
|
|
267
|
+
* Emits transaction:broadcasted immediately with intended values,
|
|
268
|
+
* then fetches and emits transaction:fetched with actual values.
|
|
143
269
|
*/
|
|
144
270
|
async function executeTrackedTransaction(args) {
|
|
145
|
-
const { metadata, restArgs, execute, extractHash } = args;
|
|
271
|
+
const { metadata, restArgs, intendedParams, execute, extractHash } = args;
|
|
272
|
+
const broadcastTimestampMs = clock();
|
|
146
273
|
// Extract common context
|
|
147
274
|
const { from, intendedNonce } = await extractTransactionContext(args);
|
|
148
275
|
// Execute the underlying transaction with nonce injected
|
|
@@ -151,31 +278,32 @@ export function createTrackedWalletClient(options) {
|
|
|
151
278
|
nonce: intendedNonce,
|
|
152
279
|
});
|
|
153
280
|
const hash = extractHash(result);
|
|
154
|
-
//
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
emitter.emit('transaction:broadcasted', trackedTx);
|
|
281
|
+
// Emit transaction:broadcasted immediately with intended values
|
|
282
|
+
const unknownTx = createUnknownTrackedTransaction(hash, from, intendedNonce, walletClient.chain?.id, metadata, broadcastTimestampMs, intendedParams);
|
|
283
|
+
emitter.emit('transaction:broadcasted', unknownTx);
|
|
284
|
+
// Fire-and-forget: fetch full data and emit transaction:fetched
|
|
285
|
+
fetchAndEmitFullData(hash, metadata, broadcastTimestampMs);
|
|
160
286
|
return result;
|
|
161
287
|
}
|
|
162
288
|
/**
|
|
163
289
|
* Common wrapper for raw transaction broadcasts (sendRawTransaction).
|
|
164
|
-
*
|
|
290
|
+
* For raw transactions, we can parse full data immediately.
|
|
291
|
+
* Emits KnownTrackedTransaction directly to transaction:broadcasted.
|
|
165
292
|
*/
|
|
166
293
|
async function executeTrackedRawTransaction(args) {
|
|
167
294
|
const { serializedTransaction, metadata, execute, extractHash } = args;
|
|
168
|
-
|
|
169
|
-
const
|
|
295
|
+
const broadcastTimestampMs = clock();
|
|
296
|
+
const from = await recoverTransactionAddress({ serializedTransaction });
|
|
297
|
+
const parsedTx = parseTransaction(serializedTransaction);
|
|
170
298
|
// Execute the broadcast
|
|
171
299
|
const result = await execute();
|
|
172
300
|
const hash = extractHash(result);
|
|
173
|
-
// For raw transactions,
|
|
174
|
-
|
|
175
|
-
//
|
|
176
|
-
|
|
177
|
-
//
|
|
178
|
-
emitter.emit('transaction:
|
|
301
|
+
// For raw transactions, we can parse full data immediately
|
|
302
|
+
const knownTx = createKnownTrackedTransactionFromRaw(parsedTx, from, hash, metadata, walletClient.chain?.id, broadcastTimestampMs);
|
|
303
|
+
// Emit as KnownTrackedTransaction since we have all data
|
|
304
|
+
emitter.emit('transaction:broadcasted', knownTx);
|
|
305
|
+
// Also emit to transaction:fetched for consistency
|
|
306
|
+
emitter.emit('transaction:fetched', knownTx);
|
|
179
307
|
return result;
|
|
180
308
|
}
|
|
181
309
|
return {
|
|
@@ -186,22 +314,26 @@ export function createTrackedWalletClient(options) {
|
|
|
186
314
|
// ============================================
|
|
187
315
|
async writeContract(args) {
|
|
188
316
|
const { metadata, nonce, ...writeArgs } = args;
|
|
317
|
+
const intendedParams = extractIntendedParamsFromWriteContract(args);
|
|
189
318
|
return executeTrackedTransaction({
|
|
190
319
|
account: normalizeAccount(args.account),
|
|
191
320
|
nonce,
|
|
192
321
|
metadata: metadata,
|
|
193
322
|
restArgs: writeArgs,
|
|
323
|
+
intendedParams,
|
|
194
324
|
execute: (argsWithNonce) => walletClient.writeContract(argsWithNonce),
|
|
195
325
|
extractHash: (hash) => hash,
|
|
196
326
|
});
|
|
197
327
|
},
|
|
198
328
|
async sendTransaction(args) {
|
|
199
329
|
const { metadata, nonce, ...sendArgs } = args;
|
|
330
|
+
const intendedParams = extractIntendedParamsFromSendTransaction(args);
|
|
200
331
|
return executeTrackedTransaction({
|
|
201
332
|
account: normalizeAccount(args.account),
|
|
202
333
|
nonce,
|
|
203
334
|
metadata: metadata,
|
|
204
335
|
restArgs: sendArgs,
|
|
336
|
+
intendedParams,
|
|
205
337
|
execute: (argsWithNonce) => walletClient.sendTransaction(argsWithNonce),
|
|
206
338
|
extractHash: (hash) => hash,
|
|
207
339
|
});
|
|
@@ -220,22 +352,26 @@ export function createTrackedWalletClient(options) {
|
|
|
220
352
|
// ============================================
|
|
221
353
|
async writeContractSync(args) {
|
|
222
354
|
const { metadata, nonce, ...writeArgs } = args;
|
|
355
|
+
const intendedParams = extractIntendedParamsFromWriteContract(args);
|
|
223
356
|
return executeTrackedTransaction({
|
|
224
357
|
account: normalizeAccount(args.account),
|
|
225
358
|
nonce,
|
|
226
359
|
metadata: metadata,
|
|
227
360
|
restArgs: writeArgs,
|
|
361
|
+
intendedParams,
|
|
228
362
|
execute: (argsWithNonce) => walletClient.writeContractSync(argsWithNonce),
|
|
229
363
|
extractHash: (receipt) => receipt.transactionHash,
|
|
230
364
|
});
|
|
231
365
|
},
|
|
232
366
|
async sendTransactionSync(args) {
|
|
233
367
|
const { metadata, nonce, ...sendArgs } = args;
|
|
368
|
+
const intendedParams = extractIntendedParamsFromSendTransaction(args);
|
|
234
369
|
return executeTrackedTransaction({
|
|
235
370
|
account: normalizeAccount(args.account),
|
|
236
371
|
nonce,
|
|
237
372
|
metadata: metadata,
|
|
238
373
|
restArgs: sendArgs,
|
|
374
|
+
intendedParams,
|
|
239
375
|
execute: (argsWithNonce) => walletClient.sendTransactionSync(argsWithNonce),
|
|
240
376
|
extractHash: (receipt) => receipt.transactionHash,
|
|
241
377
|
});
|
|
@@ -252,8 +388,8 @@ export function createTrackedWalletClient(options) {
|
|
|
252
388
|
// ============================================
|
|
253
389
|
// Event subscription methods
|
|
254
390
|
// ============================================
|
|
255
|
-
|
|
256
|
-
|
|
391
|
+
on: emitter.on.bind(emitter),
|
|
392
|
+
off: emitter.off.bind(emitter),
|
|
257
393
|
};
|
|
258
394
|
},
|
|
259
395
|
};
|
|
@@ -262,10 +398,10 @@ export function createTrackedWalletClient(options) {
|
|
|
262
398
|
* Create an auto-populate builder for TrackedWalletClient.
|
|
263
399
|
* This builder auto-populates operation, functionName and args in writeContract metadata.
|
|
264
400
|
*/
|
|
265
|
-
function createAutoPopulateBuilder() {
|
|
401
|
+
function createAutoPopulateBuilder(clock) {
|
|
266
402
|
return {
|
|
267
403
|
using(walletClient, publicClient) {
|
|
268
|
-
// Create emitter for transaction
|
|
404
|
+
// Create emitter for transaction events
|
|
269
405
|
const emitter = new Emitter();
|
|
270
406
|
/**
|
|
271
407
|
* Resolve the nonce to use for a transaction.
|
|
@@ -294,53 +430,21 @@ function createAutoPopulateBuilder() {
|
|
|
294
430
|
return { from, intendedNonce };
|
|
295
431
|
}
|
|
296
432
|
/**
|
|
297
|
-
*
|
|
433
|
+
* Fetch full transaction data and emit transaction:fetched event.
|
|
434
|
+
* Non-blocking, runs in background. Does not throw.
|
|
298
435
|
*/
|
|
299
|
-
async function
|
|
300
|
-
const parsedTx = parseTransaction(serializedTransaction);
|
|
301
|
-
if (parsedTx.nonce === undefined) {
|
|
302
|
-
throw new Error('[TrackedWalletClient] Could not extract nonce from serialized transaction.');
|
|
303
|
-
}
|
|
304
|
-
const from = await recoverTransactionAddress({
|
|
305
|
-
serializedTransaction,
|
|
306
|
-
});
|
|
307
|
-
return {
|
|
308
|
-
from,
|
|
309
|
-
intendedNonce: parsedTx.nonce,
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* Fetch the transaction after broadcast to verify nonce.
|
|
314
|
-
*/
|
|
315
|
-
async function verifyTransactionNonce(hash, intendedNonce) {
|
|
436
|
+
async function fetchAndEmitFullData(hash, metadata, broadcastTimestampMs) {
|
|
316
437
|
try {
|
|
317
438
|
const tx = await publicClient.getTransaction({ hash });
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
console.warn(`[TrackedWalletClient] Nonce mismatch: intended ${intendedNonce}, actual ${actualNonce}. ` +
|
|
321
|
-
`Wallet may have overridden the nonce.`);
|
|
322
|
-
}
|
|
323
|
-
return actualNonce;
|
|
439
|
+
const knownTx = createKnownTrackedTransaction(tx, metadata, broadcastTimestampMs);
|
|
440
|
+
emitter.emit('transaction:fetched', knownTx);
|
|
324
441
|
}
|
|
325
|
-
catch (
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
442
|
+
catch (error) {
|
|
443
|
+
// Log but don't throw - transaction:fetched simply won't fire
|
|
444
|
+
console.warn(`[TrackedWalletClient] Could not fetch tx ${hash}. ` +
|
|
445
|
+
`transaction:fetched event will not be emitted. Error: ${error}`);
|
|
329
446
|
}
|
|
330
447
|
}
|
|
331
|
-
/**
|
|
332
|
-
* Create a tracked transaction record.
|
|
333
|
-
*/
|
|
334
|
-
function createTrackedTransactionRecord(txHash, from, nonce, metadata) {
|
|
335
|
-
return {
|
|
336
|
-
hash: txHash,
|
|
337
|
-
from,
|
|
338
|
-
nonce,
|
|
339
|
-
chainId: walletClient.chain?.id,
|
|
340
|
-
metadata,
|
|
341
|
-
broadcastTimestampMs: Date.now(),
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
448
|
/**
|
|
345
449
|
* Validate that user didn't provide operation, functionName or args in metadata
|
|
346
450
|
* when populateMetadata is enabled.
|
|
@@ -363,30 +467,43 @@ function createAutoPopulateBuilder() {
|
|
|
363
467
|
}
|
|
364
468
|
/**
|
|
365
469
|
* Common wrapper for transaction methods that broadcast.
|
|
470
|
+
* Emits transaction:broadcasted immediately with intended values,
|
|
471
|
+
* then fetches and emits transaction:fetched with actual values.
|
|
366
472
|
*/
|
|
367
473
|
async function executeTrackedTransaction(args) {
|
|
368
|
-
const { metadata, restArgs, execute, extractHash } = args;
|
|
474
|
+
const { metadata, restArgs, intendedParams, execute, extractHash } = args;
|
|
475
|
+
const broadcastTimestampMs = clock();
|
|
369
476
|
const { from, intendedNonce } = await extractTransactionContext(args);
|
|
370
477
|
const result = await execute({
|
|
371
478
|
...restArgs,
|
|
372
479
|
nonce: intendedNonce,
|
|
373
480
|
});
|
|
374
481
|
const hash = extractHash(result);
|
|
375
|
-
|
|
376
|
-
const
|
|
377
|
-
emitter.emit('transaction:broadcasted',
|
|
482
|
+
// Emit transaction:broadcasted immediately with intended values
|
|
483
|
+
const unknownTx = createUnknownTrackedTransaction(hash, from, intendedNonce, walletClient.chain?.id, metadata, broadcastTimestampMs, intendedParams);
|
|
484
|
+
emitter.emit('transaction:broadcasted', unknownTx);
|
|
485
|
+
// Fire-and-forget: fetch full data and emit transaction:fetched
|
|
486
|
+
fetchAndEmitFullData(hash, metadata, broadcastTimestampMs);
|
|
378
487
|
return result;
|
|
379
488
|
}
|
|
380
489
|
/**
|
|
381
490
|
* Common wrapper for raw transaction broadcasts.
|
|
491
|
+
* For raw transactions, we can parse full data immediately.
|
|
492
|
+
* Emits KnownTrackedTransaction directly to transaction:broadcasted.
|
|
382
493
|
*/
|
|
383
494
|
async function executeTrackedRawTransaction(args) {
|
|
384
495
|
const { serializedTransaction, metadata, execute, extractHash } = args;
|
|
385
|
-
const
|
|
496
|
+
const broadcastTimestampMs = clock();
|
|
497
|
+
const from = await recoverTransactionAddress({ serializedTransaction });
|
|
498
|
+
const parsedTx = parseTransaction(serializedTransaction);
|
|
499
|
+
// Execute the broadcast
|
|
386
500
|
const result = await execute();
|
|
387
501
|
const hash = extractHash(result);
|
|
388
|
-
|
|
389
|
-
|
|
502
|
+
// For raw transactions, we can parse full data immediately
|
|
503
|
+
const knownTx = createKnownTrackedTransactionFromRaw(parsedTx, from, hash, metadata, walletClient.chain?.id, broadcastTimestampMs);
|
|
504
|
+
// Emit as KnownTrackedTransaction since we have all data
|
|
505
|
+
emitter.emit('transaction:broadcasted', knownTx);
|
|
506
|
+
// We do not emit fetched as the tx is already known
|
|
390
507
|
return result;
|
|
391
508
|
}
|
|
392
509
|
return {
|
|
@@ -406,22 +523,26 @@ function createAutoPopulateBuilder() {
|
|
|
406
523
|
functionName: args.functionName,
|
|
407
524
|
args: args.args,
|
|
408
525
|
};
|
|
526
|
+
const intendedParams = extractIntendedParamsFromWriteContract(args);
|
|
409
527
|
return executeTrackedTransaction({
|
|
410
528
|
account: normalizeAccount(args.account),
|
|
411
529
|
nonce,
|
|
412
530
|
metadata: finalMetadata,
|
|
413
531
|
restArgs: writeArgs,
|
|
532
|
+
intendedParams,
|
|
414
533
|
execute: (argsWithNonce) => walletClient.writeContract(argsWithNonce),
|
|
415
534
|
extractHash: (hash) => hash,
|
|
416
535
|
});
|
|
417
536
|
},
|
|
418
537
|
async sendTransaction(args) {
|
|
419
538
|
const { metadata, nonce, ...sendArgs } = args;
|
|
539
|
+
const intendedParams = extractIntendedParamsFromSendTransaction(args);
|
|
420
540
|
return executeTrackedTransaction({
|
|
421
541
|
account: normalizeAccount(args.account),
|
|
422
542
|
nonce,
|
|
423
543
|
metadata: metadata,
|
|
424
544
|
restArgs: sendArgs,
|
|
545
|
+
intendedParams,
|
|
425
546
|
execute: (argsWithNonce) => walletClient.sendTransaction(argsWithNonce),
|
|
426
547
|
extractHash: (hash) => hash,
|
|
427
548
|
});
|
|
@@ -449,22 +570,26 @@ function createAutoPopulateBuilder() {
|
|
|
449
570
|
functionName: args.functionName,
|
|
450
571
|
args: args.args,
|
|
451
572
|
};
|
|
573
|
+
const intendedParams = extractIntendedParamsFromWriteContract(args);
|
|
452
574
|
return executeTrackedTransaction({
|
|
453
575
|
account: normalizeAccount(args.account),
|
|
454
576
|
nonce,
|
|
455
577
|
metadata: finalMetadata,
|
|
456
578
|
restArgs: writeArgs,
|
|
579
|
+
intendedParams,
|
|
457
580
|
execute: (argsWithNonce) => walletClient.writeContractSync(argsWithNonce),
|
|
458
581
|
extractHash: (receipt) => receipt.transactionHash,
|
|
459
582
|
});
|
|
460
583
|
},
|
|
461
584
|
async sendTransactionSync(args) {
|
|
462
585
|
const { metadata, nonce, ...sendArgs } = args;
|
|
586
|
+
const intendedParams = extractIntendedParamsFromSendTransaction(args);
|
|
463
587
|
return executeTrackedTransaction({
|
|
464
588
|
account: normalizeAccount(args.account),
|
|
465
589
|
nonce,
|
|
466
590
|
metadata: metadata,
|
|
467
591
|
restArgs: sendArgs,
|
|
592
|
+
intendedParams,
|
|
468
593
|
execute: (argsWithNonce) => walletClient.sendTransactionSync(argsWithNonce),
|
|
469
594
|
extractHash: (receipt) => receipt.transactionHash,
|
|
470
595
|
});
|
|
@@ -481,8 +606,8 @@ function createAutoPopulateBuilder() {
|
|
|
481
606
|
// ============================================
|
|
482
607
|
// Event subscription methods
|
|
483
608
|
// ============================================
|
|
484
|
-
|
|
485
|
-
|
|
609
|
+
on: emitter.on.bind(emitter),
|
|
610
|
+
off: emitter.off.bind(emitter),
|
|
486
611
|
};
|
|
487
612
|
},
|
|
488
613
|
};
|