@prb/effect-evm 1.0.0-beta.6 → 1.0.0-beta.8

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.
@@ -8,233 +8,249 @@ import { TxReplacement } from "./replacement.js";
8
8
  import { makeTxTracker } from "./tracker.js";
9
9
  export class TxManager extends Context.Tag("ew3/TxManager")() {
10
10
  }
11
- export const TxManagerLive = Layer.effect(TxManager, Effect.gen(function* () {
12
- const publicClientService = yield* PublicClientService;
13
- const txReplacement = yield* TxReplacement;
14
- return {
15
- getConfirmations: Effect.fn("TxManager.getConfirmations")(function* (chainId, params) {
16
- const client = yield* publicClientService.get(chainId);
17
- return yield* Effect.tryPromise({
18
- catch: (cause) => new TransportError({
19
- cause,
20
- message: cause instanceof Error ? cause.message : "Failed to get transaction confirmations",
21
- url: client.transport.url ?? "",
22
- }),
23
- try: () => client.getTransactionConfirmations(params),
24
- }).pipe(Effect.withSpan(SpanNames.TX_GET_CONFIRMATIONS, {
25
- attributes: {
26
- chainId,
27
- hash: "hash" in params ? params.hash : params.transactionReceipt.transactionHash,
28
- },
29
- }));
30
- }),
31
- track: Effect.fn("TxManager.track")(function* (chainId, hash, providedPolicy) {
32
- const tracker = yield* makeTxTracker;
33
- const client = yield* publicClientService.get(chainId);
34
- const policy = {
35
- ...defaultPolicy,
36
- ...providedPolicy,
37
- replacement: {
38
- ...(defaultPolicy.replacement ?? {}),
39
- ...(providedPolicy?.replacement ?? {}),
40
- },
41
- };
42
- yield* tracker.set({ hash, status: "submitted" });
43
- yield* Effect.forkScoped(Effect.gen(function* () {
44
- const currentHashRef = yield* Ref.make(hash);
45
- const confirmationsRef = yield* Ref.make(0);
46
- const startedAtMsRef = yield* Ref.make(yield* Clock.currentTimeMillis);
47
- const autoAttemptsRef = yield* Ref.make(0);
48
- const autoReplacingRef = yield* Ref.make(false);
49
- const replacementStrategy = policy.replacement?.strategy ?? policy.replacementStrategy ?? "none";
50
- const stuckBlocks = policy.replacement?.stuckBlocks ?? 3;
51
- const stuckMs = policy.replacement?.stuckMs ?? DEFAULT_STUCK_TX_MS;
52
- const maxAttempts = policy.replacement?.maxAttempts ?? 1;
53
- const updatePendingState = (currentHash) => Effect.gen(function* () {
54
- const confirmations = yield* Ref.modify(confirmationsRef, (n) => [n + 1, n + 1]);
55
- yield* tracker.set({
56
- confirmations,
57
- hash: currentHash,
58
- status: "pending",
59
- });
60
- return confirmations;
61
- });
62
- const performAutoReplacement = (currentHash, now) => Ref.set(autoReplacingRef, true).pipe(Effect.zipRight((replacementStrategy === "cancel"
63
- ? txReplacement.cancel(chainId, currentHash, policy)
64
- : txReplacement.speedup(chainId, currentHash, policy)).pipe(Effect.either, Effect.ensuring(Ref.set(autoReplacingRef, false)), Effect.flatMap((replaced) => {
65
- if (replaced._tag === "Left") {
66
- return Effect.void;
67
- }
68
- const newHash = replaced.right;
69
- return Effect.all([
70
- Ref.set(currentHashRef, newHash),
71
- Ref.set(confirmationsRef, 0),
72
- Ref.set(startedAtMsRef, now),
73
- Ref.update(autoAttemptsRef, (n) => n + 1),
74
- tracker.set({
75
- newHash,
76
- oldHash: currentHash,
77
- reason: replacementStrategy === "cancel" ? "cancelled" : "repriced",
78
- status: "replaced",
79
- }),
80
- tracker.set({ hash: newHash, status: "submitted" }),
81
- ]).pipe(Effect.asVoid);
82
- }))));
83
- const autoReplaceIfStuck = (currentHash, confirmations) => {
84
- if (replacementStrategy === "none") {
85
- return Effect.void;
86
- }
87
- return Effect.all({
88
- alreadyReplacing: Ref.get(autoReplacingRef),
89
- attempts: Ref.get(autoAttemptsRef),
90
- now: Clock.currentTimeMillis,
91
- startedAt: Ref.get(startedAtMsRef),
92
- }).pipe(Effect.flatMap(({ alreadyReplacing, attempts, now, startedAt }) => {
93
- const elapsed = startedAt > 0 ? now - startedAt : 0;
94
- const stuck = confirmations >= stuckBlocks || elapsed >= stuckMs;
95
- const allowed = attempts < maxAttempts && !alreadyReplacing;
96
- return stuck && allowed ? performAutoReplacement(currentHash, now) : Effect.void;
97
- }));
11
+ export function makeTxManagerLive(layerPolicy) {
12
+ const layerDefault = {
13
+ ...defaultPolicy,
14
+ ...layerPolicy,
15
+ replacement: {
16
+ ...(defaultPolicy.replacement ?? {}),
17
+ ...(layerPolicy?.replacement ?? {}),
18
+ },
19
+ };
20
+ return Layer.effect(TxManager, Effect.gen(function* () {
21
+ const publicClientService = yield* PublicClientService;
22
+ const txReplacement = yield* TxReplacement;
23
+ return {
24
+ getConfirmations: Effect.fn("TxManager.getConfirmations")(function* (chainId, params) {
25
+ const client = yield* publicClientService.get(chainId);
26
+ return yield* Effect.tryPromise({
27
+ catch: (cause) => new TransportError({
28
+ cause,
29
+ message: cause instanceof Error
30
+ ? cause.message
31
+ : "Failed to get transaction confirmations",
32
+ url: client.transport.url ?? "",
33
+ }),
34
+ try: () => client.getTransactionConfirmations(params),
35
+ }).pipe(Effect.withSpan(SpanNames.TX_GET_CONFIRMATIONS, {
36
+ attributes: {
37
+ chainId,
38
+ hash: "hash" in params ? params.hash : params.transactionReceipt.transactionHash,
39
+ },
40
+ }));
41
+ }),
42
+ track: Effect.fn("TxManager.track")(function* (chainId, hash, providedPolicy) {
43
+ const tracker = yield* makeTxTracker;
44
+ const client = yield* publicClientService.get(chainId);
45
+ const policy = {
46
+ ...layerDefault,
47
+ ...providedPolicy,
48
+ replacement: {
49
+ ...(layerDefault.replacement ?? {}),
50
+ ...(providedPolicy?.replacement ?? {}),
51
+ },
98
52
  };
99
- const onPendingBlock = Effect.gen(function* () {
100
- const currentHash = yield* Ref.get(currentHashRef);
101
- const confirmations = yield* updatePendingState(currentHash);
102
- yield* autoReplaceIfStuck(currentHash, confirmations);
103
- });
104
- const pendingFiber = yield* Stream.runForEach(Stream.async((emit) => {
105
- const unwatch = client.watchBlockNumber({
106
- onBlockNumber: (blockNumber) => emit.single(blockNumber),
107
- onError: (error) => emit.fail(error),
108
- pollingInterval: policy.pollingInterval,
53
+ yield* tracker.set({ hash, status: "submitted" });
54
+ yield* Effect.forkScoped(Effect.gen(function* () {
55
+ const currentHashRef = yield* Ref.make(hash);
56
+ const confirmationsRef = yield* Ref.make(0);
57
+ const startedAtMsRef = yield* Ref.make(yield* Clock.currentTimeMillis);
58
+ const autoAttemptsRef = yield* Ref.make(0);
59
+ const autoReplacingRef = yield* Ref.make(false);
60
+ const replacementStrategy = policy.replacement?.strategy ?? policy.replacementStrategy ?? "none";
61
+ const stuckMs = policy.replacement?.stuckMs ?? DEFAULT_STUCK_TX_MS;
62
+ const maxAttempts = policy.replacement?.maxAttempts ?? 1;
63
+ const updatePendingState = (currentHash) => Effect.gen(function* () {
64
+ const confirmations = yield* Ref.modify(confirmationsRef, (n) => [n + 1, n + 1]);
65
+ yield* tracker.set({
66
+ confirmations,
67
+ hash: currentHash,
68
+ status: "pending",
69
+ });
70
+ return confirmations;
109
71
  });
110
- return Effect.sync(() => {
111
- unwatch();
72
+ const performAutoReplacement = (currentHash, now) => Ref.set(autoReplacingRef, true).pipe(Effect.zipRight((replacementStrategy === "cancel"
73
+ ? txReplacement.cancel(chainId, currentHash, policy)
74
+ : txReplacement.speedup(chainId, currentHash, policy)).pipe(Effect.either, Effect.ensuring(Ref.set(autoReplacingRef, false)), Effect.flatMap((replaced) => {
75
+ if (replaced._tag === "Left") {
76
+ return Effect.void;
77
+ }
78
+ const newHash = replaced.right;
79
+ return Effect.all([
80
+ Ref.set(currentHashRef, newHash),
81
+ Ref.set(confirmationsRef, 0),
82
+ Ref.set(startedAtMsRef, now),
83
+ Ref.update(autoAttemptsRef, (n) => n + 1),
84
+ tracker.set({
85
+ newHash,
86
+ oldHash: currentHash,
87
+ reason: replacementStrategy === "cancel" ? "cancelled" : "repriced",
88
+ status: "replaced",
89
+ }),
90
+ tracker.set({ hash: newHash, status: "submitted" }),
91
+ ]).pipe(Effect.asVoid);
92
+ }))));
93
+ const autoReplaceIfStuck = (currentHash) => {
94
+ if (replacementStrategy === "none") {
95
+ return Effect.void;
96
+ }
97
+ return Effect.all({
98
+ alreadyReplacing: Ref.get(autoReplacingRef),
99
+ attempts: Ref.get(autoAttemptsRef),
100
+ now: Clock.currentTimeMillis,
101
+ startedAt: Ref.get(startedAtMsRef),
102
+ }).pipe(Effect.flatMap(({ alreadyReplacing, attempts, now, startedAt }) => {
103
+ const elapsed = startedAt > 0 ? now - startedAt : 0;
104
+ const stuck = elapsed >= stuckMs;
105
+ const allowed = attempts < maxAttempts && !alreadyReplacing;
106
+ return stuck && allowed
107
+ ? performAutoReplacement(currentHash, now)
108
+ : Effect.void;
109
+ }));
110
+ };
111
+ const onPendingBlock = Effect.gen(function* () {
112
+ const currentHash = yield* Ref.get(currentHashRef);
113
+ yield* updatePendingState(currentHash);
114
+ yield* autoReplaceIfStuck(currentHash);
112
115
  });
113
- }), () => onPendingBlock).pipe(Effect.forkScoped);
114
- let replacement;
115
- const receiptResult = yield* Effect.either(Effect.tryPromise({
116
- catch: (e) => e,
117
- try: () => client.waitForTransactionReceipt({
118
- hash,
119
- onReplaced: (info) => {
120
- replacement = {
121
- newHash: info.transaction.hash,
122
- oldHash: info.replacedTransaction.hash,
123
- reason: info.reason,
124
- };
125
- },
126
- pollingInterval: policy.pollingInterval,
127
- timeout: policy.receiptTimeout,
128
- }),
129
- })).pipe(Effect.ensuring(Fiber.interrupt(pendingFiber)));
130
- if (receiptResult._tag === "Left") {
131
- const cause = receiptResult.left;
132
- const timeout = policy.receiptTimeout ?? DEFAULT_RECEIPT_TIMEOUT;
133
- const failure = cause instanceof WaitForTransactionReceiptTimeoutError
134
- ? new ReceiptTimeoutError({
135
- hash,
136
- message: cause.message,
137
- timeout,
138
- })
139
- : cause;
140
- yield* tracker.set({
141
- error: new TxFailedError({
142
- cause: failure,
116
+ const pendingFiber = yield* Stream.runForEach(Stream.async((emit) => {
117
+ const unwatch = client.watchBlockNumber({
118
+ onBlockNumber: (blockNumber) => emit.single(blockNumber),
119
+ onError: (error) => emit.fail(error),
120
+ pollingInterval: policy.pollingInterval,
121
+ });
122
+ return Effect.sync(() => {
123
+ unwatch();
124
+ });
125
+ }), () => onPendingBlock).pipe(Effect.forkScoped);
126
+ let replacement;
127
+ const receiptResult = yield* Effect.either(Effect.tryPromise({
128
+ catch: (e) => e,
129
+ try: () => client.waitForTransactionReceipt({
143
130
  hash,
144
- message: failure instanceof Error ? failure.message : String(failure),
131
+ onReplaced: (info) => {
132
+ replacement = {
133
+ newHash: info.transaction.hash,
134
+ oldHash: info.replacedTransaction.hash,
135
+ reason: info.reason,
136
+ };
137
+ },
138
+ pollingInterval: policy.pollingInterval,
139
+ timeout: policy.receiptTimeout,
145
140
  }),
146
- status: "failed",
147
- });
148
- return;
149
- }
150
- const receipt = receiptResult.right;
151
- if (replacement) {
152
- yield* Ref.set(currentHashRef, replacement.newHash);
153
- yield* tracker.set({
154
- newHash: replacement.newHash,
155
- oldHash: replacement.oldHash,
156
- reason: replacement.reason,
157
- status: "replaced",
158
- });
141
+ })).pipe(Effect.ensuring(Fiber.interrupt(pendingFiber)));
142
+ if (receiptResult._tag === "Left") {
143
+ const cause = receiptResult.left;
144
+ const timeout = policy.receiptTimeout ?? DEFAULT_RECEIPT_TIMEOUT;
145
+ const failure = cause instanceof WaitForTransactionReceiptTimeoutError
146
+ ? new ReceiptTimeoutError({
147
+ hash,
148
+ message: cause.message,
149
+ timeout,
150
+ })
151
+ : cause;
152
+ yield* tracker.set({
153
+ error: new TxFailedError({
154
+ cause: failure,
155
+ hash,
156
+ message: failure instanceof Error ? failure.message : String(failure),
157
+ }),
158
+ status: "failed",
159
+ });
160
+ return;
161
+ }
162
+ const receipt = receiptResult.right;
163
+ if (replacement) {
164
+ yield* Ref.set(currentHashRef, replacement.newHash);
165
+ yield* tracker.set({
166
+ newHash: replacement.newHash,
167
+ oldHash: replacement.oldHash,
168
+ reason: replacement.reason,
169
+ status: "replaced",
170
+ });
171
+ yield* tracker.set({
172
+ hash: replacement.newHash,
173
+ status: "submitted",
174
+ });
175
+ }
159
176
  yield* tracker.set({
160
- hash: replacement.newHash,
161
- status: "submitted",
177
+ effectiveGasPrice: receipt.effectiveGasPrice,
178
+ hash: receipt.transactionHash,
179
+ receipt,
180
+ status: "mined",
162
181
  });
163
- }
164
- yield* tracker.set({
165
- effectiveGasPrice: receipt.effectiveGasPrice,
166
- hash: receipt.transactionHash,
167
- receipt,
168
- status: "mined",
169
- });
170
- }));
171
- return tracker.ref;
172
- }),
173
- waitForReceipt: Effect.fn("TxManager.waitForReceipt")(function* (chainId, hash, timeoutOrPolicy) {
174
- const client = yield* publicClientService.get(chainId);
175
- const policy = typeof timeoutOrPolicy === "object" && timeoutOrPolicy !== null
176
- ? timeoutOrPolicy
177
- : undefined;
178
- const timeout = typeof timeoutOrPolicy === "number"
179
- ? timeoutOrPolicy
180
- : (policy?.receiptTimeout ?? defaultPolicy.receiptTimeout ?? DEFAULT_RECEIPT_TIMEOUT);
181
- const pollingInterval = policy?.pollingInterval ?? defaultPolicy.pollingInterval;
182
- return yield* Effect.tryPromise({
183
- catch: (cause) => {
184
- if (cause instanceof TxReplacedError) {
185
- return cause;
186
- }
187
- if (cause instanceof WaitForTransactionReceiptTimeoutError) {
188
- return new ReceiptTimeoutError({
182
+ }));
183
+ return tracker.ref;
184
+ }),
185
+ waitForReceipt: Effect.fn("TxManager.waitForReceipt")(function* (chainId, hash, timeoutOrPolicy) {
186
+ const client = yield* publicClientService.get(chainId);
187
+ const policy = typeof timeoutOrPolicy === "object" && timeoutOrPolicy !== null
188
+ ? timeoutOrPolicy
189
+ : undefined;
190
+ const timeout = typeof timeoutOrPolicy === "number"
191
+ ? timeoutOrPolicy
192
+ : (policy?.receiptTimeout ??
193
+ layerDefault.receiptTimeout ??
194
+ DEFAULT_RECEIPT_TIMEOUT);
195
+ const pollingInterval = policy?.pollingInterval ?? layerDefault.pollingInterval;
196
+ return yield* Effect.tryPromise({
197
+ catch: (cause) => {
198
+ if (cause instanceof TxReplacedError) {
199
+ return cause;
200
+ }
201
+ if (cause instanceof WaitForTransactionReceiptTimeoutError) {
202
+ return new ReceiptTimeoutError({
203
+ hash,
204
+ message: cause.message,
205
+ timeout,
206
+ });
207
+ }
208
+ if (cause instanceof Error && cause.message.toLowerCase().includes("timeout")) {
209
+ return new ReceiptTimeoutError({
210
+ hash,
211
+ message: cause.message,
212
+ timeout,
213
+ });
214
+ }
215
+ return new TxFailedError({
216
+ cause,
189
217
  hash,
190
- message: cause.message,
191
- timeout,
218
+ message: `Failed to get receipt for ${hash}`,
192
219
  });
193
- }
194
- if (cause instanceof Error && cause.message.toLowerCase().includes("timeout")) {
195
- return new ReceiptTimeoutError({
220
+ },
221
+ try: async () => {
222
+ let replacement;
223
+ const receipt = await client.waitForTransactionReceipt({
196
224
  hash,
197
- message: cause.message,
225
+ onReplaced: (info) => {
226
+ replacement = {
227
+ newHash: info.transaction.hash,
228
+ reason: info.reason,
229
+ };
230
+ },
231
+ pollingInterval,
198
232
  timeout,
199
233
  });
200
- }
201
- return new TxFailedError({
202
- cause,
203
- hash,
204
- message: `Failed to get receipt for ${hash}`,
205
- });
206
- },
207
- try: async () => {
208
- let replacement;
209
- const receipt = await client.waitForTransactionReceipt({
234
+ if (replacement && replacement.newHash !== hash) {
235
+ throw new TxReplacedError({
236
+ message: `Transaction ${hash} was ${replacement.reason} with ${replacement.newHash}`,
237
+ newHash: replacement.newHash,
238
+ oldHash: hash,
239
+ reason: replacement.reason,
240
+ });
241
+ }
242
+ return receipt;
243
+ },
244
+ }).pipe(Effect.withSpan(SpanNames.TX_WAIT, {
245
+ attributes: {
246
+ chainId,
210
247
  hash,
211
- onReplaced: (info) => {
212
- replacement = {
213
- newHash: info.transaction.hash,
214
- reason: info.reason,
215
- };
216
- },
217
- pollingInterval,
218
248
  timeout,
219
- });
220
- if (replacement && replacement.newHash !== hash) {
221
- throw new TxReplacedError({
222
- message: `Transaction ${hash} was ${replacement.reason} with ${replacement.newHash}`,
223
- newHash: replacement.newHash,
224
- oldHash: hash,
225
- reason: replacement.reason,
226
- });
227
- }
228
- return receipt;
229
- },
230
- }).pipe(Effect.withSpan(SpanNames.TX_WAIT, {
231
- attributes: {
232
- chainId,
233
- hash,
234
- timeout,
235
- },
236
- }));
237
- }),
238
- };
239
- }));
249
+ },
250
+ }));
251
+ }),
252
+ };
253
+ }));
254
+ }
255
+ export const TxManagerLive = makeTxManagerLive();
240
256
  //# sourceMappingURL=manager.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/tx/manager.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Scope, SubscriptionRef } from \"effect\";\nimport { Clock, Context, Effect, Fiber, Layer, Ref, Stream } from \"effect\";\nimport type { Hash, TransactionReceipt } from \"viem\";\nimport { WaitForTransactionReceiptTimeoutError } from \"viem\";\nimport { DEFAULT_RECEIPT_TIMEOUT, DEFAULT_STUCK_TX_MS } from \"@/src/constants/index.js\";\nimport type { ClientNotFoundError, TxReplacementReason } from \"@/src/core/index.js\";\nimport {\n PublicClientService,\n ReceiptTimeoutError,\n TransportError,\n TxFailedError,\n TxReplacedError,\n} from \"@/src/core/index.js\";\nimport { SpanNames } from \"@/src/telemetry/index.js\";\nimport type { TxPolicy } from \"./policy.js\";\nimport { defaultPolicy } from \"./policy.js\";\nimport { TxReplacement } from \"./replacement.js\";\nimport type { TxState } from \"./tracker.js\";\nimport { makeTxTracker } from \"./tracker.js\";\n\nexport type TxManagerShape = {\n /**\n * Track an existing transaction hash and return a SubscriptionRef for state updates\n */\n readonly track: (\n chainId: number,\n hash: Hash,\n policy?: TxPolicy\n ) => Effect.Effect<SubscriptionRef.SubscriptionRef<TxState>, ClientNotFoundError, Scope.Scope>;\n\n /**\n * Wait for transaction receipt with timeout\n */\n readonly waitForReceipt: (\n chainId: number,\n hash: Hash,\n timeoutOrPolicy?: number | TxPolicy\n ) => Effect.Effect<\n TransactionReceipt,\n TxFailedError | ReceiptTimeoutError | TxReplacedError | ClientNotFoundError\n >;\n\n /**\n * Get the number of confirmations for a transaction\n */\n readonly getConfirmations: (\n chainId: number,\n params: { hash: Hash } | { transactionReceipt: TransactionReceipt }\n ) => Effect.Effect<bigint, ClientNotFoundError | TransportError>;\n};\n\nexport class TxManager extends Context.Tag(\"ew3/TxManager\")<TxManager, TxManagerShape>() {}\n\nexport const TxManagerLive = Layer.effect(\n TxManager,\n Effect.gen(function* () {\n const publicClientService = yield* PublicClientService;\n const txReplacement = yield* TxReplacement;\n\n return {\n getConfirmations: Effect.fn(\"TxManager.getConfirmations\")(function* (chainId, params) {\n const client = yield* publicClientService.get(chainId);\n\n return yield* Effect.tryPromise({\n catch: (cause) =>\n new TransportError({\n cause,\n message:\n cause instanceof Error ? cause.message : \"Failed to get transaction confirmations\",\n url: client.transport.url ?? \"\",\n }),\n try: () => client.getTransactionConfirmations(params),\n }).pipe(\n Effect.withSpan(SpanNames.TX_GET_CONFIRMATIONS, {\n attributes: {\n chainId,\n hash: \"hash\" in params ? params.hash : params.transactionReceipt.transactionHash,\n },\n })\n );\n }),\n track: Effect.fn(\"TxManager.track\")(function* (chainId, hash, providedPolicy) {\n const tracker = yield* makeTxTracker;\n const client = yield* publicClientService.get(chainId);\n const policy: TxPolicy = {\n ...defaultPolicy,\n ...providedPolicy,\n replacement: {\n ...(defaultPolicy.replacement ?? {}),\n ...(providedPolicy?.replacement ?? {}),\n },\n };\n\n // Set initial state\n yield* tracker.set({ hash, status: \"submitted\" });\n\n // Start tracking in background\n yield* Effect.forkScoped(\n Effect.gen(function* () {\n const currentHashRef = yield* Ref.make<Hash>(hash);\n const confirmationsRef = yield* Ref.make(0);\n const startedAtMsRef = yield* Ref.make(yield* Clock.currentTimeMillis);\n const autoAttemptsRef = yield* Ref.make(0);\n const autoReplacingRef = yield* Ref.make(false);\n\n const replacementStrategy =\n policy.replacement?.strategy ?? policy.replacementStrategy ?? \"none\";\n const stuckBlocks = policy.replacement?.stuckBlocks ?? 3;\n const stuckMs = policy.replacement?.stuckMs ?? DEFAULT_STUCK_TX_MS;\n const maxAttempts = policy.replacement?.maxAttempts ?? 1;\n\n const updatePendingState = (currentHash: Hash) =>\n Effect.gen(function* () {\n const confirmations = yield* Ref.modify(\n confirmationsRef,\n (n) => [n + 1, n + 1] as const\n );\n\n yield* tracker.set({\n confirmations,\n hash: currentHash,\n status: \"pending\",\n });\n return confirmations;\n });\n\n const performAutoReplacement = (currentHash: Hash, now: number) =>\n Ref.set(autoReplacingRef, true).pipe(\n Effect.zipRight(\n (replacementStrategy === \"cancel\"\n ? txReplacement.cancel(chainId, currentHash, policy)\n : txReplacement.speedup(chainId, currentHash, policy)\n ).pipe(\n Effect.either,\n Effect.ensuring(Ref.set(autoReplacingRef, false)),\n Effect.flatMap((replaced) => {\n if (replaced._tag === \"Left\") {\n return Effect.void;\n }\n\n const newHash = replaced.right;\n return Effect.all([\n Ref.set(currentHashRef, newHash),\n Ref.set(confirmationsRef, 0),\n Ref.set(startedAtMsRef, now),\n Ref.update(autoAttemptsRef, (n) => n + 1),\n tracker.set({\n newHash,\n oldHash: currentHash,\n reason: replacementStrategy === \"cancel\" ? \"cancelled\" : \"repriced\",\n status: \"replaced\",\n }),\n tracker.set({ hash: newHash, status: \"submitted\" }),\n ]).pipe(Effect.asVoid);\n })\n )\n )\n );\n\n const autoReplaceIfStuck = (currentHash: Hash, confirmations: number) => {\n if (replacementStrategy === \"none\") {\n return Effect.void;\n }\n\n return Effect.all({\n alreadyReplacing: Ref.get(autoReplacingRef),\n attempts: Ref.get(autoAttemptsRef),\n now: Clock.currentTimeMillis,\n startedAt: Ref.get(startedAtMsRef),\n }).pipe(\n Effect.flatMap(({ alreadyReplacing, attempts, now, startedAt }) => {\n const elapsed = startedAt > 0 ? now - startedAt : 0;\n const stuck = confirmations >= stuckBlocks || elapsed >= stuckMs;\n const allowed = attempts < maxAttempts && !alreadyReplacing;\n return stuck && allowed ? performAutoReplacement(currentHash, now) : Effect.void;\n })\n );\n };\n\n const onPendingBlock = Effect.gen(function* () {\n const currentHash = yield* Ref.get(currentHashRef);\n const confirmations = yield* updatePendingState(currentHash);\n yield* autoReplaceIfStuck(currentHash, confirmations);\n });\n\n const pendingFiber = yield* Stream.runForEach(\n Stream.async<bigint, unknown>((emit) => {\n const unwatch = client.watchBlockNumber({\n onBlockNumber: (blockNumber: bigint) => emit.single(blockNumber),\n onError: (error) => emit.fail(error as unknown),\n pollingInterval: policy.pollingInterval,\n });\n\n return Effect.sync(() => {\n unwatch();\n });\n }),\n () => onPendingBlock\n ).pipe(Effect.forkScoped);\n\n let replacement:\n | {\n oldHash: Hash;\n newHash: Hash;\n reason: TxReplacementReason;\n }\n | undefined;\n\n const receiptResult = yield* Effect.either(\n Effect.tryPromise({\n catch: (e) => e,\n try: () =>\n client.waitForTransactionReceipt({\n hash,\n onReplaced: (info) => {\n replacement = {\n newHash: info.transaction.hash,\n oldHash: info.replacedTransaction.hash,\n reason: info.reason,\n };\n },\n pollingInterval: policy.pollingInterval,\n timeout: policy.receiptTimeout,\n }),\n })\n ).pipe(Effect.ensuring(Fiber.interrupt(pendingFiber)));\n\n if (receiptResult._tag === \"Left\") {\n const cause = receiptResult.left;\n const timeout = policy.receiptTimeout ?? DEFAULT_RECEIPT_TIMEOUT;\n\n const failure =\n cause instanceof WaitForTransactionReceiptTimeoutError\n ? new ReceiptTimeoutError({\n hash,\n message: cause.message,\n timeout,\n })\n : cause;\n\n yield* tracker.set({\n error: new TxFailedError({\n cause: failure,\n hash,\n message: failure instanceof Error ? failure.message : String(failure),\n }),\n status: \"failed\",\n });\n\n return;\n }\n\n const receipt = receiptResult.right;\n\n if (replacement) {\n yield* Ref.set(currentHashRef, replacement.newHash);\n yield* tracker.set({\n newHash: replacement.newHash,\n oldHash: replacement.oldHash,\n reason: replacement.reason,\n status: \"replaced\",\n });\n yield* tracker.set({\n hash: replacement.newHash,\n status: \"submitted\",\n });\n }\n\n yield* tracker.set({\n effectiveGasPrice: receipt.effectiveGasPrice,\n hash: receipt.transactionHash,\n receipt,\n status: \"mined\",\n });\n })\n );\n\n return tracker.ref;\n }),\n\n waitForReceipt: Effect.fn(\"TxManager.waitForReceipt\")(\n function* (chainId, hash, timeoutOrPolicy) {\n const client = yield* publicClientService.get(chainId);\n const policy =\n typeof timeoutOrPolicy === \"object\" && timeoutOrPolicy !== null\n ? timeoutOrPolicy\n : undefined;\n const timeout =\n typeof timeoutOrPolicy === \"number\"\n ? timeoutOrPolicy\n : (policy?.receiptTimeout ?? defaultPolicy.receiptTimeout ?? DEFAULT_RECEIPT_TIMEOUT);\n const pollingInterval = policy?.pollingInterval ?? defaultPolicy.pollingInterval;\n\n return yield* Effect.tryPromise({\n catch: (cause) => {\n if (cause instanceof TxReplacedError) {\n return cause;\n }\n\n if (cause instanceof WaitForTransactionReceiptTimeoutError) {\n return new ReceiptTimeoutError({\n hash,\n message: cause.message,\n timeout,\n });\n }\n\n if (cause instanceof Error && cause.message.toLowerCase().includes(\"timeout\")) {\n return new ReceiptTimeoutError({\n hash,\n message: cause.message,\n timeout,\n });\n }\n\n return new TxFailedError({\n cause,\n hash,\n message: `Failed to get receipt for ${hash}`,\n });\n },\n try: async () => {\n let replacement: { newHash: Hash; reason: TxReplacementReason } | undefined;\n\n const receipt = await client.waitForTransactionReceipt({\n hash,\n onReplaced: (info) => {\n replacement = {\n newHash: info.transaction.hash,\n reason: info.reason,\n };\n },\n pollingInterval,\n timeout,\n });\n\n // Only throw if there's an actual replacement (different hash)\n if (replacement && replacement.newHash !== hash) {\n throw new TxReplacedError({\n message: `Transaction ${hash} was ${replacement.reason} with ${replacement.newHash}`,\n newHash: replacement.newHash,\n oldHash: hash,\n reason: replacement.reason,\n });\n }\n\n return receipt;\n },\n }).pipe(\n Effect.withSpan(SpanNames.TX_WAIT, {\n attributes: {\n chainId,\n hash,\n timeout,\n },\n })\n );\n }\n ),\n };\n })\n);\n"]}
1
+ {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/tx/manager.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Scope, SubscriptionRef } from \"effect\";\nimport { Clock, Context, Effect, Fiber, Layer, Ref, Stream } from \"effect\";\nimport type { Hash, TransactionReceipt } from \"viem\";\nimport { WaitForTransactionReceiptTimeoutError } from \"viem\";\nimport { DEFAULT_RECEIPT_TIMEOUT, DEFAULT_STUCK_TX_MS } from \"@/src/constants/index.js\";\nimport type { ClientNotFoundError, TxReplacementReason } from \"@/src/core/index.js\";\nimport {\n PublicClientService,\n ReceiptTimeoutError,\n TransportError,\n TxFailedError,\n TxReplacedError,\n} from \"@/src/core/index.js\";\nimport { SpanNames } from \"@/src/telemetry/index.js\";\nimport type { TxPolicy } from \"./policy.js\";\nimport { defaultPolicy } from \"./policy.js\";\nimport { TxReplacement } from \"./replacement.js\";\nimport type { TxState } from \"./tracker.js\";\nimport { makeTxTracker } from \"./tracker.js\";\n\nexport type TxManagerShape = {\n /**\n * Track an existing transaction hash and return a SubscriptionRef for state updates\n */\n readonly track: (\n chainId: number,\n hash: Hash,\n policy?: TxPolicy\n ) => Effect.Effect<SubscriptionRef.SubscriptionRef<TxState>, ClientNotFoundError, Scope.Scope>;\n\n /**\n * Wait for transaction receipt with timeout\n */\n readonly waitForReceipt: (\n chainId: number,\n hash: Hash,\n timeoutOrPolicy?: number | TxPolicy\n ) => Effect.Effect<\n TransactionReceipt,\n TxFailedError | ReceiptTimeoutError | TxReplacedError | ClientNotFoundError\n >;\n\n /**\n * Get the number of confirmations for a transaction\n */\n readonly getConfirmations: (\n chainId: number,\n params: { hash: Hash } | { transactionReceipt: TransactionReceipt }\n ) => Effect.Effect<bigint, ClientNotFoundError | TransportError>;\n};\n\nexport class TxManager extends Context.Tag(\"ew3/TxManager\")<TxManager, TxManagerShape>() {}\n\nexport function makeTxManagerLive(\n layerPolicy?: TxPolicy\n): Layer.Layer<TxManager, never, PublicClientService | TxReplacement> {\n const layerDefault: TxPolicy = {\n ...defaultPolicy,\n ...layerPolicy,\n replacement: {\n ...(defaultPolicy.replacement ?? {}),\n ...(layerPolicy?.replacement ?? {}),\n },\n };\n\n return Layer.effect(\n TxManager,\n Effect.gen(function* () {\n const publicClientService = yield* PublicClientService;\n const txReplacement = yield* TxReplacement;\n\n return {\n getConfirmations: Effect.fn(\"TxManager.getConfirmations\")(function* (chainId, params) {\n const client = yield* publicClientService.get(chainId);\n\n return yield* Effect.tryPromise({\n catch: (cause) =>\n new TransportError({\n cause,\n message:\n cause instanceof Error\n ? cause.message\n : \"Failed to get transaction confirmations\",\n url: client.transport.url ?? \"\",\n }),\n try: () => client.getTransactionConfirmations(params),\n }).pipe(\n Effect.withSpan(SpanNames.TX_GET_CONFIRMATIONS, {\n attributes: {\n chainId,\n hash: \"hash\" in params ? params.hash : params.transactionReceipt.transactionHash,\n },\n })\n );\n }),\n track: Effect.fn(\"TxManager.track\")(function* (chainId, hash, providedPolicy) {\n const tracker = yield* makeTxTracker;\n const client = yield* publicClientService.get(chainId);\n const policy: TxPolicy = {\n ...layerDefault,\n ...providedPolicy,\n replacement: {\n ...(layerDefault.replacement ?? {}),\n ...(providedPolicy?.replacement ?? {}),\n },\n };\n\n // Set initial state\n yield* tracker.set({ hash, status: \"submitted\" });\n\n // Start tracking in background\n yield* Effect.forkScoped(\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: tracking orchestration is inherently complex\n Effect.gen(function* () {\n const currentHashRef = yield* Ref.make<Hash>(hash);\n const confirmationsRef = yield* Ref.make(0);\n const startedAtMsRef = yield* Ref.make(yield* Clock.currentTimeMillis);\n const autoAttemptsRef = yield* Ref.make(0);\n const autoReplacingRef = yield* Ref.make(false);\n\n const replacementStrategy =\n policy.replacement?.strategy ?? policy.replacementStrategy ?? \"none\";\n const stuckMs = policy.replacement?.stuckMs ?? DEFAULT_STUCK_TX_MS;\n const maxAttempts = policy.replacement?.maxAttempts ?? 1;\n\n const updatePendingState = (currentHash: Hash) =>\n Effect.gen(function* () {\n const confirmations = yield* Ref.modify(\n confirmationsRef,\n (n) => [n + 1, n + 1] as const\n );\n\n yield* tracker.set({\n confirmations,\n hash: currentHash,\n status: \"pending\",\n });\n return confirmations;\n });\n\n const performAutoReplacement = (currentHash: Hash, now: number) =>\n Ref.set(autoReplacingRef, true).pipe(\n Effect.zipRight(\n (replacementStrategy === \"cancel\"\n ? txReplacement.cancel(chainId, currentHash, policy)\n : txReplacement.speedup(chainId, currentHash, policy)\n ).pipe(\n Effect.either,\n Effect.ensuring(Ref.set(autoReplacingRef, false)),\n Effect.flatMap((replaced) => {\n if (replaced._tag === \"Left\") {\n return Effect.void;\n }\n\n const newHash = replaced.right;\n return Effect.all([\n Ref.set(currentHashRef, newHash),\n Ref.set(confirmationsRef, 0),\n Ref.set(startedAtMsRef, now),\n Ref.update(autoAttemptsRef, (n) => n + 1),\n tracker.set({\n newHash,\n oldHash: currentHash,\n reason: replacementStrategy === \"cancel\" ? \"cancelled\" : \"repriced\",\n status: \"replaced\",\n }),\n tracker.set({ hash: newHash, status: \"submitted\" }),\n ]).pipe(Effect.asVoid);\n })\n )\n )\n );\n\n const autoReplaceIfStuck = (currentHash: Hash) => {\n if (replacementStrategy === \"none\") {\n return Effect.void;\n }\n\n return Effect.all({\n alreadyReplacing: Ref.get(autoReplacingRef),\n attempts: Ref.get(autoAttemptsRef),\n now: Clock.currentTimeMillis,\n startedAt: Ref.get(startedAtMsRef),\n }).pipe(\n Effect.flatMap(({ alreadyReplacing, attempts, now, startedAt }) => {\n const elapsed = startedAt > 0 ? now - startedAt : 0;\n const stuck = elapsed >= stuckMs;\n const allowed = attempts < maxAttempts && !alreadyReplacing;\n return stuck && allowed\n ? performAutoReplacement(currentHash, now)\n : Effect.void;\n })\n );\n };\n\n const onPendingBlock = Effect.gen(function* () {\n const currentHash = yield* Ref.get(currentHashRef);\n yield* updatePendingState(currentHash);\n yield* autoReplaceIfStuck(currentHash);\n });\n\n const pendingFiber = yield* Stream.runForEach(\n Stream.async<bigint, unknown>((emit) => {\n const unwatch = client.watchBlockNumber({\n onBlockNumber: (blockNumber: bigint) => emit.single(blockNumber),\n onError: (error) => emit.fail(error as unknown),\n pollingInterval: policy.pollingInterval,\n });\n\n return Effect.sync(() => {\n unwatch();\n });\n }),\n () => onPendingBlock\n ).pipe(Effect.forkScoped);\n\n let replacement:\n | {\n oldHash: Hash;\n newHash: Hash;\n reason: TxReplacementReason;\n }\n | undefined;\n\n const receiptResult = yield* Effect.either(\n Effect.tryPromise({\n catch: (e) => e,\n try: () =>\n client.waitForTransactionReceipt({\n hash,\n onReplaced: (info) => {\n replacement = {\n newHash: info.transaction.hash,\n oldHash: info.replacedTransaction.hash,\n reason: info.reason,\n };\n },\n pollingInterval: policy.pollingInterval,\n timeout: policy.receiptTimeout,\n }),\n })\n ).pipe(Effect.ensuring(Fiber.interrupt(pendingFiber)));\n\n if (receiptResult._tag === \"Left\") {\n const cause = receiptResult.left;\n const timeout = policy.receiptTimeout ?? DEFAULT_RECEIPT_TIMEOUT;\n\n const failure =\n cause instanceof WaitForTransactionReceiptTimeoutError\n ? new ReceiptTimeoutError({\n hash,\n message: cause.message,\n timeout,\n })\n : cause;\n\n yield* tracker.set({\n error: new TxFailedError({\n cause: failure,\n hash,\n message: failure instanceof Error ? failure.message : String(failure),\n }),\n status: \"failed\",\n });\n\n return;\n }\n\n const receipt = receiptResult.right;\n\n if (replacement) {\n yield* Ref.set(currentHashRef, replacement.newHash);\n yield* tracker.set({\n newHash: replacement.newHash,\n oldHash: replacement.oldHash,\n reason: replacement.reason,\n status: \"replaced\",\n });\n yield* tracker.set({\n hash: replacement.newHash,\n status: \"submitted\",\n });\n }\n\n yield* tracker.set({\n effectiveGasPrice: receipt.effectiveGasPrice,\n hash: receipt.transactionHash,\n receipt,\n status: \"mined\",\n });\n })\n );\n\n return tracker.ref;\n }),\n\n waitForReceipt: Effect.fn(\"TxManager.waitForReceipt\")(\n function* (chainId, hash, timeoutOrPolicy) {\n const client = yield* publicClientService.get(chainId);\n const policy =\n typeof timeoutOrPolicy === \"object\" && timeoutOrPolicy !== null\n ? timeoutOrPolicy\n : undefined;\n const timeout =\n typeof timeoutOrPolicy === \"number\"\n ? timeoutOrPolicy\n : (policy?.receiptTimeout ??\n layerDefault.receiptTimeout ??\n DEFAULT_RECEIPT_TIMEOUT);\n const pollingInterval = policy?.pollingInterval ?? layerDefault.pollingInterval;\n\n return yield* Effect.tryPromise({\n catch: (cause) => {\n if (cause instanceof TxReplacedError) {\n return cause;\n }\n\n if (cause instanceof WaitForTransactionReceiptTimeoutError) {\n return new ReceiptTimeoutError({\n hash,\n message: cause.message,\n timeout,\n });\n }\n\n if (cause instanceof Error && cause.message.toLowerCase().includes(\"timeout\")) {\n return new ReceiptTimeoutError({\n hash,\n message: cause.message,\n timeout,\n });\n }\n\n return new TxFailedError({\n cause,\n hash,\n message: `Failed to get receipt for ${hash}`,\n });\n },\n try: async () => {\n let replacement: { newHash: Hash; reason: TxReplacementReason } | undefined;\n\n const receipt = await client.waitForTransactionReceipt({\n hash,\n onReplaced: (info) => {\n replacement = {\n newHash: info.transaction.hash,\n reason: info.reason,\n };\n },\n pollingInterval,\n timeout,\n });\n\n // Only throw if there's an actual replacement (different hash)\n if (replacement && replacement.newHash !== hash) {\n throw new TxReplacedError({\n message: `Transaction ${hash} was ${replacement.reason} with ${replacement.newHash}`,\n newHash: replacement.newHash,\n oldHash: hash,\n reason: replacement.reason,\n });\n }\n\n return receipt;\n },\n }).pipe(\n Effect.withSpan(SpanNames.TX_WAIT, {\n attributes: {\n chainId,\n hash,\n timeout,\n },\n })\n );\n }\n ),\n };\n })\n );\n}\n\nexport const TxManagerLive = makeTxManagerLive();\n"]}
@@ -2,7 +2,7 @@ import { describe, expect, it } from "@effect/vitest";
2
2
  import { Effect, Either, Exit, Layer } from "effect";
3
3
  import { MIN_TX_GAS } from "../constants/index.js";
4
4
  import { makeMockPublicClientLayer, TEST_CHAIN_ID, TEST_TX_HASH } from "../testing-kit/index.js";
5
- import { TxManager, TxManagerLive, TxReplacement } from "../tx/index.js";
5
+ import { makeTxManagerLive, TxManager, TxManagerLive, TxReplacement } from "../tx/index.js";
6
6
  const txReplacementLayer = Layer.succeed(TxReplacement, TxReplacement.of({
7
7
  cancel: () => Effect.succeed(TEST_TX_HASH),
8
8
  speedup: () => Effect.succeed(TEST_TX_HASH),
@@ -124,6 +124,26 @@ describe("TxManager", () => {
124
124
  }),
125
125
  }), txReplacementLayer))), Effect.scoped));
126
126
  });
127
+ describe("makeTxManagerLive", () => {
128
+ it.effect("applies custom layer policy as base for waitForReceipt", () => Effect.gen(function* () {
129
+ const manager = yield* TxManager;
130
+ const exit = yield* manager.waitForReceipt(TEST_CHAIN_ID, TEST_TX_HASH).pipe(Effect.exit);
131
+ expect(Exit.isFailure(exit)).toBe(true);
132
+ }).pipe(Effect.provide(Layer.provide(makeTxManagerLive({ receiptTimeout: 100 }), Layer.mergeAll(makeMockPublicClientLayer({
133
+ waitForTransactionReceipt: () => new Promise((_resolve, reject) => {
134
+ setTimeout(() => reject(new Error("timeout waiting for transaction")), 200);
135
+ }),
136
+ }), txReplacementLayer)))));
137
+ it.effect("per-call policy overrides layer policy", () => Effect.gen(function* () {
138
+ const manager = yield* TxManager;
139
+ const receipt = yield* manager.waitForReceipt(TEST_CHAIN_ID, TEST_TX_HASH, {
140
+ receiptTimeout: 5000,
141
+ });
142
+ expect(receipt.transactionHash).toBe(TEST_TX_HASH);
143
+ }).pipe(Effect.provide(Layer.provide(makeTxManagerLive({ receiptTimeout: 100 }), Layer.mergeAll(makeMockPublicClientLayer({
144
+ waitForTransactionReceipt: async () => TEST_RECEIPT,
145
+ }), txReplacementLayer)))));
146
+ });
127
147
  describe("getConfirmations", () => {
128
148
  it.effect("returns confirmations when called with hash param", () => Effect.gen(function* () {
129
149
  const manager = yield* TxManager;