@etherkit/viem-tx-tracker 0.0.2 → 0.0.4
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 +3 -3
- package/dist/TrackedWalletClient.d.ts +42 -6
- package/dist/TrackedWalletClient.d.ts.map +1 -1
- package/dist/TrackedWalletClient.js +235 -220
- package/dist/TrackedWalletClient.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +61 -96
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/TrackedWalletClient.ts +455 -373
- package/src/index.ts +5 -12
- package/src/types.ts +68 -106
|
@@ -17,13 +17,13 @@ import {
|
|
|
17
17
|
import {Emitter} from 'radiate';
|
|
18
18
|
import type {
|
|
19
19
|
BlockTag,
|
|
20
|
+
MetadataField,
|
|
20
21
|
NonceOption,
|
|
21
22
|
TrackedRawTransactionParameters,
|
|
22
23
|
TrackedSendTransactionParameters,
|
|
23
24
|
TrackedTransaction,
|
|
24
25
|
TrackedWalletClient,
|
|
25
26
|
TrackedWriteContractParameters,
|
|
26
|
-
TransactionMetadata,
|
|
27
27
|
} from './types.js';
|
|
28
28
|
|
|
29
29
|
/**
|
|
@@ -71,402 +71,484 @@ interface TransactionContext {
|
|
|
71
71
|
intendedNonce: number;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Infer transport type from WalletClient
|
|
76
|
+
*/
|
|
77
|
+
type InferTransport<T> =
|
|
78
|
+
T extends WalletClient<infer TTransport, any, any> ? TTransport : Transport;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Infer chain type from WalletClient
|
|
82
|
+
*/
|
|
83
|
+
type InferChain<T> =
|
|
84
|
+
T extends WalletClient<any, infer TChain, any> ? TChain : Chain | undefined;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Infer account type from WalletClient
|
|
88
|
+
*/
|
|
89
|
+
type InferAccount<T> =
|
|
90
|
+
T extends WalletClient<any, any, infer TAccount>
|
|
91
|
+
? TAccount
|
|
92
|
+
: Account | undefined;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Builder interface returned by createTrackedWalletClient for the curried API.
|
|
96
|
+
*/
|
|
97
|
+
export interface TrackedWalletClientBuilder<TMetadata> {
|
|
98
|
+
/**
|
|
99
|
+
* Create the tracked wallet client using the provided wallet and public clients.
|
|
100
|
+
*
|
|
101
|
+
* @param walletClient - The underlying viem WalletClient
|
|
102
|
+
* @param publicClient - A PublicClient for nonce fetching and tx verification
|
|
103
|
+
* @returns A TrackedWalletClient instance
|
|
104
|
+
*/
|
|
105
|
+
using<TClient extends WalletClient>(
|
|
106
|
+
walletClient: TClient,
|
|
107
|
+
publicClient: PublicClient,
|
|
108
|
+
): TrackedWalletClient<
|
|
109
|
+
TMetadata,
|
|
110
|
+
InferTransport<TClient>,
|
|
111
|
+
InferChain<TClient>,
|
|
112
|
+
InferAccount<TClient>
|
|
113
|
+
>;
|
|
114
|
+
}
|
|
115
|
+
|
|
74
116
|
/**
|
|
75
117
|
* Create a tracked wallet client that wraps a viem WalletClient.
|
|
76
118
|
*
|
|
77
119
|
* The tracked client provides the same API as WalletClient but with:
|
|
78
|
-
* -
|
|
120
|
+
* - Metadata field for transaction tracking (required unless TMetadata includes undefined)
|
|
79
121
|
* - Automatic nonce fetching (with 'pending' by default)
|
|
80
122
|
* - Post-broadcast transaction verification
|
|
81
|
-
* -
|
|
123
|
+
* - Event emission for tracking
|
|
124
|
+
*
|
|
125
|
+
* @typeParam TMetadata - The metadata type. Use `MyMeta | undefined` to make metadata optional.
|
|
126
|
+
* @returns A builder with a `.using()` method to provide the wallet and public clients
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```typescript
|
|
130
|
+
* // With required metadata
|
|
131
|
+
* const tracked = createTrackedWalletClient<{purpose: string}>()
|
|
132
|
+
* .using(walletClient, publicClient);
|
|
82
133
|
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
134
|
+
* // With optional metadata
|
|
135
|
+
* const tracked = createTrackedWalletClient<{purpose: string} | undefined>()
|
|
136
|
+
* .using(walletClient, publicClient);
|
|
137
|
+
* ```
|
|
86
138
|
*/
|
|
87
139
|
export function createTrackedWalletClient<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
>(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
): TrackedWalletClient<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
140
|
+
TMetadata,
|
|
141
|
+
>(): TrackedWalletClientBuilder<TMetadata> {
|
|
142
|
+
return {
|
|
143
|
+
using<TClient extends WalletClient>(
|
|
144
|
+
walletClient: TClient,
|
|
145
|
+
publicClient: PublicClient,
|
|
146
|
+
): TrackedWalletClient<
|
|
147
|
+
TMetadata,
|
|
148
|
+
InferTransport<TClient>,
|
|
149
|
+
InferChain<TClient>,
|
|
150
|
+
InferAccount<TClient>
|
|
151
|
+
> {
|
|
152
|
+
// Type aliases for internal use
|
|
153
|
+
type TTransport = InferTransport<TClient>;
|
|
154
|
+
type TChain = InferChain<TClient>;
|
|
155
|
+
type TAccount = InferAccount<TClient>;
|
|
156
|
+
|
|
157
|
+
// Create emitter for transaction broadcast events
|
|
158
|
+
const emitter = new Emitter<{
|
|
159
|
+
'transaction:broadcasted': TrackedTransaction<TMetadata>;
|
|
160
|
+
}>();
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Resolve the nonce to use for a transaction.
|
|
164
|
+
*
|
|
165
|
+
* @param nonceOption - The nonce option provided by the caller
|
|
166
|
+
* @param from - The sender address
|
|
167
|
+
* @returns The resolved nonce number
|
|
168
|
+
*/
|
|
169
|
+
async function resolveNonce(
|
|
170
|
+
nonceOption: NonceOption | undefined,
|
|
171
|
+
from: Address,
|
|
172
|
+
): Promise<number> {
|
|
173
|
+
if (typeof nonceOption === 'number') {
|
|
174
|
+
// Explicit number - use as-is
|
|
175
|
+
return nonceOption;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Block tag (string) or undefined - fetch from chain
|
|
179
|
+
const blockTag = isBlockTag(nonceOption) ? nonceOption : 'pending';
|
|
180
|
+
return await publicClient.getTransactionCount({
|
|
181
|
+
address: from,
|
|
182
|
+
blockTag,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
99
185
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
186
|
+
/**
|
|
187
|
+
* Extract common transaction context (account, nonce) from request args.
|
|
188
|
+
* This is the shared logic between all transaction methods.
|
|
189
|
+
*
|
|
190
|
+
* @param args - The transaction args containing account and nonce options
|
|
191
|
+
* @returns TransactionContext with resolved from address and nonce
|
|
192
|
+
*/
|
|
193
|
+
async function extractTransactionContext(args: {
|
|
194
|
+
account?: Account | Address;
|
|
195
|
+
nonce?: NonceOption;
|
|
196
|
+
}): Promise<TransactionContext> {
|
|
197
|
+
// Get account/from address
|
|
198
|
+
const account = args.account ?? walletClient.account;
|
|
199
|
+
const from = resolveAccountAddress(account);
|
|
200
|
+
|
|
201
|
+
if (!from) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
'[TrackedWalletClient] No account available. ' +
|
|
204
|
+
'Provide an account in the request or configure the wallet client with an account.',
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Resolve nonce
|
|
209
|
+
const intendedNonce = await resolveNonce(args.nonce, from);
|
|
210
|
+
|
|
211
|
+
return {from, intendedNonce};
|
|
212
|
+
}
|
|
123
213
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
214
|
+
/**
|
|
215
|
+
* Extract transaction context from a serialized (signed) transaction.
|
|
216
|
+
* Parses the transaction and recovers the sender address.
|
|
217
|
+
*
|
|
218
|
+
* @param serializedTransaction - The RLP-encoded signed transaction
|
|
219
|
+
* @returns TransactionContext with from address and nonce
|
|
220
|
+
*/
|
|
221
|
+
async function extractRawTransactionContext(
|
|
222
|
+
serializedTransaction: TransactionSerialized,
|
|
223
|
+
): Promise<TransactionContext> {
|
|
224
|
+
// Parse the serialized transaction to get the nonce
|
|
225
|
+
const parsedTx = parseTransaction(serializedTransaction);
|
|
226
|
+
|
|
227
|
+
if (parsedTx.nonce === undefined) {
|
|
228
|
+
throw new Error(
|
|
229
|
+
'[TrackedWalletClient] Could not extract nonce from serialized transaction.',
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Recover the sender address from the signature
|
|
234
|
+
const from = await recoverTransactionAddress({
|
|
235
|
+
serializedTransaction,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
from,
|
|
240
|
+
intendedNonce: parsedTx.nonce,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
151
243
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
244
|
+
/**
|
|
245
|
+
* Fetch the transaction after broadcast to verify nonce.
|
|
246
|
+
* Logs a warning if the nonce was overridden or if tx cannot be found.
|
|
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
|
|
251
|
+
*/
|
|
252
|
+
async function verifyTransactionNonce(
|
|
253
|
+
hash: Hash,
|
|
254
|
+
intendedNonce: number,
|
|
255
|
+
): Promise<number> {
|
|
256
|
+
try {
|
|
257
|
+
const tx = await publicClient.getTransaction({hash});
|
|
258
|
+
const actualNonce = tx.nonce;
|
|
259
|
+
|
|
260
|
+
if (actualNonce !== intendedNonce) {
|
|
261
|
+
console.warn(
|
|
262
|
+
`[TrackedWalletClient] Nonce mismatch: intended ${intendedNonce}, actual ${actualNonce}. ` +
|
|
263
|
+
`Wallet may have overridden the nonce.`,
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return actualNonce;
|
|
268
|
+
} catch (fetchError) {
|
|
269
|
+
// Transaction not found in mempool/chain yet
|
|
270
|
+
console.warn(
|
|
271
|
+
`[TrackedWalletClient] Could not fetch tx ${hash} after broadcast. ` +
|
|
272
|
+
`It may not be in the mempool yet.`,
|
|
273
|
+
);
|
|
274
|
+
return intendedNonce;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
181
277
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
console.warn(
|
|
200
|
-
`[TrackedWalletClient] Nonce mismatch: intended ${intendedNonce}, actual ${actualNonce}. ` +
|
|
201
|
-
`Wallet may have overridden the nonce.`,
|
|
202
|
-
);
|
|
278
|
+
/**
|
|
279
|
+
* Create a tracked transaction record.
|
|
280
|
+
*/
|
|
281
|
+
function createTrackedTransactionRecord(
|
|
282
|
+
txHash: Hash,
|
|
283
|
+
from: Address,
|
|
284
|
+
nonce: number,
|
|
285
|
+
metadata: TMetadata,
|
|
286
|
+
): TrackedTransaction<TMetadata> {
|
|
287
|
+
return {
|
|
288
|
+
hash: txHash,
|
|
289
|
+
from,
|
|
290
|
+
nonce,
|
|
291
|
+
chainId: walletClient.chain?.id,
|
|
292
|
+
metadata,
|
|
293
|
+
broadcastTimestampMs: Date.now(),
|
|
294
|
+
};
|
|
203
295
|
}
|
|
204
296
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
297
|
+
/**
|
|
298
|
+
* Common wrapper for transaction methods that broadcast (sendTransaction, writeContract).
|
|
299
|
+
* Handles nonce resolution, underlying call, post-broadcast verification, and tracking record creation.
|
|
300
|
+
*/
|
|
301
|
+
async function executeTrackedTransaction<T, R>(args: {
|
|
302
|
+
account?: Account | Address;
|
|
303
|
+
nonce?: NonceOption;
|
|
304
|
+
metadata: TMetadata;
|
|
305
|
+
restArgs: T;
|
|
306
|
+
execute: (argsWithNonce: T & {nonce: number}) => Promise<R>;
|
|
307
|
+
extractHash: (result: R) => Hash;
|
|
308
|
+
}): Promise<R> {
|
|
309
|
+
const {metadata, restArgs, execute, extractHash} = args;
|
|
310
|
+
|
|
311
|
+
// Extract common context
|
|
312
|
+
const {from, intendedNonce} = await extractTransactionContext(args);
|
|
313
|
+
|
|
314
|
+
// Execute the underlying transaction with nonce injected
|
|
315
|
+
const result = await execute({
|
|
316
|
+
...restArgs,
|
|
317
|
+
nonce: intendedNonce,
|
|
318
|
+
} as T & {
|
|
319
|
+
nonce: number;
|
|
320
|
+
});
|
|
321
|
+
const hash = extractHash(result);
|
|
322
|
+
|
|
323
|
+
// Verify transaction and get actual nonce
|
|
324
|
+
const actualNonce = await verifyTransactionNonce(hash, intendedNonce);
|
|
325
|
+
|
|
326
|
+
// Create tracked transaction record
|
|
327
|
+
const trackedTx = createTrackedTransactionRecord(
|
|
328
|
+
hash,
|
|
329
|
+
from,
|
|
330
|
+
actualNonce,
|
|
331
|
+
metadata,
|
|
332
|
+
);
|
|
215
333
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
*/
|
|
219
|
-
function createTrackedTransaction<M extends TransactionMetadata>(
|
|
220
|
-
txHash: Hash,
|
|
221
|
-
from: Address,
|
|
222
|
-
nonce: number,
|
|
223
|
-
metadata: M | undefined,
|
|
224
|
-
request: unknown,
|
|
225
|
-
): TrackedTransaction<M> {
|
|
226
|
-
return {
|
|
227
|
-
trackingId: metadata?.id ?? generateTrackingId(),
|
|
228
|
-
txHash,
|
|
229
|
-
from,
|
|
230
|
-
nonce,
|
|
231
|
-
chainId: walletClient.chain?.id ?? 1,
|
|
232
|
-
metadata: (metadata ?? {}) as M,
|
|
233
|
-
initiatedAt: Date.now(),
|
|
234
|
-
request,
|
|
235
|
-
};
|
|
236
|
-
}
|
|
334
|
+
// Emit transaction broadcasted event
|
|
335
|
+
emitter.emit('transaction:broadcasted', trackedTx);
|
|
237
336
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
* Handles nonce resolution, underlying call, post-broadcast verification, and tracking record creation.
|
|
241
|
-
*/
|
|
242
|
-
async function executeTrackedTransaction<T, R>(args: {
|
|
243
|
-
account?: Account | Address;
|
|
244
|
-
nonce?: NonceOption;
|
|
245
|
-
metadata?: TransactionMetadata;
|
|
246
|
-
restArgs: T;
|
|
247
|
-
execute: (argsWithNonce: T & {nonce: number}) => Promise<R>;
|
|
248
|
-
extractHash: (result: R) => Hash;
|
|
249
|
-
}): Promise<R> {
|
|
250
|
-
const {metadata, restArgs, execute, extractHash} = args;
|
|
251
|
-
|
|
252
|
-
// Extract common context
|
|
253
|
-
const {from, intendedNonce} = await extractTransactionContext(args);
|
|
254
|
-
|
|
255
|
-
// Execute the underlying transaction with nonce injected
|
|
256
|
-
const result = await execute({...restArgs, nonce: intendedNonce} as T & {
|
|
257
|
-
nonce: number;
|
|
258
|
-
});
|
|
259
|
-
const hash = extractHash(result);
|
|
260
|
-
|
|
261
|
-
// Verify transaction and get actual nonce
|
|
262
|
-
const actualNonce = await verifyTransactionNonce(hash, intendedNonce);
|
|
263
|
-
|
|
264
|
-
// Create tracked transaction record
|
|
265
|
-
const trackedTx = createTrackedTransaction(
|
|
266
|
-
hash,
|
|
267
|
-
from,
|
|
268
|
-
actualNonce,
|
|
269
|
-
metadata,
|
|
270
|
-
restArgs,
|
|
271
|
-
);
|
|
272
|
-
|
|
273
|
-
// Emit transaction broadcasted event
|
|
274
|
-
emitter.emit('transaction:broadcasted', trackedTx);
|
|
275
|
-
|
|
276
|
-
return result;
|
|
277
|
-
}
|
|
337
|
+
return result;
|
|
338
|
+
}
|
|
278
339
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
// Execute the broadcast
|
|
297
|
-
const result = await execute();
|
|
298
|
-
const hash = extractHash(result);
|
|
299
|
-
|
|
300
|
-
// For raw transactions, the nonce is already embedded, so no verification needed
|
|
301
|
-
// (wallet cannot override nonce in an already-signed transaction)
|
|
302
|
-
|
|
303
|
-
// Create tracked transaction record
|
|
304
|
-
const trackedTx = createTrackedTransaction(
|
|
305
|
-
hash,
|
|
306
|
-
from,
|
|
307
|
-
intendedNonce,
|
|
308
|
-
metadata,
|
|
309
|
-
{serializedTransaction},
|
|
310
|
-
);
|
|
311
|
-
|
|
312
|
-
// Emit transaction broadcasted event
|
|
313
|
-
emitter.emit('transaction:broadcasted', trackedTx);
|
|
314
|
-
|
|
315
|
-
return result;
|
|
316
|
-
}
|
|
340
|
+
/**
|
|
341
|
+
* Common wrapper for raw transaction broadcasts (sendRawTransaction).
|
|
342
|
+
* Decodes the transaction to extract from/nonce, broadcasts, and creates tracking record.
|
|
343
|
+
*/
|
|
344
|
+
async function executeTrackedRawTransaction<R>(args: {
|
|
345
|
+
serializedTransaction: TransactionSerialized;
|
|
346
|
+
metadata: TMetadata;
|
|
347
|
+
execute: () => Promise<R>;
|
|
348
|
+
extractHash: (result: R) => Hash;
|
|
349
|
+
}): Promise<R> {
|
|
350
|
+
const {serializedTransaction, metadata, execute, extractHash} = args;
|
|
351
|
+
|
|
352
|
+
// Extract context from the serialized transaction
|
|
353
|
+
const {from, intendedNonce} = await extractRawTransactionContext(
|
|
354
|
+
serializedTransaction,
|
|
355
|
+
);
|
|
317
356
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
// ============================================
|
|
323
|
-
// Async methods (return hash)
|
|
324
|
-
// ============================================
|
|
325
|
-
|
|
326
|
-
async writeContract<
|
|
327
|
-
const TAbi extends Abi | readonly unknown[],
|
|
328
|
-
TFunctionName extends ContractFunctionName<
|
|
329
|
-
TAbi,
|
|
330
|
-
'nonpayable' | 'payable'
|
|
331
|
-
>,
|
|
332
|
-
TArgs extends ContractFunctionArgs<
|
|
333
|
-
TAbi,
|
|
334
|
-
'nonpayable' | 'payable',
|
|
335
|
-
TFunctionName
|
|
336
|
-
>,
|
|
337
|
-
TChainOverride extends Chain | undefined = undefined,
|
|
338
|
-
>(
|
|
339
|
-
args: TrackedWriteContractParameters<
|
|
340
|
-
TAbi,
|
|
341
|
-
TFunctionName,
|
|
342
|
-
TArgs,
|
|
343
|
-
TChain,
|
|
344
|
-
TAccount,
|
|
345
|
-
TChainOverride
|
|
346
|
-
>,
|
|
347
|
-
): Promise<Hash> {
|
|
348
|
-
const {metadata, nonce, ...writeArgs} = args;
|
|
349
|
-
|
|
350
|
-
return executeTrackedTransaction({
|
|
351
|
-
account: normalizeAccount(args.account),
|
|
352
|
-
nonce,
|
|
353
|
-
metadata,
|
|
354
|
-
restArgs: writeArgs,
|
|
355
|
-
execute: (argsWithNonce) =>
|
|
356
|
-
walletClient.writeContract(argsWithNonce as any),
|
|
357
|
-
extractHash: (hash) => hash,
|
|
358
|
-
});
|
|
359
|
-
},
|
|
357
|
+
// Execute the broadcast
|
|
358
|
+
const result = await execute();
|
|
359
|
+
const hash = extractHash(result);
|
|
360
360
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
): Promise<Hash> {
|
|
364
|
-
const {metadata, nonce, ...sendArgs} = args;
|
|
365
|
-
|
|
366
|
-
return executeTrackedTransaction({
|
|
367
|
-
account: normalizeAccount(args.account),
|
|
368
|
-
nonce,
|
|
369
|
-
metadata,
|
|
370
|
-
restArgs: sendArgs,
|
|
371
|
-
execute: (argsWithNonce) =>
|
|
372
|
-
walletClient.sendTransaction(argsWithNonce as any),
|
|
373
|
-
extractHash: (hash) => hash,
|
|
374
|
-
});
|
|
375
|
-
},
|
|
361
|
+
// For raw transactions, the nonce is already embedded, so no verification needed
|
|
362
|
+
// (wallet cannot override nonce in an already-signed transaction)
|
|
376
363
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
metadata,
|
|
385
|
-
execute: () => walletClient.sendRawTransaction({serializedTransaction}),
|
|
386
|
-
extractHash: (hash) => hash,
|
|
387
|
-
});
|
|
388
|
-
},
|
|
364
|
+
// Create tracked transaction record
|
|
365
|
+
const trackedTx = createTrackedTransactionRecord(
|
|
366
|
+
hash,
|
|
367
|
+
from,
|
|
368
|
+
intendedNonce,
|
|
369
|
+
metadata,
|
|
370
|
+
);
|
|
389
371
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
// ============================================
|
|
393
|
-
|
|
394
|
-
async writeContractSync<
|
|
395
|
-
const TAbi extends Abi | readonly unknown[],
|
|
396
|
-
TFunctionName extends ContractFunctionName<
|
|
397
|
-
TAbi,
|
|
398
|
-
'nonpayable' | 'payable'
|
|
399
|
-
>,
|
|
400
|
-
TArgs extends ContractFunctionArgs<
|
|
401
|
-
TAbi,
|
|
402
|
-
'nonpayable' | 'payable',
|
|
403
|
-
TFunctionName
|
|
404
|
-
>,
|
|
405
|
-
TChainOverride extends Chain | undefined = undefined,
|
|
406
|
-
>(
|
|
407
|
-
args: TrackedWriteContractParameters<
|
|
408
|
-
TAbi,
|
|
409
|
-
TFunctionName,
|
|
410
|
-
TArgs,
|
|
411
|
-
TChain,
|
|
412
|
-
TAccount,
|
|
413
|
-
TChainOverride
|
|
414
|
-
>,
|
|
415
|
-
): Promise<TransactionReceipt> {
|
|
416
|
-
const {metadata, nonce, ...writeArgs} = args;
|
|
417
|
-
|
|
418
|
-
return executeTrackedTransaction({
|
|
419
|
-
account: normalizeAccount(args.account),
|
|
420
|
-
nonce,
|
|
421
|
-
metadata,
|
|
422
|
-
restArgs: writeArgs,
|
|
423
|
-
execute: (argsWithNonce) =>
|
|
424
|
-
walletClient.writeContractSync(argsWithNonce as any),
|
|
425
|
-
extractHash: (receipt) => receipt.transactionHash,
|
|
426
|
-
});
|
|
427
|
-
},
|
|
372
|
+
// Emit transaction broadcasted event
|
|
373
|
+
emitter.emit('transaction:broadcasted', trackedTx);
|
|
428
374
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
>(
|
|
432
|
-
args: TrackedSendTransactionParameters<TChain, TAccount, TChainOverride>,
|
|
433
|
-
): Promise<TransactionReceipt> {
|
|
434
|
-
const {metadata, nonce, ...sendArgs} = args;
|
|
435
|
-
|
|
436
|
-
return executeTrackedTransaction({
|
|
437
|
-
account: normalizeAccount(args.account),
|
|
438
|
-
nonce,
|
|
439
|
-
metadata,
|
|
440
|
-
restArgs: sendArgs,
|
|
441
|
-
execute: (argsWithNonce) =>
|
|
442
|
-
walletClient.sendTransactionSync(argsWithNonce as any),
|
|
443
|
-
extractHash: (receipt) => receipt.transactionHash,
|
|
444
|
-
});
|
|
445
|
-
},
|
|
375
|
+
return result;
|
|
376
|
+
}
|
|
446
377
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
378
|
+
return {
|
|
379
|
+
walletClient: walletClient as unknown as WalletClient<
|
|
380
|
+
TTransport,
|
|
381
|
+
TChain,
|
|
382
|
+
TAccount
|
|
383
|
+
>,
|
|
384
|
+
publicClient,
|
|
385
|
+
|
|
386
|
+
// ============================================
|
|
387
|
+
// Async methods (return hash)
|
|
388
|
+
// ============================================
|
|
389
|
+
|
|
390
|
+
async writeContract<
|
|
391
|
+
const TAbi extends Abi | readonly unknown[],
|
|
392
|
+
TFunctionName extends ContractFunctionName<
|
|
393
|
+
TAbi,
|
|
394
|
+
'nonpayable' | 'payable'
|
|
395
|
+
>,
|
|
396
|
+
TArgs extends ContractFunctionArgs<
|
|
397
|
+
TAbi,
|
|
398
|
+
'nonpayable' | 'payable',
|
|
399
|
+
TFunctionName
|
|
400
|
+
>,
|
|
401
|
+
TChainOverride extends Chain | undefined = undefined,
|
|
402
|
+
>(
|
|
403
|
+
args: TrackedWriteContractParameters<
|
|
404
|
+
TMetadata,
|
|
405
|
+
TAbi,
|
|
406
|
+
TFunctionName,
|
|
407
|
+
TArgs,
|
|
408
|
+
TChain,
|
|
409
|
+
TAccount,
|
|
410
|
+
TChainOverride
|
|
411
|
+
>,
|
|
412
|
+
): Promise<Hash> {
|
|
413
|
+
const {metadata, nonce, ...writeArgs} = args;
|
|
414
|
+
|
|
415
|
+
return executeTrackedTransaction({
|
|
416
|
+
account: normalizeAccount(args.account),
|
|
417
|
+
nonce,
|
|
418
|
+
metadata: metadata as TMetadata,
|
|
419
|
+
restArgs: writeArgs,
|
|
420
|
+
execute: (argsWithNonce) =>
|
|
421
|
+
walletClient.writeContract(argsWithNonce as any),
|
|
422
|
+
extractHash: (hash) => hash,
|
|
423
|
+
});
|
|
424
|
+
},
|
|
425
|
+
|
|
426
|
+
async sendTransaction<
|
|
427
|
+
TChainOverride extends Chain | undefined = undefined,
|
|
428
|
+
>(
|
|
429
|
+
args: TrackedSendTransactionParameters<
|
|
430
|
+
TMetadata,
|
|
431
|
+
TChain,
|
|
432
|
+
TAccount,
|
|
433
|
+
TChainOverride
|
|
434
|
+
>,
|
|
435
|
+
): Promise<Hash> {
|
|
436
|
+
const {metadata, nonce, ...sendArgs} = args;
|
|
437
|
+
|
|
438
|
+
return executeTrackedTransaction({
|
|
439
|
+
account: normalizeAccount(args.account),
|
|
440
|
+
nonce,
|
|
441
|
+
metadata: metadata as TMetadata,
|
|
442
|
+
restArgs: sendArgs,
|
|
443
|
+
execute: (argsWithNonce) =>
|
|
444
|
+
walletClient.sendTransaction(argsWithNonce as any),
|
|
445
|
+
extractHash: (hash) => hash,
|
|
446
|
+
});
|
|
447
|
+
},
|
|
448
|
+
|
|
449
|
+
async sendRawTransaction(
|
|
450
|
+
args: TrackedRawTransactionParameters<TMetadata>,
|
|
451
|
+
): Promise<Hash> {
|
|
452
|
+
const {metadata, serializedTransaction} = args;
|
|
453
|
+
|
|
454
|
+
return executeTrackedRawTransaction({
|
|
455
|
+
serializedTransaction,
|
|
456
|
+
metadata: metadata as TMetadata,
|
|
457
|
+
execute: () =>
|
|
458
|
+
walletClient.sendRawTransaction({serializedTransaction}),
|
|
459
|
+
extractHash: (hash) => hash,
|
|
460
|
+
});
|
|
461
|
+
},
|
|
462
|
+
|
|
463
|
+
// ============================================
|
|
464
|
+
// Sync methods (return receipt, wait for confirmation)
|
|
465
|
+
// ============================================
|
|
466
|
+
|
|
467
|
+
async writeContractSync<
|
|
468
|
+
const TAbi extends Abi | readonly unknown[],
|
|
469
|
+
TFunctionName extends ContractFunctionName<
|
|
470
|
+
TAbi,
|
|
471
|
+
'nonpayable' | 'payable'
|
|
472
|
+
>,
|
|
473
|
+
TArgs extends ContractFunctionArgs<
|
|
474
|
+
TAbi,
|
|
475
|
+
'nonpayable' | 'payable',
|
|
476
|
+
TFunctionName
|
|
477
|
+
>,
|
|
478
|
+
TChainOverride extends Chain | undefined = undefined,
|
|
479
|
+
>(
|
|
480
|
+
args: TrackedWriteContractParameters<
|
|
481
|
+
TMetadata,
|
|
482
|
+
TAbi,
|
|
483
|
+
TFunctionName,
|
|
484
|
+
TArgs,
|
|
485
|
+
TChain,
|
|
486
|
+
TAccount,
|
|
487
|
+
TChainOverride
|
|
488
|
+
>,
|
|
489
|
+
): Promise<TransactionReceipt> {
|
|
490
|
+
const {metadata, nonce, ...writeArgs} = args;
|
|
491
|
+
|
|
492
|
+
return executeTrackedTransaction({
|
|
493
|
+
account: normalizeAccount(args.account),
|
|
494
|
+
nonce,
|
|
495
|
+
metadata: metadata as TMetadata,
|
|
496
|
+
restArgs: writeArgs,
|
|
497
|
+
execute: (argsWithNonce) =>
|
|
498
|
+
walletClient.writeContractSync(argsWithNonce as any),
|
|
499
|
+
extractHash: (receipt) => receipt.transactionHash,
|
|
500
|
+
});
|
|
501
|
+
},
|
|
502
|
+
|
|
503
|
+
async sendTransactionSync<
|
|
504
|
+
TChainOverride extends Chain | undefined = undefined,
|
|
505
|
+
>(
|
|
506
|
+
args: TrackedSendTransactionParameters<
|
|
507
|
+
TMetadata,
|
|
508
|
+
TChain,
|
|
509
|
+
TAccount,
|
|
510
|
+
TChainOverride
|
|
511
|
+
>,
|
|
512
|
+
): Promise<TransactionReceipt> {
|
|
513
|
+
const {metadata, nonce, ...sendArgs} = args;
|
|
514
|
+
|
|
515
|
+
return executeTrackedTransaction({
|
|
516
|
+
account: normalizeAccount(args.account),
|
|
517
|
+
nonce,
|
|
518
|
+
metadata: metadata as TMetadata,
|
|
519
|
+
restArgs: sendArgs,
|
|
520
|
+
execute: (argsWithNonce) =>
|
|
521
|
+
walletClient.sendTransactionSync(argsWithNonce as any),
|
|
522
|
+
extractHash: (receipt) => receipt.transactionHash,
|
|
523
|
+
});
|
|
524
|
+
},
|
|
525
|
+
|
|
526
|
+
async sendRawTransactionSync(
|
|
527
|
+
args: TrackedRawTransactionParameters<TMetadata>,
|
|
528
|
+
): Promise<TransactionReceipt> {
|
|
529
|
+
const {metadata, serializedTransaction} = args;
|
|
530
|
+
|
|
531
|
+
return executeTrackedRawTransaction({
|
|
532
|
+
serializedTransaction,
|
|
533
|
+
metadata: metadata as TMetadata,
|
|
534
|
+
execute: () =>
|
|
535
|
+
walletClient.sendRawTransactionSync({serializedTransaction}),
|
|
536
|
+
extractHash: (receipt) => receipt.transactionHash,
|
|
537
|
+
});
|
|
538
|
+
},
|
|
539
|
+
|
|
540
|
+
// ============================================
|
|
541
|
+
// Event subscription methods
|
|
542
|
+
// ============================================
|
|
543
|
+
|
|
544
|
+
onTransactionBroadcasted: (
|
|
545
|
+
listener: (event: TrackedTransaction<TMetadata>) => void,
|
|
546
|
+
) => emitter.on('transaction:broadcasted', listener),
|
|
547
|
+
|
|
548
|
+
offTransactionBroadcasted: (
|
|
549
|
+
listener: (event: TrackedTransaction<TMetadata>) => void,
|
|
550
|
+
) => emitter.off('transaction:broadcasted', listener),
|
|
551
|
+
};
|
|
459
552
|
},
|
|
460
|
-
|
|
461
|
-
// ============================================
|
|
462
|
-
// Event subscription methods
|
|
463
|
-
// ============================================
|
|
464
|
-
|
|
465
|
-
onTransactionBroadcasted: (listener: (event: TrackedTransaction) => void) =>
|
|
466
|
-
emitter.on('transaction:broadcasted', listener),
|
|
467
|
-
|
|
468
|
-
offTransactionBroadcasted: (
|
|
469
|
-
listener: (event: TrackedTransaction) => void,
|
|
470
|
-
) => emitter.off('transaction:broadcasted', listener),
|
|
471
553
|
};
|
|
472
554
|
}
|