@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.
- package/README.md +3 -0
- package/dist/constants/index.js +1 -1
- package/dist/constants/index.js.map +1 -1
- package/dist/contract/pipeline/write-and-track.d.ts.map +1 -1
- package/dist/contract/pipeline/write-and-track.js +4 -5
- package/dist/contract/pipeline/write-and-track.js.map +1 -1
- package/dist/gas/estimator.d.ts.map +1 -1
- package/dist/gas/estimator.js +61 -45
- package/dist/gas/estimator.js.map +1 -1
- package/dist/gas/service.d.ts.map +1 -1
- package/dist/gas/service.js +14 -2
- package/dist/gas/service.js.map +1 -1
- package/dist/gas/service.test.integration.js +24 -0
- package/dist/gas/service.test.integration.js.map +1 -1
- package/dist/tx/manager.d.ts +1 -0
- package/dist/tx/manager.d.ts.map +1 -1
- package/dist/tx/manager.js +231 -215
- package/dist/tx/manager.js.map +1 -1
- package/dist/tx/manager.test.integration.js +21 -1
- package/dist/tx/manager.test.integration.js.map +1 -1
- package/dist/tx/policy.d.ts +0 -1
- package/dist/tx/policy.d.ts.map +1 -1
- package/dist/tx/policy.js.map +1 -1
- package/package.json +1 -1
package/dist/tx/manager.js
CHANGED
|
@@ -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
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
yield*
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
111
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
161
|
-
|
|
177
|
+
effectiveGasPrice: receipt.effectiveGasPrice,
|
|
178
|
+
hash: receipt.transactionHash,
|
|
179
|
+
receipt,
|
|
180
|
+
status: "mined",
|
|
162
181
|
});
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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:
|
|
191
|
-
timeout,
|
|
218
|
+
message: `Failed to get receipt for ${hash}`,
|
|
192
219
|
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
|
|
220
|
+
},
|
|
221
|
+
try: async () => {
|
|
222
|
+
let replacement;
|
|
223
|
+
const receipt = await client.waitForTransactionReceipt({
|
|
196
224
|
hash,
|
|
197
|
-
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
package/dist/tx/manager.js.map
CHANGED
|
@@ -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;
|