@etherkit/viem-tx-tracker 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -12
- package/dist/TrackedWalletClient.d.ts.map +1 -1
- package/dist/TrackedWalletClient.js +94 -52
- package/dist/TrackedWalletClient.js.map +1 -1
- package/dist/types.d.ts +88 -11
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/TrackedWalletClient.ts +180 -67
- package/src/types.ts +93 -11
package/README.md
CHANGED
|
@@ -11,10 +11,11 @@ This package wraps a `WalletClient` with the same method names (`sendTransaction
|
|
|
11
11
|
- the facts observed at dispatch: `hash`, `from`, `nonce`, `to`, `value`, `data`, `broadcastTimestampMs`, the intended gas parameters and inferred transaction type
|
|
12
12
|
- `metadata`: whatever your application says the transaction means, in your own type
|
|
13
13
|
- `source`: an opaque marker of which signing route produced it, in your own type
|
|
14
|
+
- `correlation`: an opaque marker of which caller-side request a send answers, supplied per call
|
|
14
15
|
|
|
15
|
-
It also resolves the nonce for you (`pending` by default) and, once broadcast,
|
|
16
|
+
It also resolves the nonce for you (`pending` by default) and, once broadcast, reads the transaction back from the chain and emits a second event with the values that were actually recorded.
|
|
16
17
|
|
|
17
|
-
The tracker never interprets `metadata` or `
|
|
18
|
+
The tracker never interprets `metadata`, `source` or `correlation`. It carries them.
|
|
18
19
|
|
|
19
20
|
## Installation
|
|
20
21
|
|
|
@@ -44,7 +45,7 @@ tracked.on('transaction:broadcasted', (tx) => {
|
|
|
44
45
|
save(tx.hash, tx);
|
|
45
46
|
});
|
|
46
47
|
|
|
47
|
-
tracked.on('transaction:
|
|
48
|
+
tracked.on('transaction:known', (tx) => {
|
|
48
49
|
// Fires once the transaction has been read back from the chain,
|
|
49
50
|
// with the values the chain actually recorded.
|
|
50
51
|
save(tx.hash, tx);
|
|
@@ -128,6 +129,42 @@ Points worth knowing:
|
|
|
128
129
|
- It defaults to `undefined`, in which case `source` is simply `undefined` on every transaction.
|
|
129
130
|
- The tracker never inspects it. There is no wallet, EIP-6963 or connection-library knowledge in this package: the union is yours.
|
|
130
131
|
|
|
132
|
+
## Correlation: which request this send answers
|
|
133
|
+
|
|
134
|
+
A send is asynchronous in a way that matters: `transaction:broadcasted` fires after the wallet round trip, which includes a popup the user may read for a while, during which the page stays interactive and can issue further sends. So the handler has to work out which of its own in-flight requests the event belongs to.
|
|
135
|
+
|
|
136
|
+
`(from, nonce)` cannot answer that. Replacing or cancelling a stuck transaction deliberately creates a second operation at the *same* nonce, so two concurrent replacements collide and the handler silently attaches a broadcast to the wrong one.
|
|
137
|
+
|
|
138
|
+
Pass a `correlation` on the call and read it off the event:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
const correlation = crypto.randomUUID();
|
|
142
|
+
|
|
143
|
+
tracked.on('transaction:broadcasted', (tx) => {
|
|
144
|
+
// route this broadcast to the request that issued it, not to a (from, nonce) guess
|
|
145
|
+
if (tx.correlation) pendingRequests.get(tx.correlation)?.resolve(tx);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
await tracked.sendTransaction({to, value, metadata: {id: 'deposit-1'}, correlation});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
It is a sibling of `metadata` and `nonce` on every tracked send, never nested inside metadata. Three fields are now carried on a tracked transaction and they answer three different questions:
|
|
152
|
+
|
|
153
|
+
| Field | Question it answers | Supplied | Lifetime |
|
|
154
|
+
|-------|---------------------|----------|----------|
|
|
155
|
+
| `metadata` | What the application says the transaction **means** | Per call, in your own type | Persisted by consumers |
|
|
156
|
+
| `source` | **Which signing route** produced it, a fact observed at dispatch | Once, at client construction, in your own type | Persisted by consumers |
|
|
157
|
+
| `correlation` | Which **caller-side request** this send answers | Per call, as a `string` | Ephemeral plumbing, not persisted |
|
|
158
|
+
|
|
159
|
+
Points worth knowing:
|
|
160
|
+
|
|
161
|
+
- It is **not intended to be persisted**. That is the whole reason it is not part of `metadata`: metadata is what consumers store, so plumbing written there ends up in every stored record forever.
|
|
162
|
+
- It is **not an identity** for the transaction. `hash` is that.
|
|
163
|
+
- It is **meaningless outside the session that issued the send**. A value rehydrated from storage carries no information, because the requests it referred to are gone.
|
|
164
|
+
- It is typed `correlation?: string`, deliberately, rather than a generic type parameter: identity is all a consumer needs to route an event, and the value is not `unknown`, which would invite smuggling structured data into a field the tracker will never interpret.
|
|
165
|
+
- The tracker never inspects, interprets, validates or defaults it, exactly as with `source`. It is also removed from the arguments before they reach viem, so the wallet never sees an unrecognised field.
|
|
166
|
+
- It is optional everywhere. Omit it and `correlation` is simply `undefined` on the emitted transaction.
|
|
167
|
+
|
|
131
168
|
## Nonces
|
|
132
169
|
|
|
133
170
|
Every method takes `nonce` as either an exact number or a block tag:
|
|
@@ -145,7 +182,7 @@ The resolved nonce is injected into the request and recorded on the emitted tran
|
|
|
145
182
|
| Event | Payload | When |
|
|
146
183
|
|-------|---------|------|
|
|
147
184
|
| `transaction:broadcasted` | `TrackedTransaction<TMetadata, TSource>` | Immediately after the broadcast succeeds |
|
|
148
|
-
| `transaction:
|
|
185
|
+
| `transaction:known` | `KnownTrackedTransaction<TMetadata, TSource>` | Once the values are known to be final rather than intended |
|
|
149
186
|
|
|
150
187
|
```typescript
|
|
151
188
|
const unsubscribe = tracked.on('transaction:broadcasted', listener);
|
|
@@ -154,9 +191,16 @@ unsubscribe();
|
|
|
154
191
|
tracked.off('transaction:broadcasted', listener);
|
|
155
192
|
```
|
|
156
193
|
|
|
157
|
-
`transaction:
|
|
194
|
+
`transaction:known` says the values are the final ones, not the ones you intended. It deliberately names that promise rather than the mechanism, because the mechanism differs: for a normal send the transaction is read back from the chain, and for `sendRawTransaction` the values are parsed from the signed payload, which needs no network call at all. Both produce final values, so both emit the event, and you can persist on it alone.
|
|
195
|
+
|
|
196
|
+
It does not say the transaction was mined. It fires while the transaction is still in the mempool, so it is not a substitute for waiting on a receipt.
|
|
197
|
+
|
|
198
|
+
The two paths differ in reliability, which the name cannot express, so it is worth stating plainly:
|
|
199
|
+
|
|
200
|
+
- **Normal sends**: best effort. If the read fails (not in the mempool yet, network trouble) the event simply never fires and a warning is logged.
|
|
201
|
+
- **Raw sends**: always fires, immediately, since nothing can fail. The same object is delivered to `transaction:broadcasted` first, so a handler subscribed to both sees that transaction twice.
|
|
158
202
|
|
|
159
|
-
|
|
203
|
+
Treat `transaction:broadcasted` as the event you must handle, and `transaction:known` as a refinement that is usually but not always delivered.
|
|
160
204
|
|
|
161
205
|
## The transaction shape
|
|
162
206
|
|
|
@@ -178,6 +222,7 @@ type CommonFields = {
|
|
|
178
222
|
readonly broadcastTimestampMs: number;
|
|
179
223
|
readonly metadata: TMetadata;
|
|
180
224
|
readonly source: TSource;
|
|
225
|
+
readonly correlation?: string;
|
|
181
226
|
};
|
|
182
227
|
```
|
|
183
228
|
|
|
@@ -192,9 +237,9 @@ if (tx.txType === 'eip1559') {
|
|
|
192
237
|
}
|
|
193
238
|
```
|
|
194
239
|
|
|
195
|
-
For a broadcast transaction (`known: false`) the type is inferred from what you provided: `maxFeePerGas` gives `eip1559`, `gasPrice` with an `accessList` gives `eip2930`, `gasPrice` alone gives `legacy`, and nothing gives `txType: undefined`, meaning the wallet will decide. On a
|
|
240
|
+
For a broadcast transaction (`known: false`) the type is inferred from what you provided: `maxFeePerGas` gives `eip1559`, `gasPrice` with an `accessList` gives `eip2930`, `gasPrice` alone gives `legacy`, and nothing gives `txType: undefined`, meaning the wallet will decide. On a known transaction (`known: true`) the type and its gas values are the final ones, and every gas field is present.
|
|
196
241
|
|
|
197
|
-
`from` and `nonce` are re-read from the chain on `transaction:
|
|
242
|
+
`from` and `nonce` are re-read from the chain on `transaction:known`. `metadata`, `source` and `correlation` are not observable on chain, so they are carried through from dispatch unchanged, on every event that carries a transaction.
|
|
198
243
|
|
|
199
244
|
## API
|
|
200
245
|
|
|
@@ -214,20 +259,20 @@ Returns a builder with a single method, `.using(walletClient, publicClient)`.
|
|
|
214
259
|
|--------|---------|
|
|
215
260
|
| `writeContract(args)` | `Promise<Hash>` |
|
|
216
261
|
| `sendTransaction(args)` | `Promise<Hash>` |
|
|
217
|
-
| `sendRawTransaction({serializedTransaction, metadata})` | `Promise<Hash>` |
|
|
262
|
+
| `sendRawTransaction({serializedTransaction, metadata, correlation})` | `Promise<Hash>` |
|
|
218
263
|
| `writeContractSync(args)` | `Promise<TransactionReceipt>` |
|
|
219
264
|
| `sendTransactionSync(args)` | `Promise<TransactionReceipt>` |
|
|
220
|
-
| `sendRawTransactionSync({serializedTransaction, metadata})` | `Promise<TransactionReceipt>` |
|
|
265
|
+
| `sendRawTransactionSync({serializedTransaction, metadata, correlation})` | `Promise<TransactionReceipt>` |
|
|
221
266
|
| `on(event, listener)` | `() => void` (unsubscribe) |
|
|
222
267
|
| `off(event, listener)` | `void` |
|
|
223
268
|
|
|
224
|
-
The arguments are viem's, with `nonce` widened to accept a block tag and
|
|
269
|
+
The arguments are viem's, with `nonce` widened to accept a block tag and two fields added: `metadata` and the optional `correlation`. Both are stripped from the arguments before they are handed to viem. The underlying clients stay reachable as `tracked.walletClient` and `tracked.publicClient`.
|
|
225
270
|
|
|
226
271
|
The account comes from the call (`account`) or from the wallet client. If neither has one, the call throws.
|
|
227
272
|
|
|
228
273
|
### Types
|
|
229
274
|
|
|
230
|
-
`TrackedTransaction`, `KnownTrackedTransaction`, `UnknownTrackedTransaction`, `TrackedWalletClient`, `TrackedWalletClientAutoPopulate`, `TrackedWalletClientEvents`, `PopulatedMetadata`, `FunctionCallMetadata`, `UnknownTypeMetadata`, `NonceOption`, `BlockTag`, `AccessList`, `IntendedGasParameters`, `CreateTrackedWalletClientOptions`.
|
|
275
|
+
`TrackedTransaction`, `KnownTrackedTransaction`, `UnknownTrackedTransaction`, `TrackedWalletClient`, `TrackedWalletClientAutoPopulate`, `TrackedWalletClientEvents`, `PopulatedMetadata`, `FunctionCallMetadata`, `UnknownTypeMetadata`, `NonceOption`, `BlockTag`, `AccessList`, `IntendedGasParameters`, `CreateTrackedWalletClientOptions`, `CorrelationField`.
|
|
231
276
|
|
|
232
277
|
`TrackedWalletClientType` is a convenience for declaring a client variable without spelling out the whole interface:
|
|
233
278
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TrackedWalletClient.d.ts","sourceRoot":"","sources":["../src/TrackedWalletClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAMN,KAAK,OAAO,EAEZ,KAAK,KAAK,EAIV,KAAK,YAAY,EAIjB,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,MAAM,MAAM,CAAC;AAEd,OAAO,KAAK,EAGX,gCAAgC,EAIhC,iBAAiB,EAIjB,mBAAmB,EACnB,+BAA+B,EAI/B,MAAM,YAAY,CAAC;AA2EpB;;;;;;;;;;GAUG;AACH,KAAK,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"TrackedWalletClient.d.ts","sourceRoot":"","sources":["../src/TrackedWalletClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAMN,KAAK,OAAO,EAEZ,KAAK,KAAK,EAIV,KAAK,YAAY,EAIjB,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,MAAM,MAAM,CAAC;AAEd,OAAO,KAAK,EAGX,gCAAgC,EAIhC,iBAAiB,EAIjB,mBAAmB,EACnB,+BAA+B,EAI/B,MAAM,YAAY,CAAC;AA2EpB;;;;;;;;;;GAUG;AACH,KAAK,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;AAsVvD;;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,EAAE,OAAO,GAAG,SAAS;IACzE;;;;;;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,EACrB,OAAO,CACP,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,WAAW,sCAAsC,CACtD,SAAS,EACT,OAAO,GAAG,SAAS;IAEnB;;;;;;;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,EACrB,OAAO,CACP,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAGH,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,OAAO,GAAG,SAAS,EACvE,GAAG,IAAI,EAAE,SAAS,SAAS,OAAO,GAC/B;IACA,OAAO,CAAC,EAAE,gCAAgC,CACzC,KAAK,EACL,aAAa,CAAC,OAAO,CAAC,CACtB;CACD,GACA,CAAC,OAAO,EAAE,gCAAgC,CAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,GAC3E,0BAA0B,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;AAGlD,wBAAgB,yBAAyB,CACxC,OAAO,EAAE,gCAAgC,CAAC,IAAI,CAAC,GAC7C,sCAAsC,CAAC,iBAAiB,CAAC,CAAC;AAG7D,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,OAAO,GAAG,SAAS,EACvE,OAAO,EAAE,gCAAgC,CAAC,IAAI,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC,GACrE,sCAAsC,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC"}
|
|
@@ -55,7 +55,7 @@ function resolveSource(source) {
|
|
|
55
55
|
* Create an UnknownTrackedTransaction for immediate emission.
|
|
56
56
|
* Populates all known intended values from the transaction parameters.
|
|
57
57
|
*/
|
|
58
|
-
function createUnknownTrackedTransaction(hash, from, nonce, chainId,
|
|
58
|
+
function createUnknownTrackedTransaction(hash, from, nonce, chainId, carried, broadcastTimestampMs, params) {
|
|
59
59
|
const base = {
|
|
60
60
|
known: false,
|
|
61
61
|
chainId,
|
|
@@ -63,8 +63,7 @@ function createUnknownTrackedTransaction(hash, from, nonce, chainId, metadata, s
|
|
|
63
63
|
from,
|
|
64
64
|
nonce,
|
|
65
65
|
broadcastTimestampMs,
|
|
66
|
-
|
|
67
|
-
source,
|
|
66
|
+
...carried,
|
|
68
67
|
to: params.to,
|
|
69
68
|
value: params.value,
|
|
70
69
|
data: params.data,
|
|
@@ -158,10 +157,10 @@ function extractTransactionTypeFields(tx) {
|
|
|
158
157
|
* Create a KnownTrackedTransaction from a fetched transaction.
|
|
159
158
|
*
|
|
160
159
|
* Note: this object is rebuilt from the chain fetch, not copied from the
|
|
161
|
-
* broadcasted one, so every carried-but-not-observed field (metadata, source
|
|
162
|
-
* must be threaded in explicitly.
|
|
160
|
+
* broadcasted one, so every carried-but-not-observed field (metadata, source,
|
|
161
|
+
* correlation) must be threaded in explicitly.
|
|
163
162
|
*/
|
|
164
|
-
function createKnownTrackedTransaction(tx,
|
|
163
|
+
function createKnownTrackedTransaction(tx, carried, broadcastTimestampMs) {
|
|
165
164
|
const base = {
|
|
166
165
|
known: true,
|
|
167
166
|
hash: tx.hash,
|
|
@@ -171,8 +170,7 @@ function createKnownTrackedTransaction(tx, metadata, source, broadcastTimestampM
|
|
|
171
170
|
value: tx.value,
|
|
172
171
|
data: tx.input,
|
|
173
172
|
broadcastTimestampMs,
|
|
174
|
-
|
|
175
|
-
source,
|
|
173
|
+
...carried,
|
|
176
174
|
};
|
|
177
175
|
const typeFields = extractTransactionTypeFields(tx);
|
|
178
176
|
return {
|
|
@@ -183,7 +181,7 @@ function createKnownTrackedTransaction(tx, metadata, source, broadcastTimestampM
|
|
|
183
181
|
/**
|
|
184
182
|
* Create a KnownTrackedTransaction from a parsed raw transaction.
|
|
185
183
|
*/
|
|
186
|
-
function createKnownTrackedTransactionFromRaw(parsedTx, from, hash,
|
|
184
|
+
function createKnownTrackedTransactionFromRaw(parsedTx, from, hash, carried, chainId, broadcastTimestampMs) {
|
|
187
185
|
const base = {
|
|
188
186
|
known: true,
|
|
189
187
|
hash,
|
|
@@ -193,8 +191,7 @@ function createKnownTrackedTransactionFromRaw(parsedTx, from, hash, metadata, so
|
|
|
193
191
|
value: parsedTx.value ?? 0n,
|
|
194
192
|
data: parsedTx.data ?? '0x',
|
|
195
193
|
broadcastTimestampMs,
|
|
196
|
-
|
|
197
|
-
source,
|
|
194
|
+
...carried,
|
|
198
195
|
};
|
|
199
196
|
// Determine transaction type from parsed tx
|
|
200
197
|
if ('maxFeePerGas' in parsedTx && parsedTx.maxFeePerGas !== undefined) {
|
|
@@ -334,32 +331,40 @@ export function createTrackedWalletClient(options) {
|
|
|
334
331
|
return { from, intendedNonce };
|
|
335
332
|
}
|
|
336
333
|
/**
|
|
337
|
-
* Fetch full transaction data and emit transaction:
|
|
334
|
+
* Fetch full transaction data and emit transaction:known event.
|
|
338
335
|
* Non-blocking, runs in background. Does not throw.
|
|
339
336
|
*/
|
|
340
|
-
async function fetchAndEmitFullData(hash,
|
|
337
|
+
async function fetchAndEmitFullData(hash, carried, broadcastTimestampMs) {
|
|
341
338
|
try {
|
|
342
339
|
const tx = await publicClient.getTransaction({ hash });
|
|
343
|
-
const knownTx = createKnownTrackedTransaction(tx,
|
|
344
|
-
emitter.emit('transaction:
|
|
340
|
+
const knownTx = createKnownTrackedTransaction(tx, carried, broadcastTimestampMs);
|
|
341
|
+
emitter.emit('transaction:known', knownTx);
|
|
345
342
|
}
|
|
346
343
|
catch (error) {
|
|
347
|
-
// Log but don't throw - transaction:
|
|
344
|
+
// Log but don't throw - transaction:known simply won't fire
|
|
348
345
|
console.warn(`[TrackedWalletClient] Could not fetch tx ${hash}. ` +
|
|
349
|
-
`transaction:
|
|
346
|
+
`transaction:known event will not be emitted. Error: ${error}`);
|
|
350
347
|
}
|
|
351
348
|
}
|
|
352
349
|
/**
|
|
353
350
|
* Common wrapper for transaction methods that broadcast (sendTransaction, writeContract).
|
|
354
351
|
* Emits transaction:broadcasted immediately with intended values,
|
|
355
|
-
* then fetches and emits transaction:
|
|
352
|
+
* then fetches and emits transaction:known with actual values.
|
|
356
353
|
*/
|
|
357
354
|
async function executeTrackedTransaction(args) {
|
|
358
|
-
const { metadata, restArgs, intendedParams, execute, extractHash } = args;
|
|
355
|
+
const { metadata, correlation, restArgs, intendedParams, execute, extractHash, } = args;
|
|
359
356
|
const broadcastTimestampMs = clock();
|
|
360
357
|
// Stamped here, in the same step as from/nonce/broadcastTimestampMs.
|
|
361
358
|
// A thunk is re-evaluated on every send, never captured once.
|
|
362
359
|
const source = resolveSource(sourceOption);
|
|
360
|
+
// The carried values travel as one bundle from here on: metadata (what
|
|
361
|
+
// the tx means), source (which route signed) and correlation (which
|
|
362
|
+
// caller-side request this send answers). None is interpreted.
|
|
363
|
+
const carried = {
|
|
364
|
+
metadata,
|
|
365
|
+
source,
|
|
366
|
+
correlation,
|
|
367
|
+
};
|
|
363
368
|
// Extract common context
|
|
364
369
|
const { from, intendedNonce } = await extractTransactionContext(args);
|
|
365
370
|
// Execute the underlying transaction with nonce injected
|
|
@@ -369,10 +374,10 @@ export function createTrackedWalletClient(options) {
|
|
|
369
374
|
});
|
|
370
375
|
const hash = extractHash(result);
|
|
371
376
|
// Emit transaction:broadcasted immediately with intended values
|
|
372
|
-
const unknownTx = createUnknownTrackedTransaction(hash, from, intendedNonce, walletClient.chain?.id,
|
|
377
|
+
const unknownTx = createUnknownTrackedTransaction(hash, from, intendedNonce, walletClient.chain?.id, carried, broadcastTimestampMs, intendedParams);
|
|
373
378
|
emitter.emit('transaction:broadcasted', unknownTx);
|
|
374
|
-
// Fire-and-forget: fetch full data and emit transaction:
|
|
375
|
-
fetchAndEmitFullData(hash,
|
|
379
|
+
// Fire-and-forget: fetch full data and emit transaction:known
|
|
380
|
+
fetchAndEmitFullData(hash, carried, broadcastTimestampMs);
|
|
376
381
|
return result;
|
|
377
382
|
}
|
|
378
383
|
/**
|
|
@@ -381,22 +386,30 @@ export function createTrackedWalletClient(options) {
|
|
|
381
386
|
* Emits KnownTrackedTransaction directly to transaction:broadcasted.
|
|
382
387
|
*/
|
|
383
388
|
async function executeTrackedRawTransaction(args) {
|
|
384
|
-
const { serializedTransaction, metadata, execute, extractHash } = args;
|
|
389
|
+
const { serializedTransaction, metadata, correlation, execute, extractHash, } = args;
|
|
385
390
|
const broadcastTimestampMs = clock();
|
|
386
391
|
// Stamped here, in the same step as from/nonce/broadcastTimestampMs.
|
|
387
392
|
// A thunk is re-evaluated on every send, never captured once.
|
|
388
393
|
const source = resolveSource(sourceOption);
|
|
394
|
+
const carried = {
|
|
395
|
+
metadata,
|
|
396
|
+
source,
|
|
397
|
+
correlation,
|
|
398
|
+
};
|
|
389
399
|
const from = await recoverTransactionAddress({ serializedTransaction });
|
|
390
400
|
const parsedTx = parseTransaction(serializedTransaction);
|
|
391
401
|
// Execute the broadcast
|
|
392
402
|
const result = await execute();
|
|
393
403
|
const hash = extractHash(result);
|
|
394
404
|
// For raw transactions, we can parse full data immediately
|
|
395
|
-
const knownTx = createKnownTrackedTransactionFromRaw(parsedTx, from, hash,
|
|
405
|
+
const knownTx = createKnownTrackedTransactionFromRaw(parsedTx, from, hash, carried, walletClient.chain?.id, broadcastTimestampMs);
|
|
396
406
|
// Emit as KnownTrackedTransaction since we have all data
|
|
397
407
|
emitter.emit('transaction:broadcasted', knownTx);
|
|
398
|
-
//
|
|
399
|
-
|
|
408
|
+
// The values are final (parsed from the signed payload, not merely
|
|
409
|
+
// intended), which is exactly what transaction:known promises, so it
|
|
410
|
+
// is emitted here too: every tracked transaction reaches that event,
|
|
411
|
+
// and a consumer can persist on it alone.
|
|
412
|
+
emitter.emit('transaction:known', knownTx);
|
|
400
413
|
return result;
|
|
401
414
|
}
|
|
402
415
|
return {
|
|
@@ -406,12 +419,13 @@ export function createTrackedWalletClient(options) {
|
|
|
406
419
|
// Async methods (return hash)
|
|
407
420
|
// ============================================
|
|
408
421
|
async writeContract(args) {
|
|
409
|
-
const { metadata, nonce, ...writeArgs } = args;
|
|
422
|
+
const { metadata, correlation, nonce, ...writeArgs } = args;
|
|
410
423
|
const intendedParams = extractIntendedParamsFromWriteContract(args);
|
|
411
424
|
return executeTrackedTransaction({
|
|
412
425
|
account: normalizeAccount(args.account),
|
|
413
426
|
nonce,
|
|
414
427
|
metadata: metadata,
|
|
428
|
+
correlation,
|
|
415
429
|
restArgs: writeArgs,
|
|
416
430
|
intendedParams,
|
|
417
431
|
execute: (argsWithNonce) => walletClient.writeContract(argsWithNonce),
|
|
@@ -419,12 +433,13 @@ export function createTrackedWalletClient(options) {
|
|
|
419
433
|
});
|
|
420
434
|
},
|
|
421
435
|
async sendTransaction(args) {
|
|
422
|
-
const { metadata, nonce, ...sendArgs } = args;
|
|
436
|
+
const { metadata, correlation, nonce, ...sendArgs } = args;
|
|
423
437
|
const intendedParams = extractIntendedParamsFromSendTransaction(args);
|
|
424
438
|
return executeTrackedTransaction({
|
|
425
439
|
account: normalizeAccount(args.account),
|
|
426
440
|
nonce,
|
|
427
441
|
metadata: metadata,
|
|
442
|
+
correlation,
|
|
428
443
|
restArgs: sendArgs,
|
|
429
444
|
intendedParams,
|
|
430
445
|
execute: (argsWithNonce) => walletClient.sendTransaction(argsWithNonce),
|
|
@@ -432,10 +447,11 @@ export function createTrackedWalletClient(options) {
|
|
|
432
447
|
});
|
|
433
448
|
},
|
|
434
449
|
async sendRawTransaction(args) {
|
|
435
|
-
const { metadata, serializedTransaction } = args;
|
|
450
|
+
const { metadata, correlation, serializedTransaction } = args;
|
|
436
451
|
return executeTrackedRawTransaction({
|
|
437
452
|
serializedTransaction,
|
|
438
453
|
metadata: metadata,
|
|
454
|
+
correlation,
|
|
439
455
|
execute: () => walletClient.sendRawTransaction({ serializedTransaction }),
|
|
440
456
|
extractHash: (hash) => hash,
|
|
441
457
|
});
|
|
@@ -444,12 +460,13 @@ export function createTrackedWalletClient(options) {
|
|
|
444
460
|
// Sync methods (return receipt, wait for confirmation)
|
|
445
461
|
// ============================================
|
|
446
462
|
async writeContractSync(args) {
|
|
447
|
-
const { metadata, nonce, ...writeArgs } = args;
|
|
463
|
+
const { metadata, correlation, nonce, ...writeArgs } = args;
|
|
448
464
|
const intendedParams = extractIntendedParamsFromWriteContract(args);
|
|
449
465
|
return executeTrackedTransaction({
|
|
450
466
|
account: normalizeAccount(args.account),
|
|
451
467
|
nonce,
|
|
452
468
|
metadata: metadata,
|
|
469
|
+
correlation,
|
|
453
470
|
restArgs: writeArgs,
|
|
454
471
|
intendedParams,
|
|
455
472
|
execute: (argsWithNonce) => walletClient.writeContractSync(argsWithNonce),
|
|
@@ -457,12 +474,13 @@ export function createTrackedWalletClient(options) {
|
|
|
457
474
|
});
|
|
458
475
|
},
|
|
459
476
|
async sendTransactionSync(args) {
|
|
460
|
-
const { metadata, nonce, ...sendArgs } = args;
|
|
477
|
+
const { metadata, correlation, nonce, ...sendArgs } = args;
|
|
461
478
|
const intendedParams = extractIntendedParamsFromSendTransaction(args);
|
|
462
479
|
return executeTrackedTransaction({
|
|
463
480
|
account: normalizeAccount(args.account),
|
|
464
481
|
nonce,
|
|
465
482
|
metadata: metadata,
|
|
483
|
+
correlation,
|
|
466
484
|
restArgs: sendArgs,
|
|
467
485
|
intendedParams,
|
|
468
486
|
execute: (argsWithNonce) => walletClient.sendTransactionSync(argsWithNonce),
|
|
@@ -470,10 +488,11 @@ export function createTrackedWalletClient(options) {
|
|
|
470
488
|
});
|
|
471
489
|
},
|
|
472
490
|
async sendRawTransactionSync(args) {
|
|
473
|
-
const { metadata, serializedTransaction } = args;
|
|
491
|
+
const { metadata, correlation, serializedTransaction } = args;
|
|
474
492
|
return executeTrackedRawTransaction({
|
|
475
493
|
serializedTransaction,
|
|
476
494
|
metadata: metadata,
|
|
495
|
+
correlation,
|
|
477
496
|
execute: () => walletClient.sendRawTransactionSync({ serializedTransaction }),
|
|
478
497
|
extractHash: (receipt) => receipt.transactionHash,
|
|
479
498
|
});
|
|
@@ -523,19 +542,19 @@ function createAutoPopulateBuilder(clock, sourceOption) {
|
|
|
523
542
|
return { from, intendedNonce };
|
|
524
543
|
}
|
|
525
544
|
/**
|
|
526
|
-
* Fetch full transaction data and emit transaction:
|
|
545
|
+
* Fetch full transaction data and emit transaction:known event.
|
|
527
546
|
* Non-blocking, runs in background. Does not throw.
|
|
528
547
|
*/
|
|
529
|
-
async function fetchAndEmitFullData(hash,
|
|
548
|
+
async function fetchAndEmitFullData(hash, carried, broadcastTimestampMs) {
|
|
530
549
|
try {
|
|
531
550
|
const tx = await publicClient.getTransaction({ hash });
|
|
532
|
-
const knownTx = createKnownTrackedTransaction(tx,
|
|
533
|
-
emitter.emit('transaction:
|
|
551
|
+
const knownTx = createKnownTrackedTransaction(tx, carried, broadcastTimestampMs);
|
|
552
|
+
emitter.emit('transaction:known', knownTx);
|
|
534
553
|
}
|
|
535
554
|
catch (error) {
|
|
536
|
-
// Log but don't throw - transaction:
|
|
555
|
+
// Log but don't throw - transaction:known simply won't fire
|
|
537
556
|
console.warn(`[TrackedWalletClient] Could not fetch tx ${hash}. ` +
|
|
538
|
-
`transaction:
|
|
557
|
+
`transaction:known event will not be emitted. Error: ${error}`);
|
|
539
558
|
}
|
|
540
559
|
}
|
|
541
560
|
/**
|
|
@@ -561,14 +580,22 @@ function createAutoPopulateBuilder(clock, sourceOption) {
|
|
|
561
580
|
/**
|
|
562
581
|
* Common wrapper for transaction methods that broadcast.
|
|
563
582
|
* Emits transaction:broadcasted immediately with intended values,
|
|
564
|
-
* then fetches and emits transaction:
|
|
583
|
+
* then fetches and emits transaction:known with actual values.
|
|
565
584
|
*/
|
|
566
585
|
async function executeTrackedTransaction(args) {
|
|
567
|
-
const { metadata, restArgs, intendedParams, execute, extractHash } = args;
|
|
586
|
+
const { metadata, correlation, restArgs, intendedParams, execute, extractHash, } = args;
|
|
568
587
|
const broadcastTimestampMs = clock();
|
|
569
588
|
// Stamped here, in the same step as from/nonce/broadcastTimestampMs.
|
|
570
589
|
// A thunk is re-evaluated on every send, never captured once.
|
|
571
590
|
const source = resolveSource(sourceOption);
|
|
591
|
+
// The carried values travel as one bundle from here on: metadata (what
|
|
592
|
+
// the tx means), source (which route signed) and correlation (which
|
|
593
|
+
// caller-side request this send answers). None is interpreted.
|
|
594
|
+
const carried = {
|
|
595
|
+
metadata,
|
|
596
|
+
source,
|
|
597
|
+
correlation,
|
|
598
|
+
};
|
|
572
599
|
const { from, intendedNonce } = await extractTransactionContext(args);
|
|
573
600
|
const result = await execute({
|
|
574
601
|
...restArgs,
|
|
@@ -576,10 +603,10 @@ function createAutoPopulateBuilder(clock, sourceOption) {
|
|
|
576
603
|
});
|
|
577
604
|
const hash = extractHash(result);
|
|
578
605
|
// Emit transaction:broadcasted immediately with intended values
|
|
579
|
-
const unknownTx = createUnknownTrackedTransaction(hash, from, intendedNonce, walletClient.chain?.id,
|
|
606
|
+
const unknownTx = createUnknownTrackedTransaction(hash, from, intendedNonce, walletClient.chain?.id, carried, broadcastTimestampMs, intendedParams);
|
|
580
607
|
emitter.emit('transaction:broadcasted', unknownTx);
|
|
581
|
-
// Fire-and-forget: fetch full data and emit transaction:
|
|
582
|
-
fetchAndEmitFullData(hash,
|
|
608
|
+
// Fire-and-forget: fetch full data and emit transaction:known
|
|
609
|
+
fetchAndEmitFullData(hash, carried, broadcastTimestampMs);
|
|
583
610
|
return result;
|
|
584
611
|
}
|
|
585
612
|
/**
|
|
@@ -588,21 +615,30 @@ function createAutoPopulateBuilder(clock, sourceOption) {
|
|
|
588
615
|
* Emits KnownTrackedTransaction directly to transaction:broadcasted.
|
|
589
616
|
*/
|
|
590
617
|
async function executeTrackedRawTransaction(args) {
|
|
591
|
-
const { serializedTransaction, metadata, execute, extractHash } = args;
|
|
618
|
+
const { serializedTransaction, metadata, correlation, execute, extractHash, } = args;
|
|
592
619
|
const broadcastTimestampMs = clock();
|
|
593
620
|
// Stamped here, in the same step as from/nonce/broadcastTimestampMs.
|
|
594
621
|
// A thunk is re-evaluated on every send, never captured once.
|
|
595
622
|
const source = resolveSource(sourceOption);
|
|
623
|
+
const carried = {
|
|
624
|
+
metadata,
|
|
625
|
+
source,
|
|
626
|
+
correlation,
|
|
627
|
+
};
|
|
596
628
|
const from = await recoverTransactionAddress({ serializedTransaction });
|
|
597
629
|
const parsedTx = parseTransaction(serializedTransaction);
|
|
598
630
|
// Execute the broadcast
|
|
599
631
|
const result = await execute();
|
|
600
632
|
const hash = extractHash(result);
|
|
601
633
|
// For raw transactions, we can parse full data immediately
|
|
602
|
-
const knownTx = createKnownTrackedTransactionFromRaw(parsedTx, from, hash,
|
|
634
|
+
const knownTx = createKnownTrackedTransactionFromRaw(parsedTx, from, hash, carried, walletClient.chain?.id, broadcastTimestampMs);
|
|
603
635
|
// Emit as KnownTrackedTransaction since we have all data
|
|
604
636
|
emitter.emit('transaction:broadcasted', knownTx);
|
|
605
|
-
//
|
|
637
|
+
// The values are final (parsed from the signed payload, not merely
|
|
638
|
+
// intended), which is exactly what transaction:known promises, so it
|
|
639
|
+
// is emitted here too: every tracked transaction reaches that event,
|
|
640
|
+
// and a consumer can persist on it alone.
|
|
641
|
+
emitter.emit('transaction:known', knownTx);
|
|
606
642
|
return result;
|
|
607
643
|
}
|
|
608
644
|
return {
|
|
@@ -612,7 +648,7 @@ function createAutoPopulateBuilder(clock, sourceOption) {
|
|
|
612
648
|
// Async methods (return hash)
|
|
613
649
|
// ============================================
|
|
614
650
|
async writeContract(args) {
|
|
615
|
-
const { metadata: userMetadata, nonce, ...writeArgs } = args;
|
|
651
|
+
const { metadata: userMetadata, correlation, nonce, ...writeArgs } = args;
|
|
616
652
|
// Validate that user didn't provide operation, functionName or args
|
|
617
653
|
validateNoAutoPopulatedFieldsInMetadata(userMetadata);
|
|
618
654
|
// Auto-populate type, functionName and args
|
|
@@ -627,6 +663,7 @@ function createAutoPopulateBuilder(clock, sourceOption) {
|
|
|
627
663
|
account: normalizeAccount(args.account),
|
|
628
664
|
nonce,
|
|
629
665
|
metadata: finalMetadata,
|
|
666
|
+
correlation,
|
|
630
667
|
restArgs: writeArgs,
|
|
631
668
|
intendedParams,
|
|
632
669
|
execute: (argsWithNonce) => walletClient.writeContract(argsWithNonce),
|
|
@@ -634,12 +671,13 @@ function createAutoPopulateBuilder(clock, sourceOption) {
|
|
|
634
671
|
});
|
|
635
672
|
},
|
|
636
673
|
async sendTransaction(args) {
|
|
637
|
-
const { metadata, nonce, ...sendArgs } = args;
|
|
674
|
+
const { metadata, correlation, nonce, ...sendArgs } = args;
|
|
638
675
|
const intendedParams = extractIntendedParamsFromSendTransaction(args);
|
|
639
676
|
return executeTrackedTransaction({
|
|
640
677
|
account: normalizeAccount(args.account),
|
|
641
678
|
nonce,
|
|
642
679
|
metadata: metadata,
|
|
680
|
+
correlation,
|
|
643
681
|
restArgs: sendArgs,
|
|
644
682
|
intendedParams,
|
|
645
683
|
execute: (argsWithNonce) => walletClient.sendTransaction(argsWithNonce),
|
|
@@ -647,10 +685,11 @@ function createAutoPopulateBuilder(clock, sourceOption) {
|
|
|
647
685
|
});
|
|
648
686
|
},
|
|
649
687
|
async sendRawTransaction(args) {
|
|
650
|
-
const { metadata, serializedTransaction } = args;
|
|
688
|
+
const { metadata, correlation, serializedTransaction } = args;
|
|
651
689
|
return executeTrackedRawTransaction({
|
|
652
690
|
serializedTransaction,
|
|
653
691
|
metadata: metadata,
|
|
692
|
+
correlation,
|
|
654
693
|
execute: () => walletClient.sendRawTransaction({ serializedTransaction }),
|
|
655
694
|
extractHash: (hash) => hash,
|
|
656
695
|
});
|
|
@@ -659,7 +698,7 @@ function createAutoPopulateBuilder(clock, sourceOption) {
|
|
|
659
698
|
// Sync methods (return receipt, wait for confirmation)
|
|
660
699
|
// ============================================
|
|
661
700
|
async writeContractSync(args) {
|
|
662
|
-
const { metadata: userMetadata, nonce, ...writeArgs } = args;
|
|
701
|
+
const { metadata: userMetadata, correlation, nonce, ...writeArgs } = args;
|
|
663
702
|
// Validate that user didn't provide operation, functionName or args
|
|
664
703
|
validateNoAutoPopulatedFieldsInMetadata(userMetadata);
|
|
665
704
|
// Auto-populate type, functionName and args
|
|
@@ -674,6 +713,7 @@ function createAutoPopulateBuilder(clock, sourceOption) {
|
|
|
674
713
|
account: normalizeAccount(args.account),
|
|
675
714
|
nonce,
|
|
676
715
|
metadata: finalMetadata,
|
|
716
|
+
correlation,
|
|
677
717
|
restArgs: writeArgs,
|
|
678
718
|
intendedParams,
|
|
679
719
|
execute: (argsWithNonce) => walletClient.writeContractSync(argsWithNonce),
|
|
@@ -681,12 +721,13 @@ function createAutoPopulateBuilder(clock, sourceOption) {
|
|
|
681
721
|
});
|
|
682
722
|
},
|
|
683
723
|
async sendTransactionSync(args) {
|
|
684
|
-
const { metadata, nonce, ...sendArgs } = args;
|
|
724
|
+
const { metadata, correlation, nonce, ...sendArgs } = args;
|
|
685
725
|
const intendedParams = extractIntendedParamsFromSendTransaction(args);
|
|
686
726
|
return executeTrackedTransaction({
|
|
687
727
|
account: normalizeAccount(args.account),
|
|
688
728
|
nonce,
|
|
689
729
|
metadata: metadata,
|
|
730
|
+
correlation,
|
|
690
731
|
restArgs: sendArgs,
|
|
691
732
|
intendedParams,
|
|
692
733
|
execute: (argsWithNonce) => walletClient.sendTransactionSync(argsWithNonce),
|
|
@@ -694,10 +735,11 @@ function createAutoPopulateBuilder(clock, sourceOption) {
|
|
|
694
735
|
});
|
|
695
736
|
},
|
|
696
737
|
async sendRawTransactionSync(args) {
|
|
697
|
-
const { metadata, serializedTransaction } = args;
|
|
738
|
+
const { metadata, correlation, serializedTransaction } = args;
|
|
698
739
|
return executeTrackedRawTransaction({
|
|
699
740
|
serializedTransaction,
|
|
700
741
|
metadata: metadata,
|
|
742
|
+
correlation,
|
|
701
743
|
execute: () => walletClient.sendRawTransactionSync({ serializedTransaction }),
|
|
702
744
|
extractHash: (receipt) => receipt.transactionHash,
|
|
703
745
|
});
|