@xyo-network/chain-bridge 1.23.2 → 2.0.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.
Files changed (115) hide show
  1. package/dist/node/BridgeActor.d.ts +1 -0
  2. package/dist/node/BridgeActor.d.ts.map +1 -1
  3. package/dist/node/index.mjs +1495 -1014
  4. package/dist/node/index.mjs.map +4 -4
  5. package/dist/node/server/routes/bridge/routeDefinitions/getRouteDefinitions.d.ts.map +1 -1
  6. package/dist/node/server/routes/bridge/routeDefinitions/pathParams/ChainIdPathParam.d.ts +2 -1
  7. package/dist/node/server/routes/bridge/routeDefinitions/pathParams/ChainIdPathParam.d.ts.map +1 -1
  8. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeConfig.d.ts +0 -22
  9. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeConfig.d.ts.map +1 -1
  10. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteEstimate.d.ts +0 -89
  11. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteEstimate.d.ts.map +1 -1
  12. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteRetry.d.ts +10 -0
  13. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteRetry.d.ts.map +1 -0
  14. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteRetryFailed.d.ts +9 -0
  15. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteRetryFailed.d.ts.map +1 -0
  16. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteStatus.d.ts +0 -81
  17. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteStatus.d.ts.map +1 -1
  18. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteStatusByTx.d.ts +28 -0
  19. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteStatusByTx.d.ts.map +1 -0
  20. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemote.d.ts +0 -81
  21. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemote.d.ts.map +1 -1
  22. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteEstimate.d.ts +0 -72
  23. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteEstimate.d.ts.map +1 -1
  24. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteMaxEstimate.d.ts +0 -72
  25. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteMaxEstimate.d.ts.map +1 -1
  26. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteRetry.d.ts +10 -0
  27. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteRetry.d.ts.map +1 -0
  28. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteRetryFailed.d.ts +9 -0
  29. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteRetryFailed.d.ts.map +1 -0
  30. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteStatus.d.ts +0 -81
  31. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteStatus.d.ts.map +1 -1
  32. package/dist/node/server/routes/bridge/routeDefinitions/routes/index.d.ts +5 -0
  33. package/dist/node/server/routes/bridge/routeDefinitions/routes/index.d.ts.map +1 -1
  34. package/dist/node/server/server.d.ts +3 -1
  35. package/dist/node/server/server.d.ts.map +1 -1
  36. package/dist/node/services/BridgeFulfillmentState.d.ts +21 -12
  37. package/dist/node/services/BridgeFulfillmentState.d.ts.map +1 -1
  38. package/dist/node/services/IBridgeServiceCollection.d.ts +8 -1
  39. package/dist/node/services/IBridgeServiceCollection.d.ts.map +1 -1
  40. package/dist/node/services/evm/getFeeStructure.d.ts +1 -1
  41. package/dist/node/services/evm/getFeeStructure.d.ts.map +1 -1
  42. package/dist/node/services/evm/index.d.ts +1 -0
  43. package/dist/node/services/evm/index.d.ts.map +1 -1
  44. package/dist/node/services/evm/resolveDeployBlock.d.ts +28 -0
  45. package/dist/node/services/evm/resolveDeployBlock.d.ts.map +1 -0
  46. package/dist/node/services/getServices.d.ts +3 -1
  47. package/dist/node/services/getServices.d.ts.map +1 -1
  48. package/dist/node/services/queue/flows/createEthToXl1BridgeJob/getJobIdForEthToXl1BridgeJob.d.ts +2 -13
  49. package/dist/node/services/queue/flows/createEthToXl1BridgeJob/getJobIdForEthToXl1BridgeJob.d.ts.map +1 -1
  50. package/dist/node/services/queue/index.d.ts +2 -0
  51. package/dist/node/services/queue/index.d.ts.map +1 -1
  52. package/dist/node/services/queue/retryFailedJobs.d.ts +39 -0
  53. package/dist/node/services/queue/retryFailedJobs.d.ts.map +1 -0
  54. package/dist/node/services/queue/retrySingleFailedJob.d.ts +29 -0
  55. package/dist/node/services/queue/retrySingleFailedJob.d.ts.map +1 -0
  56. package/dist/node/services/queue/scanner/EvmBridgeCursor.d.ts +1 -0
  57. package/dist/node/services/queue/scanner/EvmBridgeCursor.d.ts.map +1 -1
  58. package/dist/node/services/queue/scanner/EvmBridgeScanner.d.ts +1 -0
  59. package/dist/node/services/queue/scanner/EvmBridgeScanner.d.ts.map +1 -1
  60. package/dist/node/services/queue/scanner/EvmBridgeScannerRunner.d.ts +1 -2
  61. package/dist/node/services/queue/scanner/EvmBridgeScannerRunner.d.ts.map +1 -1
  62. package/dist/node/services/queue/scanner/buildEvmBridgeScannerRunner.d.ts +0 -3
  63. package/dist/node/services/queue/scanner/buildEvmBridgeScannerRunner.d.ts.map +1 -1
  64. package/dist/node/services/queue/workers/EthEventVerification.d.ts.map +1 -1
  65. package/dist/node/services/queue/workers/EthToXl1BridgeParent.d.ts.map +1 -1
  66. package/dist/node/services/queue/workers/EthTransactionMonitor.d.ts.map +1 -1
  67. package/dist/node/services/queue/workers/EthTransactionPreparation.d.ts.map +1 -1
  68. package/dist/node/services/queue/workers/EthTransactionSubmission.d.ts.map +1 -1
  69. package/dist/node/services/queue/workers/EthTransactionSubmissionStorage.d.ts.map +1 -1
  70. package/dist/node/services/queue/workers/Xl1ReserveTxFulfillment.d.ts.map +1 -1
  71. package/dist/node/services/queue/workers/Xl1ToEthBridgeParent.d.ts.map +1 -1
  72. package/dist/node/services/queue/workers/Xl1TransactionMonitor.d.ts.map +1 -1
  73. package/dist/node/services/queue/workers/Xl1TransactionPreparation.d.ts.map +1 -1
  74. package/dist/node/services/queue/workers/Xl1TransactionSubmission.d.ts.map +1 -1
  75. package/dist/node/services/queue/workers/Xl1TransactionSubmissionStorage.d.ts.map +1 -1
  76. package/dist/node/services/queue/workers/util/buildAcceptedSnapshot.d.ts +47 -0
  77. package/dist/node/services/queue/workers/util/buildAcceptedSnapshot.d.ts.map +1 -0
  78. package/dist/node/services/queue/workers/util/buildEthToXl1ReserveTx.d.ts.map +1 -1
  79. package/dist/node/services/queue/workers/util/index.d.ts +1 -1
  80. package/dist/node/services/queue/workers/util/index.d.ts.map +1 -1
  81. package/dist/node/services/queue/workers/util/resolveEvmBlockTagAtDepth.d.ts +7 -3
  82. package/dist/node/services/queue/workers/util/resolveEvmBlockTagAtDepth.d.ts.map +1 -1
  83. package/dist/node/services/queue/workers/util/verifyEthBridgeEvent.d.ts +7 -4
  84. package/dist/node/services/queue/workers/util/verifyEthBridgeEvent.d.ts.map +1 -1
  85. package/dist/node/services/util/generateBridgeEstimate.d.ts.map +1 -1
  86. package/dist/node/services/util/index.d.ts +0 -5
  87. package/dist/node/services/util/index.d.ts.map +1 -1
  88. package/dist/node/services/validation/validateBridgeEstimate.d.ts.map +1 -1
  89. package/dist/node/services/validation/validateSufficientXl1ReserveBalance.d.ts +15 -10
  90. package/dist/node/services/validation/validateSufficientXl1ReserveBalance.d.ts.map +1 -1
  91. package/dist/node/telemetry/bucketFailureReason.d.ts +17 -0
  92. package/dist/node/telemetry/bucketFailureReason.d.ts.map +1 -0
  93. package/dist/node/telemetry/createBalanceMonitor.d.ts +5 -0
  94. package/dist/node/telemetry/createBalanceMonitor.d.ts.map +1 -1
  95. package/dist/node/telemetry/createBridgeFlowMetrics.d.ts +76 -0
  96. package/dist/node/telemetry/createBridgeFlowMetrics.d.ts.map +1 -0
  97. package/dist/node/telemetry/createInFlightPoller.d.ts +29 -0
  98. package/dist/node/telemetry/createInFlightPoller.d.ts.map +1 -0
  99. package/dist/node/telemetry/createQueueMetrics.d.ts +4 -0
  100. package/dist/node/telemetry/createQueueMetrics.d.ts.map +1 -1
  101. package/dist/node/telemetry/index.d.ts +3 -0
  102. package/dist/node/telemetry/index.d.ts.map +1 -1
  103. package/package.json +89 -88
  104. package/dist/node/services/queue/workers/util/buildEthToXl1BridgePayloads.d.ts +0 -44
  105. package/dist/node/services/queue/workers/util/buildEthToXl1BridgePayloads.d.ts.map +0 -1
  106. package/dist/node/services/util/BridgeFees.d.ts +0 -13
  107. package/dist/node/services/util/BridgeFees.d.ts.map +0 -1
  108. package/dist/node/services/util/bridgeFeesAsBigInt.d.ts +0 -7
  109. package/dist/node/services/util/bridgeFeesAsBigInt.d.ts.map +0 -1
  110. package/dist/node/services/util/calculateBridgeFees.d.ts +0 -6
  111. package/dist/node/services/util/calculateBridgeFees.d.ts.map +0 -1
  112. package/dist/node/services/util/calculateMaxBridgeAmount.d.ts +0 -11
  113. package/dist/node/services/util/calculateMaxBridgeAmount.d.ts.map +0 -1
  114. package/dist/node/services/util/createBridgeTransfer.d.ts +0 -14
  115. package/dist/node/services/util/createBridgeTransfer.d.ts.map +0 -1
@@ -57,14 +57,475 @@ var getFlowProducer = (connection2, telemetry2) => {
57
57
  };
58
58
 
59
59
  // src/services/queue/workers/EthEventVerification.ts
60
- import { assertEx as assertEx3, isNull } from "@xylabs/sdk-js";
60
+ import {
61
+ assertEx as assertEx7,
62
+ isNull,
63
+ spanAsync as spanAsync4
64
+ } from "@xylabs/sdk-js";
61
65
  import { UnrecoverableError, Worker } from "bullmq";
62
66
 
63
- // src/services/queue/workers/util/buildEthToXl1BridgePayloads.ts
64
- import { assertEx, toHex } from "@xylabs/sdk-js";
67
+ // src/telemetry/bucketFailureReason.ts
68
+ function bucketFailureReason(failedReason) {
69
+ if (/below_min_bridge_amount/i.test(failedReason)) return "below_min_bridge_amount";
70
+ if (/reserve below required|reserve_insufficient/i.test(failedReason)) return "reserve_insufficient";
71
+ if (/bridgestoremote slot empty|orphaned by reorg|non-canonical at/i.test(failedReason)) return "orphaned_at_depth";
72
+ if (/attempts? exhausted|attempt \d+ expired/i.test(failedReason)) return "attempts_exhausted";
73
+ return "other";
74
+ }
75
+
76
+ // src/telemetry/createBalanceMonitor.ts
77
+ import { spanAsync } from "@xylabs/sdk-js";
78
+ var DEFAULT_INTERVAL_MS = 6e4;
79
+ var WEI_PER_GWEI = 10n ** 9n;
80
+ var TOKEN_DECIMALS = 10n ** 18n;
81
+ function createBalanceMonitor(config) {
82
+ const {
83
+ account,
84
+ bridge,
85
+ bridgeableToken,
86
+ gateway,
87
+ meter,
88
+ provider,
89
+ remoteChainId,
90
+ wallet,
91
+ intervalMs = DEFAULT_INTERVAL_MS
92
+ } = config;
93
+ let timer;
94
+ const ethWalletGasBalance = meter.createGauge(
95
+ "bridge_eth_wallet_gas_balance_gwei",
96
+ { description: "ETH balance of the bridge runner wallet (in gwei)", unit: "gwei" }
97
+ );
98
+ const liquidityTokenBalance = meter.createGauge(
99
+ "bridge_eth_liquidity_token_balance",
100
+ { description: "ERC-20 token balance of the liquidity source (in whole tokens)", unit: "tokens" }
101
+ );
102
+ const liquidityTokenAllowance = meter.createGauge(
103
+ "bridge_eth_liquidity_token_allowance",
104
+ { description: "ERC-20 token allowance from liquidity source to bridge contract (in whole tokens)", unit: "tokens" }
105
+ );
106
+ const xl1AccountBalance = meter.createGauge(
107
+ "bridge_xl1_account_balance",
108
+ { description: "XL1 native balance of the bridge reserve account (in whole XL1)", unit: "xl1" }
109
+ );
110
+ const walletAddress = wallet.address;
111
+ async function poll() {
112
+ await spanAsync("bridge:balance-monitor:poll", async () => {
113
+ try {
114
+ const balance = await provider.getBalance(walletAddress);
115
+ ethWalletGasBalance.record(Number(balance / WEI_PER_GWEI), { address: walletAddress, remoteChainId });
116
+ } catch (err) {
117
+ console.error("[BalanceMonitor] Failed to read ETH wallet gas balance:", err);
118
+ }
119
+ try {
120
+ const liquiditySourceAddress = await bridge.liquiditySource();
121
+ const balance = await bridgeableToken.balanceOf(liquiditySourceAddress);
122
+ liquidityTokenBalance.record(Number(balance / TOKEN_DECIMALS), { address: liquiditySourceAddress, remoteChainId });
123
+ } catch (err) {
124
+ console.error("[BalanceMonitor] Failed to read liquidity source token balance:", err);
125
+ }
126
+ try {
127
+ const liquiditySourceAddress = await bridge.liquiditySource();
128
+ const bridgeAddress = await bridge.getAddress();
129
+ const allowance = await bridgeableToken.allowance(liquiditySourceAddress, bridgeAddress);
130
+ liquidityTokenAllowance.record(Number(allowance / TOKEN_DECIMALS), { address: liquiditySourceAddress, remoteChainId });
131
+ } catch (err) {
132
+ console.error("[BalanceMonitor] Failed to read liquidity source token allowance:", err);
133
+ }
134
+ try {
135
+ const viewer = gateway.connection.viewer;
136
+ if (viewer) {
137
+ const balance = await viewer.account.balance.accountBalance(account.address);
138
+ xl1AccountBalance.record(Number(balance / TOKEN_DECIMALS), { address: account.address.toString(), remoteChainId });
139
+ }
140
+ } catch (err) {
141
+ console.error("[BalanceMonitor] Failed to read XL1 account balance:", err);
142
+ }
143
+ }, { timeBudgetLimit: 2e3 });
144
+ }
145
+ return {
146
+ start() {
147
+ void poll();
148
+ timer = setInterval(() => void poll(), intervalMs);
149
+ },
150
+ stop() {
151
+ if (timer) {
152
+ clearInterval(timer);
153
+ timer = void 0;
154
+ }
155
+ }
156
+ };
157
+ }
158
+
159
+ // src/telemetry/createBridgeFlowMetrics.ts
160
+ function createBridgeFlowMetrics(config) {
161
+ const { meter, remoteChainId } = config;
162
+ if (meter === void 0) {
163
+ const noop = () => {
164
+ };
165
+ return {
166
+ recordAttemptExpiration: noop,
167
+ recordRetried: noop,
168
+ recordSuccess: noop,
169
+ recordTerminalFailure: noop
170
+ };
171
+ }
172
+ const completed = meter.createCounter(
173
+ "bridge_completed_total",
174
+ { description: "Successful bridge settlements, per direction" }
175
+ );
176
+ const settleLatency = meter.createHistogram(
177
+ "bridge_settle_latency_seconds",
178
+ { description: "Wall-clock seconds from acceptance to fulfillment, per direction", unit: "s" }
179
+ );
180
+ const feeRevenue = meter.createCounter(
181
+ "bridge_fee_revenue_total",
182
+ { description: "Cumulative bridge fees collected (AttoXL1), per direction and token", unit: "AttoXL1" }
183
+ );
184
+ const attemptExpirations = meter.createCounter(
185
+ "bridge_attempt_expirations_total",
186
+ { description: "Per-attempt failures that did not exhaust retries \u2014 chain or network friction" }
187
+ );
188
+ const terminalFailures = meter.createCounter(
189
+ "bridge_terminal_failures_total",
190
+ { description: "Terminal failures (retries exhausted or UnrecoverableError), bucketed by reason" }
191
+ );
192
+ const retried = meter.createCounter(
193
+ "bridge_retried_total",
194
+ { description: "Operator-initiated retries (failed \u2192 waiting), per direction and trigger" }
195
+ );
196
+ return {
197
+ recordSuccess({
198
+ direction,
199
+ feeAmount,
200
+ latencySeconds,
201
+ token
202
+ }) {
203
+ completed.add(1, { remoteChainId, direction });
204
+ if (latencySeconds !== void 0) {
205
+ settleLatency.record(latencySeconds, { remoteChainId, direction });
206
+ }
207
+ feeRevenue.add(Number(feeAmount), {
208
+ remoteChainId,
209
+ direction,
210
+ token
211
+ });
212
+ },
213
+ recordAttemptExpiration({ direction }) {
214
+ attemptExpirations.add(1, { remoteChainId, direction });
215
+ },
216
+ recordRetried({
217
+ count,
218
+ direction,
219
+ trigger
220
+ }) {
221
+ const delta = count ?? 1;
222
+ if (delta <= 0) return;
223
+ retried.add(delta, {
224
+ remoteChainId,
225
+ direction,
226
+ trigger
227
+ });
228
+ },
229
+ recordTerminalFailure({ direction, reason }) {
230
+ terminalFailures.add(1, {
231
+ remoteChainId,
232
+ direction,
233
+ reason
234
+ });
235
+ }
236
+ };
237
+ }
238
+
239
+ // src/telemetry/createInFlightPoller.ts
240
+ import { spanAsync as spanAsync2 } from "@xylabs/sdk-js";
241
+ var DEFAULT_INTERVAL_MS2 = 6e4;
242
+ function createInFlightPoller(config) {
243
+ const {
244
+ bridgeFulfillmentMap,
245
+ meter,
246
+ remoteChainId,
247
+ intervalMs = DEFAULT_INTERVAL_MS2
248
+ } = config;
249
+ let timer;
250
+ const inFlightGauge = meter.createGauge(
251
+ "bridge_in_flight",
252
+ { description: "Bridges currently in-flight (canonical observed, fulfillment pending), per direction" }
253
+ );
254
+ const poll = async () => {
255
+ await spanAsync2("bridge:inflight:poll", async () => {
256
+ try {
257
+ let count = 0;
258
+ for await (const [, state] of bridgeFulfillmentMap) {
259
+ if (state.canonical !== void 0 && state.fulfilled === void 0) count++;
260
+ }
261
+ inFlightGauge.record(count, { remoteChainId, direction: "inbound" });
262
+ } catch (err) {
263
+ console.error("[InFlightPoller] Failed to poll bridgeFulfillmentMap:", err);
264
+ }
265
+ }, { timeBudgetLimit: 1e3 });
266
+ };
267
+ return {
268
+ poll,
269
+ start() {
270
+ void poll();
271
+ timer = setInterval(() => void poll(), intervalMs);
272
+ },
273
+ stop() {
274
+ if (timer) {
275
+ clearInterval(timer);
276
+ timer = void 0;
277
+ }
278
+ }
279
+ };
280
+ }
281
+
282
+ // src/telemetry/createQueueMetrics.ts
283
+ import { spanAsync as spanAsync3 } from "@xylabs/sdk-js";
284
+ var DEFAULT_INTERVAL_MS3 = 3e4;
285
+ function createQueueMetrics(config) {
286
+ const {
287
+ meter,
288
+ queues,
289
+ remoteChainId,
290
+ intervalMs = DEFAULT_INTERVAL_MS3
291
+ } = config;
292
+ let timer;
293
+ const waitingGauge = meter.createGauge(
294
+ "bridge_queue_waiting",
295
+ { description: "Number of waiting jobs in the bridge queue" }
296
+ );
297
+ const activeGauge = meter.createGauge(
298
+ "bridge_queue_active",
299
+ { description: "Number of active jobs in the bridge queue" }
300
+ );
301
+ const completedGauge = meter.createGauge(
302
+ "bridge_queue_completed",
303
+ { description: "Number of completed jobs in the bridge queue" }
304
+ );
305
+ const failedGauge = meter.createGauge(
306
+ "bridge_queue_failed",
307
+ { description: "Number of failed jobs in the bridge queue" }
308
+ );
309
+ const delayedGauge = meter.createGauge(
310
+ "bridge_queue_delayed",
311
+ { description: "Number of delayed jobs in the bridge queue" }
312
+ );
313
+ async function poll() {
314
+ await spanAsync3("bridge:queue-metrics:poll", async () => {
315
+ for (const [name13, queue] of Object.entries(queues)) {
316
+ try {
317
+ const counts = await queue.getJobCounts("waiting", "active", "completed", "failed", "delayed");
318
+ const attrs = { queue_name: name13, remoteChainId };
319
+ waitingGauge.record(counts.waiting ?? 0, attrs);
320
+ activeGauge.record(counts.active ?? 0, attrs);
321
+ completedGauge.record(counts.completed ?? 0, attrs);
322
+ failedGauge.record(counts.failed ?? 0, attrs);
323
+ delayedGauge.record(counts.delayed ?? 0, attrs);
324
+ } catch (err) {
325
+ console.error(`[QueueMetrics] Failed to read job counts for queue ${name13}:`, err);
326
+ }
327
+ }
328
+ }, { timeBudgetLimit: 1e3 });
329
+ }
330
+ return {
331
+ start() {
332
+ void poll();
333
+ timer = setInterval(() => void poll(), intervalMs);
334
+ },
335
+ stop() {
336
+ if (timer) {
337
+ clearInterval(timer);
338
+ timer = void 0;
339
+ }
340
+ }
341
+ };
342
+ }
343
+
344
+ // src/services/evm/asChainId.ts
345
+ import { asHex } from "@xylabs/sdk-js";
346
+ var asChainId = (value) => {
347
+ const chainId = asHex(value);
348
+ return chainId;
349
+ };
350
+
351
+ // src/services/evm/asToken.ts
352
+ import { asAddress } from "@xylabs/sdk-js";
353
+ var asToken = (value) => {
354
+ const token = asAddress(value);
355
+ return token;
356
+ };
357
+
358
+ // src/services/evm/getBridgeEscrowAddress.ts
359
+ import { asAddress as asAddress2, assertEx } from "@xylabs/sdk-js";
360
+ var tryGetBridgeEscrowAddress = (config) => {
361
+ const address = asAddress2(config.escrowAddress);
362
+ return address;
363
+ };
364
+
365
+ // src/services/evm/getBridgeFeesAddress.ts
366
+ import { asAddress as asAddress3, assertEx as assertEx2 } from "@xylabs/sdk-js";
367
+ var tryGetBridgeFeesAddress = (config) => {
368
+ const address = asAddress3(config.feesAddress);
369
+ return address;
370
+ };
371
+
372
+ // src/services/evm/getFeeStructure.ts
373
+ var getFeeStructure = (config) => {
374
+ const { feeFixed, feeRateBasisPoints } = config;
375
+ return { feeFixed, feeRateBasisPoints };
376
+ };
377
+
378
+ // src/services/evm/getMaxBridgeAmount.ts
379
+ var getMaxBridgeAmount = (config) => {
380
+ const { maxBridgeAmount } = config;
381
+ return maxBridgeAmount;
382
+ };
383
+
384
+ // src/services/evm/getMinBridgeAmount.ts
385
+ var getMinBridgeAmount = (config) => {
386
+ const { minBridgeAmount } = config;
387
+ return minBridgeAmount;
388
+ };
389
+
390
+ // src/services/evm/getRemoteChainId.ts
391
+ import { assertEx as assertEx3 } from "@xylabs/sdk-js";
392
+ var getRemoteChainId = (config) => {
393
+ const remoteChainId = assertEx3(asChainId(config.remoteChainId), () => "Invalid remote chain ID in config");
394
+ return remoteChainId;
395
+ };
396
+
397
+ // src/services/evm/getRemoteTokenAddress.ts
398
+ import { assertEx as assertEx4 } from "@xylabs/sdk-js";
399
+ var getRemoteTokenAddress = (config) => {
400
+ const token = asToken(config.remoteTokenAddress);
401
+ return assertEx4(token, () => "Remote token address is not defined in bridge configuration");
402
+ };
403
+
404
+ // src/services/evm/getBridgeWalletAccount.ts
405
+ import { isDefined as isDefined3 } from "@xylabs/sdk-js";
406
+ import { resolveWalletForActor } from "@xyo-network/chain-orchestration";
407
+ var accountServiceSingleton;
408
+ var getBridgeWalletAccount = async (config) => {
409
+ if (accountServiceSingleton) return accountServiceSingleton;
410
+ const accountPath = typeof config.accountPath === "string" ? config.accountPath : void 0;
411
+ const account = await resolveWalletForActor(config.name, accountPath);
412
+ accountServiceSingleton = account;
413
+ return accountServiceSingleton;
414
+ };
415
+
416
+ // src/services/evm/getTransferAddresses.ts
417
+ var getTransferAddresses = async (config) => {
418
+ const escrowAddress = tryGetBridgeEscrowAddress(config) ?? (await getBridgeWalletAccount(config)).address;
419
+ const feesAddress = tryGetBridgeFeesAddress(config) ?? (await getBridgeWalletAccount(config)).address;
420
+ return { escrowAddress, feesAddress };
421
+ };
422
+
423
+ // src/services/evm/getXl1ChainId.ts
424
+ import { assertEx as assertEx5, isDefined as isDefined4 } from "@xylabs/sdk-js";
425
+ var getXl1ChainId = (config) => {
426
+ const xl1ChainId = config.xl1ChainId;
427
+ if (isDefined4(xl1ChainId)) {
428
+ return assertEx5(asChainId(xl1ChainId), () => "Invalid xl1ChainId in bridge config");
429
+ }
430
+ return assertEx5(asChainId(config.chain.id), () => "Invalid chain.id in config");
431
+ };
432
+
433
+ // src/services/evm/getXl1TokenAddress.ts
434
+ import { isDefined as isDefined5 } from "@xylabs/sdk-js";
435
+ var getXl1TokenAddress = (config) => {
436
+ const token = asToken(config.xl1TokenAddress);
437
+ if (isDefined5(token)) return token;
438
+ return getXl1ChainId(config);
439
+ };
440
+
441
+ // src/services/evm/getBridgeSettings.ts
442
+ var getBridgeSettings = async (config) => {
443
+ const { feeFixed, feeRateBasisPoints } = getFeeStructure(config);
444
+ const { feesAddress, escrowAddress } = await getTransferAddresses(config);
445
+ const maxBridgeAmount = getMaxBridgeAmount(config);
446
+ const minBridgeAmount = getMinBridgeAmount(config);
447
+ const remoteChainId = getRemoteChainId(config);
448
+ const remoteTokenAddress = getRemoteTokenAddress(config);
449
+ const xl1TokenAddress = getXl1TokenAddress(config);
450
+ const xl1ChainId = getXl1ChainId(config);
451
+ return {
452
+ feeFixed,
453
+ feeRateBasisPoints,
454
+ feesAddress,
455
+ escrowAddress,
456
+ maxBridgeAmount,
457
+ minBridgeAmount,
458
+ remoteChainId,
459
+ remoteTokenAddress,
460
+ xl1TokenAddress,
461
+ xl1ChainId
462
+ };
463
+ };
464
+
465
+ // src/services/evm/getRemoteConfirmationDepth.ts
466
+ import { isDefined as isDefined6, toHex } from "@xylabs/sdk-js";
467
+ var HARDHAT_CHAIN_ID = toHex("0x7a69");
468
+ var ETHEREUM_MAINNET_CHAIN_ID = toHex("0x1");
469
+ var FALLBACK_CONFIRMATION_DEPTH = 32;
470
+ var DEFAULT_CONFIRMATION_DEPTH_BY_CHAIN_ID = /* @__PURE__ */ new Map([
471
+ [HARDHAT_CHAIN_ID, 0],
472
+ [ETHEREUM_MAINNET_CHAIN_ID, 32]
473
+ ]);
474
+ function getRemoteConfirmationDepth(config) {
475
+ if (isDefined6(config.remoteConfirmationDepth)) return config.remoteConfirmationDepth;
476
+ const chainId = toHex(getRemoteChainId(config));
477
+ return DEFAULT_CONFIRMATION_DEPTH_BY_CHAIN_ID.get(chainId) ?? FALLBACK_CONFIRMATION_DEPTH;
478
+ }
479
+
480
+ // src/services/evm/getScannerIntervalMs.ts
481
+ function getScannerIntervalMs(config) {
482
+ return config.scannerIntervalMs;
483
+ }
484
+
485
+ // src/services/evm/resolveDeployBlock.ts
486
+ import { getAddress } from "ethers";
487
+ async function contractExistsAt(provider, address, blockNumber) {
488
+ const code = await provider.getCode(getAddress(address), blockNumber);
489
+ return code !== "0x" && code.length > 2;
490
+ }
491
+ async function resolveDeployBlockByBinarySearch(provider, address) {
492
+ const head = await provider.getBlockNumber();
493
+ if (!await contractExistsAt(provider, address, head)) {
494
+ throw new Error(`resolveDeployBlockByBinarySearch: no contract code at ${address} at head=${head}`);
495
+ }
496
+ let low = 0;
497
+ let high = head;
498
+ while (low < high) {
499
+ const mid = Math.floor((low + high) / 2);
500
+ if (await contractExistsAt(provider, address, mid)) {
501
+ high = mid;
502
+ } else {
503
+ low = mid + 1;
504
+ }
505
+ }
506
+ return low;
507
+ }
508
+ async function resolveDeployBlock(opts) {
509
+ const {
510
+ configuredDeployBlock,
511
+ contractAddress,
512
+ provider
513
+ } = opts;
514
+ if (configuredDeployBlock !== void 0 && configuredDeployBlock >= 0) return configuredDeployBlock;
515
+ return resolveDeployBlockByBinarySearch(provider, contractAddress);
516
+ }
517
+
518
+ // src/services/queue/workers/util/buildAcceptedSnapshot.ts
519
+ import { hexToBigInt, toHex as toHex2 } from "@xylabs/sdk-js";
520
+ import { buildEthToXl1BridgePayloads, calculateBridgeFees } from "@xyo-network/chain-bridge-shared";
65
521
  import { PayloadBuilder } from "@xyo-network/sdk-js";
66
- import { BridgeIntentSchema, createTransferPayload } from "@xyo-network/xl1-sdk";
67
- function buildEthToXl1BridgePayloads(options2) {
522
+ import {
523
+ BridgeDestinationObservationFieldsZod,
524
+ BridgeDestinationObservationSchema,
525
+ BridgeSourceObservationFieldsZod,
526
+ BridgeSourceObservationSchema
527
+ } from "@xyo-network/xl1-sdk";
528
+ function buildAcceptedSnapshot(options2) {
68
529
  const {
69
530
  amount,
70
531
  bridgeAccountAddress,
@@ -74,36 +535,53 @@ function buildEthToXl1BridgePayloads(options2) {
74
535
  evmContractAddress,
75
536
  evmSrcAddress,
76
537
  evmTokenAddress,
538
+ evmTxHash,
539
+ feeFixed,
540
+ feeRateBasisPoints,
77
541
  feesAddress,
78
- feesAmount,
79
542
  xl1ChainId,
80
543
  xl1TokenAddress
81
544
  } = options2;
82
- assertEx(amount >= feesAmount, () => `buildEthToXl1BridgePayloads: amount (${amount}) < feesAmount (${feesAmount})`);
83
- const destAmount = amount - feesAmount;
84
- const srcAmountHex = toHex(amount);
85
- const destAmountHex = toHex(destAmount);
86
- const nonce = `evm-${evmChainId.toLowerCase()}-${evmContractAddress.toLowerCase()}-${bridgeId.toString()}`;
87
- const transfers = feesAmount === 0n || destAddress === feesAddress ? { [destAddress]: amount } : { [destAddress]: destAmount, [feesAddress]: feesAmount };
88
- const transfer = createTransferPayload(bridgeAccountAddress, transfers);
89
- const intent = new PayloadBuilder({ schema: BridgeIntentSchema }).fields({
90
- dest: xl1ChainId,
545
+ const acceptedFees = calculateBridgeFees(toHex2(amount), { feeFixed, feeRateBasisPoints });
546
+ const feesAmount = hexToBigInt(acceptedFees.feeFixed) + hexToBigInt(acceptedFees.feeVariable);
547
+ const { intent: acceptedIntent } = buildEthToXl1BridgePayloads({
548
+ amount,
549
+ bridgeAccountAddress,
550
+ bridgeId,
91
551
  destAddress,
92
- destAmount: destAmountHex,
93
- destToken: xl1TokenAddress,
94
- nonce,
95
- src: evmChainId,
96
- srcAddress: evmSrcAddress,
97
- srcAmount: srcAmountHex,
98
- srcToken: evmTokenAddress
99
- }).build();
100
- return { intent, transfer };
552
+ evmChainId,
553
+ evmContractAddress,
554
+ evmSrcAddress,
555
+ evmTokenAddress,
556
+ feesAddress,
557
+ feesAmount,
558
+ xl1ChainId,
559
+ xl1TokenAddress
560
+ });
561
+ const sourceFields = BridgeSourceObservationFieldsZod.parse({
562
+ ...acceptedIntent,
563
+ srcConfirmation: evmTxHash
564
+ });
565
+ const acceptedSourceObservation = new PayloadBuilder({ schema: BridgeSourceObservationSchema }).fields(sourceFields).build();
566
+ return {
567
+ acceptedFees,
568
+ acceptedIntent,
569
+ acceptedSourceObservation
570
+ };
571
+ }
572
+ function buildAcceptedDestinationObservation(intent, destConfirmation) {
573
+ const fields = BridgeDestinationObservationFieldsZod.parse({
574
+ ...intent,
575
+ destConfirmation
576
+ });
577
+ return new PayloadBuilder({ schema: BridgeDestinationObservationSchema }).fields(fields).build();
101
578
  }
102
579
 
103
580
  // src/services/queue/workers/util/buildEthToXl1ReserveTx.ts
104
- import { toHex as toHex2 } from "@xylabs/sdk-js";
581
+ import { toHex as toHex3 } from "@xylabs/sdk-js";
582
+ import { buildEthToXl1BridgePayloads as buildEthToXl1BridgePayloads2 } from "@xyo-network/chain-bridge-shared";
105
583
  import { PayloadBuilder as PayloadBuilder2 } from "@xyo-network/sdk-js";
106
- import { BridgeSourceObservationSchema, buildTransaction } from "@xyo-network/xl1-sdk";
584
+ import { BridgeSourceObservationSchema as BridgeSourceObservationSchema2, buildTransaction } from "@xyo-network/xl1-sdk";
107
585
  async function buildEthToXl1ReserveTx(options2) {
108
586
  const {
109
587
  amount,
@@ -122,7 +600,7 @@ async function buildEthToXl1ReserveTx(options2) {
122
600
  xl1ChainId,
123
601
  xl1TokenAddress
124
602
  } = options2;
125
- const { intent, transfer } = buildEthToXl1BridgePayloads({
603
+ const { intent, transfer } = buildEthToXl1BridgePayloads2({
126
604
  amount,
127
605
  bridgeAccountAddress: bridgeAccount.address,
128
606
  bridgeId,
@@ -136,9 +614,9 @@ async function buildEthToXl1ReserveTx(options2) {
136
614
  xl1ChainId,
137
615
  xl1TokenAddress
138
616
  });
139
- const destAmountHex = toHex2(amount - feesAmount);
140
- const srcAmountHex = toHex2(amount);
141
- const sourceObservation = new PayloadBuilder2({ schema: BridgeSourceObservationSchema }).fields({
617
+ const destAmountHex = toHex3(amount - feesAmount);
618
+ const srcAmountHex = toHex3(amount);
619
+ const sourceObservation = new PayloadBuilder2({ schema: BridgeSourceObservationSchema2 }).fields({
142
620
  dest: xl1ChainId,
143
621
  destAddress,
144
622
  destAmount: destAmountHex,
@@ -160,29 +638,29 @@ async function buildEthToXl1ReserveTx(options2) {
160
638
  }
161
639
 
162
640
  // src/services/queue/workers/util/resolveEvmBlockTagAtDepth.ts
163
- async function resolveEvmBlockTagAtDepth(provider, depth) {
641
+ async function resolveEvmBlockTagAtDepth(provider, depth, deployBlock) {
164
642
  if (depth === "finalized") return "finalized";
165
643
  if (depth === 0) return "latest";
166
644
  const head = await provider.getBlockNumber();
167
- return Math.max(head - depth, 0);
645
+ return Math.max(head - depth, deployBlock);
168
646
  }
169
647
 
170
648
  // src/services/queue/workers/util/submitEthTransaction.ts
171
649
  import {
172
- assertEx as assertEx2,
173
- hexToBigInt,
650
+ assertEx as assertEx6,
651
+ hexToBigInt as hexToBigInt2,
174
652
  toEthAddress
175
653
  } from "@xylabs/sdk-js";
176
654
  import { PayloadBuilder as PayloadBuilder3 } from "@xyo-network/sdk-js";
177
655
  import { isBridgeIntent } from "@xyo-network/xl1-sdk";
178
656
  var submitEthTransaction = async (tx, offChainPayloads, bridge, wallet) => {
179
- const xl1Transaction = assertEx2(tx[0], () => "No corresponding XL1 transaction found");
657
+ const xl1Transaction = assertEx6(tx[0], () => "No corresponding XL1 transaction found");
180
658
  const allPayloads = [...tx[1], ...offChainPayloads];
181
- const bridgeIntent = assertEx2(allPayloads.find(isBridgeIntent), () => "No bridge intent found");
659
+ const bridgeIntent = assertEx6(allPayloads.find(isBridgeIntent), () => "No bridge intent found");
182
660
  const srcAddress = toEthAddress(bridgeIntent.srcAddress);
183
661
  const destAddress = toEthAddress(bridgeIntent.destAddress);
184
- const amount = hexToBigInt(bridgeIntent.destAmount);
185
- const nonce = hexToBigInt(await PayloadBuilder3.hash(xl1Transaction));
662
+ const amount = hexToBigInt2(bridgeIntent.destAmount);
663
+ const nonce = hexToBigInt2(await PayloadBuilder3.hash(xl1Transaction));
186
664
  const bridgeTx = await bridge.connect(wallet).bridgeFromRemote(srcAddress, destAddress, amount, nonce);
187
665
  const receipt = await bridgeTx.wait(1);
188
666
  return receipt?.hash;
@@ -195,16 +673,16 @@ var submitXl1Transaction = async (preparedTx, offChain = [], gateway) => {
195
673
  };
196
674
 
197
675
  // src/services/queue/workers/util/verifyEthBridgeEvent.ts
198
- import { asHex, toAddress } from "@xylabs/sdk-js";
676
+ import { asHex as asHex2, toAddress } from "@xylabs/sdk-js";
199
677
  import { ZeroAddress } from "ethers";
200
- async function verifyEthBridgeEvent(id, bridge, provider, confirmationDepth) {
201
- const blockTag = await resolveEvmBlockTagAtDepth(provider, confirmationDepth);
678
+ async function verifyEthBridgeEvent(id, bridge, provider, confirmationDepth, deployBlock) {
679
+ const blockTag = await resolveEvmBlockTagAtDepth(provider, confirmationDepth, deployBlock);
202
680
  const data = await bridge.bridgesToRemote(id, { blockTag });
203
681
  if (data.srcAddress === ZeroAddress) {
204
682
  return null;
205
683
  }
206
684
  const filter = bridge.filters.BridgedToRemote(id);
207
- const events = await bridge.queryFilter(filter, 0, blockTag);
685
+ const events = await bridge.queryFilter(filter, deployBlock, blockTag);
208
686
  const event = events[0];
209
687
  if (event === void 0) {
210
688
  return null;
@@ -213,7 +691,8 @@ async function verifyEthBridgeEvent(id, bridge, provider, confirmationDepth) {
213
691
  amount: data.amount,
214
692
  destAddress: toAddress(data.destAddress),
215
693
  destToken: toAddress(data.destToken),
216
- evmTxHash: asHex(event.transactionHash, true),
694
+ evmTxHash: asHex2(event.transactionHash, true),
695
+ observedAt: Date.now(),
217
696
  srcAddress: toAddress(data.srcAddress)
218
697
  };
219
698
  }
@@ -222,41 +701,63 @@ async function verifyEthBridgeEvent(id, bridge, provider, confirmationDepth) {
222
701
  var name = "Verify ETH Bridge Event at Depth";
223
702
  var queueName = "eth-event-verify";
224
703
  var createWorker = (connection2, telemetry2, services) => {
225
- const svc = assertEx3(services, () => "services not provided");
704
+ const svc = assertEx7(services, () => "services not provided");
226
705
  const {
706
+ account,
227
707
  bridge,
708
+ bridgeFlowMetrics,
228
709
  bridgeFulfillmentMap,
710
+ config,
711
+ evmBridgeDeployBlock,
712
+ logger,
229
713
  provider,
230
714
  remoteConfirmationDepth
231
715
  } = svc;
232
716
  const worker = new Worker(
233
717
  queueName,
234
718
  async (job) => {
235
- const { bridgeId, identity } = job.data;
236
- const id = BigInt(bridgeId);
237
- const existing = await bridgeFulfillmentMap.get(identity);
238
- if (existing?.canonical) {
239
- await job.log(`[${identity}] canonical already set; verification skipped`);
240
- return;
241
- }
242
- const state = existing ?? { identity, previousAttempts: [] };
243
- if (state.failed) state.failed = void 0;
244
- await job.log(`[${identity}] verifying BridgedToRemote(${id}) at depth ${remoteConfirmationDepth}`);
245
- const data = await verifyEthBridgeEvent(id, bridge, provider, remoteConfirmationDepth);
246
- if (isNull(data)) {
247
- state.failed = {
248
- at: Date.now(),
249
- reason: "non-canonical at confirmation depth \u2014 orphaned by reorg or never reached canonical state"
250
- };
719
+ await spanAsync4("bridge:worker:eth-event-verify", async () => {
720
+ const { bridgeId, identity } = job.data;
721
+ const id = BigInt(bridgeId);
722
+ const existing = await bridgeFulfillmentMap.get(identity);
723
+ if (existing?.canonical) {
724
+ await job.log(`[${identity}] canonical already set; verification skipped`);
725
+ return;
726
+ }
727
+ const state = existing ?? { identity, previousAttempts: [] };
728
+ await job.log(`[${identity}] verifying BridgedToRemote(${id}) at depth ${remoteConfirmationDepth}`);
729
+ const data = await verifyEthBridgeEvent(id, bridge, provider, remoteConfirmationDepth, evmBridgeDeployBlock);
730
+ if (isNull(data)) {
731
+ await job.log(`[${identity}] non-canonical at confirmation depth \u2014 orphaned by reorg or never reached canonical state`);
732
+ throw new UnrecoverableError(`[${identity}] bridgesToRemote slot empty at confirmation depth ${remoteConfirmationDepth}`);
733
+ }
734
+ state.canonical = data;
735
+ const { feeFixed, feeRateBasisPoints } = getFeeStructure(config);
736
+ const { feesAddress } = await getTransferAddresses(config);
737
+ const snapshot = buildAcceptedSnapshot({
738
+ amount: data.amount,
739
+ bridgeAccountAddress: account.address,
740
+ bridgeId: id,
741
+ destAddress: data.destAddress,
742
+ evmChainId: config.remoteChainId,
743
+ evmContractAddress: config.remoteBridgeContractAddress,
744
+ evmSrcAddress: data.srcAddress,
745
+ evmTokenAddress: data.destToken,
746
+ evmTxHash: data.evmTxHash,
747
+ feeFixed,
748
+ feeRateBasisPoints,
749
+ feesAddress,
750
+ xl1ChainId: getXl1ChainId(config),
751
+ xl1TokenAddress: getXl1TokenAddress(config)
752
+ });
753
+ state.acceptedIntent = snapshot.acceptedIntent;
754
+ state.acceptedFees = snapshot.acceptedFees;
755
+ state.acceptedSourceObservation = snapshot.acceptedSourceObservation;
251
756
  await bridgeFulfillmentMap.set(identity, state);
252
- await job.log(`[${identity}] non-canonical at depth \u2014 marked failed`);
253
- throw new UnrecoverableError(`[${identity}] bridgesToRemote slot empty at confirmation depth ${remoteConfirmationDepth}`);
254
- }
255
- state.canonical = data;
256
- await bridgeFulfillmentMap.set(identity, state);
257
- await job.log(
258
- `[${identity}] verified: src=${data.srcAddress} dest=${data.destAddress} amount=${data.amount} evmTxHash=${data.evmTxHash}`
259
- );
757
+ await job.log(
758
+ `[${identity}] verified: src=${data.srcAddress} dest=${data.destAddress} amount=${data.amount} evmTxHash=${data.evmTxHash} snapshot: feeFixed=${snapshot.acceptedFees.feeFixed} feeVariable=${snapshot.acceptedFees.feeVariable}`
759
+ );
760
+ }, { logger, timeBudgetLimit: 5e3 });
260
761
  },
261
762
  {
262
763
  connection: connection2,
@@ -265,10 +766,15 @@ var createWorker = (connection2, telemetry2, services) => {
265
766
  }
266
767
  );
267
768
  worker.on("failed", (job, err) => {
268
- console.error(`[${name}] Job ${job?.id} failed:`, err.message);
769
+ logger.error(`[eth-verify] job ${job?.id} failed: ${err.message}`);
770
+ if (job === void 0) return;
771
+ const attemptsAllowed = job.opts.attempts ?? 1;
772
+ if (job.attemptsMade >= attemptsAllowed) {
773
+ bridgeFlowMetrics.recordTerminalFailure({ direction: "inbound", reason: bucketFailureReason(err.message) });
774
+ }
269
775
  });
270
776
  worker.on("error", (err) => {
271
- console.error(`[${name}] Worker error:`, err);
777
+ logger.error(`[eth-verify] worker error: ${err.message}`);
272
778
  });
273
779
  };
274
780
  var EthEventVerification = {
@@ -278,62 +784,20 @@ var EthEventVerification = {
278
784
  };
279
785
 
280
786
  // src/services/queue/workers/EthToXl1BridgeParent.ts
787
+ import { assertEx as assertEx8, spanAsync as spanAsync5 } from "@xylabs/sdk-js";
281
788
  import { Worker as Worker2 } from "bullmq";
282
789
  var name2 = "Bridge Ethereum to XL1";
283
790
  var queueName2 = "eth-to-xl1-bridge";
284
- var createWorker2 = (connection2, telemetry2) => {
791
+ var createWorker2 = (connection2, telemetry2, services) => {
792
+ const logger = assertEx8(services?.logger, () => "logger service not provided");
285
793
  const worker = new Worker2(
286
794
  queueName2,
287
795
  async (job) => {
288
- await job.log(`[${job.name}] start`);
289
- await job.log(`[${job.name}] done`);
290
- return {};
291
- },
292
- {
293
- connection: connection2,
294
- telemetry: telemetry2,
295
- prefix
296
- }
297
- );
298
- worker.on("failed", (job, err) => {
299
- console.error(`[${name2}] Job ${job?.id} failed:`, err.message);
300
- });
301
- worker.on("error", (err) => {
302
- console.error(`[${name2}] Worker error:`, err);
303
- });
304
- };
305
- var EthToXl1BridgeParent = {
306
- createWorker: createWorker2,
307
- name: name2,
308
- queueName: queueName2
309
- };
310
-
311
- // src/services/queue/workers/EthTransactionMonitor.ts
312
- import { assertEx as assertEx4 } from "@xylabs/sdk-js";
313
- import { PayloadBuilder as PayloadBuilder4 } from "@xyo-network/sdk-js";
314
- import { Worker as Worker3 } from "bullmq";
315
- var name3 = "Monitor Submitted ETH Transaction";
316
- var queueName3 = "eth-tx-monitor";
317
- var createWorker3 = (connection2, telemetry2, services) => {
318
- const provider = assertEx4(services?.provider, () => "provider service not provided");
319
- const stateMap = assertEx4(services?.ethTxStateMap, () => "ethTxStateMap service not provided");
320
- const worker = new Worker3(
321
- queueName3,
322
- async (job) => {
323
- const { tx } = job.data;
324
- const hash = await PayloadBuilder4.hash(tx[0]);
325
- const state = assertEx4(await stateMap.get(hash), () => "State not found");
326
- const submissionHash = assertEx4(state?.submissionHash, () => "submissionHash not found");
327
- const receipt = assertEx4(await provider.getTransactionReceipt(submissionHash), () => "Transaction receipt not found");
328
- await job.log(`[${hash}] confirmed ETH tx ${submissionHash} in block ${receipt.blockNumber}`);
329
- const { blockHash, blockNumber } = receipt;
330
- state.confirmationHash = blockHash;
331
- await stateMap.set(hash, state);
332
- return {
333
- blockHash,
334
- blockNumber,
335
- submissionHash
336
- };
796
+ return await spanAsync5("bridge:worker:eth-to-xl1-bridge", async () => {
797
+ await job.log(`[${job.name}] start`);
798
+ await job.log(`[${job.name}] done`);
799
+ return {};
800
+ }, { logger, timeBudgetLimit: 5e3 });
337
801
  },
338
802
  {
339
803
  connection: connection2,
@@ -342,252 +806,124 @@ var createWorker3 = (connection2, telemetry2, services) => {
342
806
  }
343
807
  );
344
808
  worker.on("failed", (job, err) => {
345
- console.error(`[${name3}] Job ${job?.id} failed:`, err.message);
809
+ logger.error(`[eth-to-xl1-parent] job ${job?.id} failed: ${err.message}`);
346
810
  });
347
811
  worker.on("error", (err) => {
348
- console.error(`[${name3}] Worker error:`, err);
812
+ logger.error(`[eth-to-xl1-parent] worker error: ${err.message}`);
349
813
  });
350
814
  };
351
- var EthTransactionMonitor = {
352
- createWorker: createWorker3,
353
- name: name3,
354
- queueName: queueName3
355
- };
356
-
357
- // src/services/queue/workers/EthTransactionPreparation.ts
358
- import { assertEx as assertEx15, hexToBigInt as hexToBigInt10 } from "@xylabs/sdk-js";
359
- import { PayloadBuilder as PayloadBuilder8 } from "@xyo-network/sdk-js";
360
- import { isBridgeIntent as isBridgeIntent4 } from "@xyo-network/xl1-sdk";
361
- import { Worker as Worker4 } from "bullmq";
362
- import { getAddress } from "ethers";
363
-
364
- // src/services/validation/validateAmountMeetsMinBridgeAmount.ts
365
- import { hexToBigInt as hexToBigInt2 } from "@xylabs/sdk-js";
366
- function validateAmountMeetsMinBridgeAmount({
367
- amount,
368
- minBridgeAmount
369
- }) {
370
- return amount >= hexToBigInt2(minBridgeAmount);
371
- }
372
-
373
- // src/services/validation/validateBridgeEstimateExact.ts
374
- import { hexToBigInt as hexToBigInt6, isUndefined } from "@xylabs/sdk-js";
375
- import { PayloadBuilder as PayloadBuilder6 } from "@xyo-network/sdk-js";
376
-
377
- // src/services/evm/asChainId.ts
378
- import { asHex as asHex2 } from "@xylabs/sdk-js";
379
- var asChainId = (value) => {
380
- const chainId = asHex2(value);
381
- return chainId;
382
- };
383
-
384
- // src/services/evm/asToken.ts
385
- import { asAddress } from "@xylabs/sdk-js";
386
- var asToken = (value) => {
387
- const token = asAddress(value);
388
- return token;
389
- };
390
-
391
- // src/services/evm/getBridgeEscrowAddress.ts
392
- import { asAddress as asAddress2, assertEx as assertEx5 } from "@xylabs/sdk-js";
393
- var tryGetBridgeEscrowAddress = (config) => {
394
- const address = asAddress2(config.escrowAddress);
395
- return address;
396
- };
397
-
398
- // src/services/evm/getBridgeFeesAddress.ts
399
- import { asAddress as asAddress3, assertEx as assertEx6 } from "@xylabs/sdk-js";
400
- var tryGetBridgeFeesAddress = (config) => {
401
- const address = asAddress3(config.feesAddress);
402
- return address;
403
- };
404
-
405
- // src/services/evm/getFeeStructure.ts
406
- var getFeeStructure = (config) => {
407
- const { feeFixed, feeRateBasisPoints } = config;
408
- return { feeFixed, feeRateBasisPoints };
409
- };
410
-
411
- // src/services/evm/getMaxBridgeAmount.ts
412
- var getMaxBridgeAmount = (config) => {
413
- const { maxBridgeAmount } = config;
414
- return maxBridgeAmount;
415
- };
416
-
417
- // src/services/evm/getMinBridgeAmount.ts
418
- var getMinBridgeAmount = (config) => {
419
- const { minBridgeAmount } = config;
420
- return minBridgeAmount;
421
- };
422
-
423
- // src/services/evm/getRemoteChainId.ts
424
- import { assertEx as assertEx7 } from "@xylabs/sdk-js";
425
- var getRemoteChainId = (config) => {
426
- const remoteChainId = assertEx7(asChainId(config.remoteChainId), () => "Invalid remote chain ID in config");
427
- return remoteChainId;
428
- };
429
-
430
- // src/services/evm/getRemoteTokenAddress.ts
431
- import { assertEx as assertEx8 } from "@xylabs/sdk-js";
432
- var getRemoteTokenAddress = (config) => {
433
- const token = asToken(config.remoteTokenAddress);
434
- return assertEx8(token, () => "Remote token address is not defined in bridge configuration");
435
- };
436
-
437
- // src/services/evm/getBridgeWalletAccount.ts
438
- import { isDefined as isDefined3 } from "@xylabs/sdk-js";
439
- import { resolveWalletForActor } from "@xyo-network/chain-orchestration";
440
- var accountServiceSingleton;
441
- var getBridgeWalletAccount = async (config) => {
442
- if (accountServiceSingleton) return accountServiceSingleton;
443
- const accountPath = typeof config.accountPath === "string" ? config.accountPath : void 0;
444
- const account = await resolveWalletForActor(config.name, accountPath);
445
- accountServiceSingleton = account;
446
- return accountServiceSingleton;
447
- };
448
-
449
- // src/services/evm/getTransferAddresses.ts
450
- var getTransferAddresses = async (config) => {
451
- const escrowAddress = tryGetBridgeEscrowAddress(config) ?? (await getBridgeWalletAccount(config)).address;
452
- const feesAddress = tryGetBridgeFeesAddress(config) ?? (await getBridgeWalletAccount(config)).address;
453
- return { escrowAddress, feesAddress };
454
- };
455
-
456
- // src/services/evm/getXl1ChainId.ts
457
- import { assertEx as assertEx9, isDefined as isDefined4 } from "@xylabs/sdk-js";
458
- var getXl1ChainId = (config) => {
459
- const xl1ChainId = config.xl1ChainId;
460
- if (isDefined4(xl1ChainId)) {
461
- return assertEx9(asChainId(xl1ChainId), () => "Invalid xl1ChainId in bridge config");
462
- }
463
- return assertEx9(asChainId(config.chain.id), () => "Invalid chain.id in config");
464
- };
465
-
466
- // src/services/evm/getXl1TokenAddress.ts
467
- import { isDefined as isDefined5 } from "@xylabs/sdk-js";
468
- var getXl1TokenAddress = (config) => {
469
- const token = asToken(config.xl1TokenAddress);
470
- if (isDefined5(token)) return token;
471
- return getXl1ChainId(config);
472
- };
815
+ var EthToXl1BridgeParent = {
816
+ createWorker: createWorker2,
817
+ name: name2,
818
+ queueName: queueName2
819
+ };
473
820
 
474
- // src/services/evm/getBridgeSettings.ts
475
- var getBridgeSettings = async (config) => {
476
- const { feeFixed, feeRateBasisPoints } = getFeeStructure(config);
477
- const { feesAddress, escrowAddress } = await getTransferAddresses(config);
478
- const maxBridgeAmount = getMaxBridgeAmount(config);
479
- const minBridgeAmount = getMinBridgeAmount(config);
480
- const remoteChainId = getRemoteChainId(config);
481
- const remoteTokenAddress = getRemoteTokenAddress(config);
482
- const xl1TokenAddress = getXl1TokenAddress(config);
483
- const xl1ChainId = getXl1ChainId(config);
484
- return {
485
- feeFixed,
486
- feeRateBasisPoints,
487
- feesAddress,
488
- escrowAddress,
489
- maxBridgeAmount,
490
- minBridgeAmount,
491
- remoteChainId,
492
- remoteTokenAddress,
493
- xl1TokenAddress,
494
- xl1ChainId
495
- };
821
+ // src/services/queue/workers/EthTransactionMonitor.ts
822
+ import {
823
+ assertEx as assertEx9,
824
+ hexToBigInt as hexToBigInt3,
825
+ spanAsync as spanAsync6
826
+ } from "@xylabs/sdk-js";
827
+ import { calculateBridgeFees as calculateBridgeFees2 } from "@xyo-network/chain-bridge-shared";
828
+ import { PayloadBuilder as PayloadBuilder4 } from "@xyo-network/sdk-js";
829
+ import { isBridgeIntent as isBridgeIntent2 } from "@xyo-network/xl1-sdk";
830
+ import { Worker as Worker3 } from "bullmq";
831
+ var name3 = "Monitor Submitted ETH Transaction";
832
+ var queueName3 = "eth-tx-monitor";
833
+ var createWorker3 = (connection2, telemetry2, services) => {
834
+ const provider = assertEx9(services?.provider, () => "provider service not provided");
835
+ const stateMap = assertEx9(services?.ethTxStateMap, () => "ethTxStateMap service not provided");
836
+ const logger = assertEx9(services?.logger, () => "logger service not provided");
837
+ const config = assertEx9(services?.config, () => "config service not provided");
838
+ const bridgeFlowMetrics = assertEx9(services?.bridgeFlowMetrics, () => "bridgeFlowMetrics service not provided");
839
+ const worker = new Worker3(
840
+ queueName3,
841
+ async (job) => {
842
+ return await spanAsync6("bridge:worker:eth-tx-monitor", async () => {
843
+ const { tx } = job.data;
844
+ const hash = await PayloadBuilder4.hash(tx[0]);
845
+ const state = assertEx9(await stateMap.get(hash), () => "State not found");
846
+ const submissionHash = assertEx9(state?.submissionHash, () => "submissionHash not found");
847
+ const receipt = assertEx9(await provider.getTransactionReceipt(submissionHash), () => "Transaction receipt not found");
848
+ await job.log(`[${hash}] confirmed ETH tx ${submissionHash} in block ${receipt.blockNumber}`);
849
+ const { blockHash, blockNumber } = receipt;
850
+ state.confirmationHash = blockHash;
851
+ await stateMap.set(hash, state);
852
+ const bridgeIntent = tx[1].find(isBridgeIntent2);
853
+ const feeAmount = (() => {
854
+ if (bridgeIntent === void 0) return 0n;
855
+ const fees = calculateBridgeFees2(bridgeIntent.srcAmount, getFeeStructure(config));
856
+ return hexToBigInt3(fees.feeFixed) + hexToBigInt3(fees.feeVariable);
857
+ })();
858
+ const latencySeconds = (Date.now() - job.timestamp) / 1e3;
859
+ bridgeFlowMetrics.recordSuccess({
860
+ direction: "outbound",
861
+ feeAmount,
862
+ latencySeconds,
863
+ token: getXl1TokenAddress(config)
864
+ });
865
+ return {
866
+ blockHash,
867
+ blockNumber,
868
+ submissionHash
869
+ };
870
+ }, { logger, timeBudgetLimit: 2e3 });
871
+ },
872
+ {
873
+ connection: connection2,
874
+ telemetry: telemetry2,
875
+ prefix
876
+ }
877
+ );
878
+ worker.on("failed", (job, err) => {
879
+ logger.error(`[eth-monitor] job ${job?.id} failed: ${err.message}`);
880
+ if (job === void 0) return;
881
+ const attemptsAllowed = job.opts.attempts ?? 1;
882
+ if (job.attemptsMade < attemptsAllowed) {
883
+ bridgeFlowMetrics.recordAttemptExpiration({ direction: "outbound" });
884
+ } else {
885
+ bridgeFlowMetrics.recordTerminalFailure({ direction: "outbound", reason: bucketFailureReason(err.message) });
886
+ }
887
+ });
888
+ worker.on("error", (err) => {
889
+ logger.error(`[eth-monitor] worker error: ${err.message}`);
890
+ });
891
+ };
892
+ var EthTransactionMonitor = {
893
+ createWorker: createWorker3,
894
+ name: name3,
895
+ queueName: queueName3
496
896
  };
497
897
 
498
- // src/services/evm/getRemoteConfirmationDepth.ts
499
- import { isDefined as isDefined6, toHex as toHex3 } from "@xylabs/sdk-js";
500
- var HARDHAT_CHAIN_ID = toHex3("0x7a69");
501
- var ETHEREUM_MAINNET_CHAIN_ID = toHex3("0x1");
502
- var FALLBACK_CONFIRMATION_DEPTH = 32;
503
- var DEFAULT_CONFIRMATION_DEPTH_BY_CHAIN_ID = /* @__PURE__ */ new Map([
504
- [HARDHAT_CHAIN_ID, 0],
505
- [ETHEREUM_MAINNET_CHAIN_ID, 32]
506
- ]);
507
- function getRemoteConfirmationDepth(config) {
508
- if (isDefined6(config.remoteConfirmationDepth)) return config.remoteConfirmationDepth;
509
- const chainId = toHex3(getRemoteChainId(config));
510
- return DEFAULT_CONFIRMATION_DEPTH_BY_CHAIN_ID.get(chainId) ?? FALLBACK_CONFIRMATION_DEPTH;
511
- }
898
+ // src/services/queue/workers/EthTransactionPreparation.ts
899
+ import {
900
+ assertEx as assertEx15,
901
+ hexToBigInt as hexToBigInt9,
902
+ spanAsync as spanAsync7
903
+ } from "@xylabs/sdk-js";
904
+ import { PayloadBuilder as PayloadBuilder7 } from "@xyo-network/sdk-js";
905
+ import { isBridgeIntent as isBridgeIntent5 } from "@xyo-network/xl1-sdk";
906
+ import { Worker as Worker4 } from "bullmq";
907
+ import { getAddress as getAddress2 } from "ethers";
512
908
 
513
- // src/services/evm/getScannerIntervalMs.ts
514
- function getScannerIntervalMs(config) {
515
- return config.scannerIntervalMs;
909
+ // src/services/validation/validateAmountMeetsMinBridgeAmount.ts
910
+ import { hexToBigInt as hexToBigInt4 } from "@xylabs/sdk-js";
911
+ function validateAmountMeetsMinBridgeAmount({
912
+ amount,
913
+ minBridgeAmount
914
+ }) {
915
+ return amount >= hexToBigInt4(minBridgeAmount);
516
916
  }
517
917
 
518
- // src/services/util/calculateBridgeFees.ts
519
- import { hexToBigInt as hexToBigInt3, toHex as toHex4 } from "@xylabs/sdk-js";
520
- var calculateBridgeFees = (srcAmount, feeStructure) => {
521
- const { feeFixed, feeRateBasisPoints } = feeStructure;
522
- const srcAmountBigInt = hexToBigInt3(srcAmount);
523
- const feeVariableBigInt = srcAmountBigInt * BigInt(feeRateBasisPoints) / 10000n;
524
- const feeVariable = toHex4(feeVariableBigInt);
525
- return {
526
- feeFixed,
527
- feeVariable,
528
- srcAmount
529
- };
530
- };
531
-
532
- // src/services/util/calculateMaxBridgeAmount.ts
533
- import { hexToBigInt as hexToBigInt4, toHex as toHex5 } from "@xylabs/sdk-js";
534
- var calculateMaxBridgeAmount = (balance, feeStructure) => {
535
- const { feeFixed, feeRateBasisPoints } = feeStructure;
536
- const balanceBigInt = hexToBigInt4(balance);
537
- const feeFixedBigInt = hexToBigInt4(feeFixed);
538
- if (balanceBigInt <= feeFixedBigInt) return toHex5(0n);
539
- const maxAmount = (balanceBigInt - feeFixedBigInt) * 10000n / (10000n + BigInt(feeRateBasisPoints));
540
- return toHex5(maxAmount);
541
- };
542
-
543
- // src/services/util/createBridgeTransfer.ts
544
- import { hexToBigInt as hexToBigInt5 } from "@xylabs/sdk-js";
545
- import { createTransferPayload as createTransferPayload2 } from "@xyo-network/xl1-sdk";
546
- var createBridgeTransfer = (sender, srcAmount, escrowAddress, feesAddress, context) => {
547
- const { feeFixed, feeVariable } = context;
548
- const escrowAmount = hexToBigInt5(srcAmount);
549
- const feesAmount = hexToBigInt5(feeFixed) + hexToBigInt5(feeVariable);
550
- const transfers = escrowAddress === feesAddress ? { [feesAddress]: escrowAmount + feesAmount } : { [escrowAddress]: escrowAmount, [feesAddress]: feesAmount };
551
- const transfer = createTransferPayload2(sender, transfers, context);
552
- return transfer;
553
- };
918
+ // src/services/validation/validateBridgeEstimateExact.ts
919
+ import { hexToBigInt as hexToBigInt5, isUndefined } from "@xylabs/sdk-js";
920
+ import { PayloadBuilder as PayloadBuilder5 } from "@xyo-network/sdk-js";
554
921
 
555
922
  // src/services/util/generateBridgeEstimate.ts
556
- import { toAddress as toAddress2 } from "@xylabs/sdk-js";
557
- import { PayloadBuilder as PayloadBuilder5 } from "@xyo-network/sdk-js";
558
- import { BridgeIntentSchema as BridgeIntentSchema2 } from "@xyo-network/xl1-sdk";
559
- import { v4 } from "uuid";
923
+ import { generateBridgeEstimate as generateBridgeEstimateShared } from "@xyo-network/chain-bridge-shared";
560
924
  var generateBridgeEstimate = async (srcAddress, srcAmount, destAddress, config, nonceOverride) => {
561
- const {
562
- escrowAddress,
563
- feeFixed,
564
- feeRateBasisPoints,
565
- feesAddress,
566
- remoteChainId,
567
- remoteTokenAddress,
568
- xl1ChainId,
569
- xl1TokenAddress
570
- } = await getBridgeSettings(config);
571
- const sender = toAddress2(srcAddress);
572
- const fees = calculateBridgeFees(srcAmount, { feeFixed, feeRateBasisPoints });
573
- const nonce = nonceOverride ?? v4();
574
- const bridgeIntentFields = {
575
- // Source
576
- src: xl1ChainId,
577
- srcAddress: sender,
578
- srcAmount,
579
- srcToken: xl1TokenAddress,
580
- // Destination
581
- dest: remoteChainId,
582
- destAddress,
583
- destAmount: srcAmount,
584
- // This is a 1:1 bridge. Fees are handled at the transfer level, not the bridge intent level.
585
- destToken: remoteTokenAddress,
586
- nonce
587
- };
588
- const bridgeIntent = new PayloadBuilder5({ schema: BridgeIntentSchema2 }).fields(bridgeIntentFields).build();
589
- const transfer = createBridgeTransfer(sender, srcAmount, escrowAddress, feesAddress, fees);
590
- return [bridgeIntent, transfer];
925
+ const settings = await getBridgeSettings(config);
926
+ return generateBridgeEstimateShared(srcAddress, srcAmount, destAddress, settings, nonceOverride);
591
927
  };
592
928
 
593
929
  // src/services/validation/validateBridgeEstimateExact.ts
@@ -597,16 +933,16 @@ var validateBridgeEstimateExact = async (intent, transfer, config) => {
597
933
  srcAmount,
598
934
  destAddress
599
935
  } = intent;
600
- if (hexToBigInt6(srcAmount) < hexToBigInt6(getMinBridgeAmount(config))) return false;
601
- if (hexToBigInt6(srcAmount) > hexToBigInt6(getMaxBridgeAmount(config))) return false;
936
+ if (hexToBigInt5(srcAmount) < hexToBigInt5(getMinBridgeAmount(config))) return false;
937
+ if (hexToBigInt5(srcAmount) > hexToBigInt5(getMaxBridgeAmount(config))) return false;
602
938
  const [calculatedIntent, calculatedTransfer] = await generateBridgeEstimate(srcAddress, srcAmount, destAddress, config);
603
939
  if (isUndefined(calculatedIntent) || isUndefined(calculatedTransfer)) return false;
604
940
  const { nonce: expectedIntentNonce, ...expectedIntentStatic } = calculatedIntent;
605
941
  const { nonce: actualIntentNonce, ...actualIntentStatic } = intent;
606
- if (await PayloadBuilder6.dataHash(expectedIntentStatic) !== await PayloadBuilder6.dataHash(actualIntentStatic)) return false;
942
+ if (await PayloadBuilder5.dataHash(expectedIntentStatic) !== await PayloadBuilder5.dataHash(actualIntentStatic)) return false;
607
943
  const { epoch: expectedTransferEpoch, ...expectedTransferStatic } = calculatedTransfer;
608
944
  const { epoch: actualTransferEpoch, ...actualTransferStatic } = transfer;
609
- if (await PayloadBuilder6.dataHash(expectedTransferStatic) !== await PayloadBuilder6.dataHash(actualTransferStatic)) return false;
945
+ if (await PayloadBuilder5.dataHash(expectedTransferStatic) !== await PayloadBuilder5.dataHash(actualTransferStatic)) return false;
610
946
  return true;
611
947
  };
612
948
 
@@ -618,15 +954,15 @@ import {
618
954
  payloadHashesContainsAll,
619
955
  payloadSchemasContainsAll
620
956
  } from "@xyo-network/boundwitness-validator";
621
- import { PayloadBuilder as PayloadBuilder7 } from "@xyo-network/sdk-js";
622
- import { BridgeIntentSchema as BridgeIntentSchema3, TransferSchema } from "@xyo-network/xl1-sdk";
957
+ import { PayloadBuilder as PayloadBuilder6 } from "@xyo-network/sdk-js";
958
+ import { BridgeIntentSchema, TransferSchema } from "@xyo-network/xl1-sdk";
623
959
  var validateBridgeTransaction = async (signedTxBw, intent, transfer, config) => {
624
960
  const { srcAddress } = intent;
625
961
  const chainId = getXl1ChainId(config);
626
962
  if (signedTxBw.chain !== chainId) return false;
627
963
  if (signedTxBw.payload_hashes.length != 2) return false;
628
- if (!payloadSchemasContainsAll(signedTxBw, [BridgeIntentSchema3, TransferSchema])) return false;
629
- const hashes = await PayloadBuilder7.hashes([intent, transfer]);
964
+ if (!payloadSchemasContainsAll(signedTxBw, [BridgeIntentSchema, TransferSchema])) return false;
965
+ const hashes = await PayloadBuilder6.hashes([intent, transfer]);
630
966
  if (!payloadHashesContainsAll(signedTxBw, hashes)) return false;
631
967
  const errors = await new BoundWitnessValidator(signedTxBw).validate();
632
968
  if (errors.length > 0) return false;
@@ -636,12 +972,12 @@ var validateBridgeTransaction = async (signedTxBw, intent, transfer, config) =>
636
972
  };
637
973
 
638
974
  // src/services/validation/validateSufficientLiquiditySourceAllowance.ts
639
- import { assertEx as assertEx10, hexToBigInt as hexToBigInt7 } from "@xylabs/sdk-js";
640
- import { isBridgeIntent as isBridgeIntent2 } from "@xyo-network/xl1-sdk";
975
+ import { assertEx as assertEx10, hexToBigInt as hexToBigInt6 } from "@xylabs/sdk-js";
976
+ import { isBridgeIntent as isBridgeIntent3 } from "@xyo-network/xl1-sdk";
641
977
  var validateSufficientLiquiditySourceAllowance = async (tx, offChainPayloads, bridgeableToken, bridge, logger) => {
642
978
  const allPayloads = [...tx[1], ...offChainPayloads];
643
- const bridgeIntent = assertEx10(allPayloads.find(isBridgeIntent2), () => "No bridge intent found");
644
- const amount = hexToBigInt7(bridgeIntent.destAmount);
979
+ const bridgeIntent = assertEx10(allPayloads.find(isBridgeIntent3), () => "No bridge intent found");
980
+ const amount = hexToBigInt6(bridgeIntent.destAmount);
645
981
  const liquiditySourceAddress = await bridge.liquiditySource();
646
982
  const bridgeAddress = await bridge.getAddress();
647
983
  const remainingAllowance = await bridgeableToken.allowance(liquiditySourceAddress, bridgeAddress);
@@ -650,12 +986,12 @@ var validateSufficientLiquiditySourceAllowance = async (tx, offChainPayloads, br
650
986
  };
651
987
 
652
988
  // src/services/validation/validateSufficientLiquiditySourceBalance.ts
653
- import { assertEx as assertEx11, hexToBigInt as hexToBigInt8 } from "@xylabs/sdk-js";
654
- import { isBridgeIntent as isBridgeIntent3 } from "@xyo-network/xl1-sdk";
989
+ import { assertEx as assertEx11, hexToBigInt as hexToBigInt7 } from "@xylabs/sdk-js";
990
+ import { isBridgeIntent as isBridgeIntent4 } from "@xyo-network/xl1-sdk";
655
991
  var validateSufficientLiquiditySourceBalance = async (tx, offChainPayloads, bridgeableToken, bridge, logger) => {
656
992
  const allPayloads = [...tx[1], ...offChainPayloads];
657
- const bridgeIntent = assertEx11(allPayloads.find(isBridgeIntent3), () => "No bridge intent found");
658
- const amount = hexToBigInt8(bridgeIntent.destAmount);
993
+ const bridgeIntent = assertEx11(allPayloads.find(isBridgeIntent4), () => "No bridge intent found");
994
+ const amount = hexToBigInt7(bridgeIntent.destAmount);
659
995
  const liquiditySourceAddress = await bridge.liquiditySource();
660
996
  const balance = await bridgeableToken.balanceOf(liquiditySourceAddress);
661
997
  await logger?.log(`Remaining balance: ${balance.toString()}`);
@@ -690,6 +1026,8 @@ var validateSufficientRunnerEthBalanceForGas = async (preparedTx, wallet, logger
690
1026
 
691
1027
  // src/services/validation/validateSufficientXl1ReserveBalance.ts
692
1028
  import { assertEx as assertEx13 } from "@xylabs/sdk-js";
1029
+ import { AttoXL1ConvertFactor } from "@xyo-network/xl1-sdk";
1030
+ var RESERVE_FEE_BUFFER = 1n * AttoXL1ConvertFactor.xl1;
693
1031
  async function validateSufficientXl1ReserveBalance({
694
1032
  amount,
695
1033
  bridgeAccount,
@@ -698,15 +1036,18 @@ async function validateSufficientXl1ReserveBalance({
698
1036
  }) {
699
1037
  const viewer = assertEx13(gateway.connection.viewer, () => "Gateway connection does not have a viewer");
700
1038
  const balance = await viewer.account.balance.accountBalance(bridgeAccount.address);
701
- await logger?.log(`XL1 reserve account ${bridgeAccount.address} balance: ${balance.toString()}; required: ${amount.toString()}`);
702
- return balance >= amount;
1039
+ const required = amount + RESERVE_FEE_BUFFER;
1040
+ await logger?.log(
1041
+ `XL1 reserve account ${bridgeAccount.address} balance: ${balance.toString()}; required: ${required.toString()} (amount=${amount.toString()} + buffer=${RESERVE_FEE_BUFFER.toString()})`
1042
+ );
1043
+ return balance >= required;
703
1044
  }
704
1045
 
705
1046
  // src/services/validation/validateSufficientXL1SourceAddressBalance.ts
706
1047
  import {
707
1048
  asAddress as asAddress5,
708
1049
  assertEx as assertEx14,
709
- hexToBigInt as hexToBigInt9
1050
+ hexToBigInt as hexToBigInt8
710
1051
  } from "@xylabs/sdk-js";
711
1052
  var validateSufficientXL1SourceAddressBalance = async (bridgeIntent, gateway, config, logger) => {
712
1053
  const viewer = assertEx14(gateway.connection.viewer, () => "Gateway connection does not have a viewer");
@@ -717,7 +1058,7 @@ var validateSufficientXL1SourceAddressBalance = async (bridgeIntent, gateway, co
717
1058
  } = bridgeIntent;
718
1059
  const srcAddressBranded = asAddress5(srcAddress, () => `Invalid source address in bridge intent: ${srcAddress}`);
719
1060
  const [_, calculatedTransfer] = await generateBridgeEstimate(srcAddress, srcAmount, destAddress, config);
720
- const totalAmount = Object.values(calculatedTransfer.transfers).reduce((acc, transfer) => acc + hexToBigInt9(transfer), 0n);
1061
+ const totalAmount = Object.values(calculatedTransfer.transfers).reduce((acc, transfer) => acc + hexToBigInt8(transfer), 0n);
721
1062
  const accountBalance = await viewer.account.balance.accountBalance(srcAddressBranded);
722
1063
  await logger?.log(`Account balance for ${srcAddressBranded}: ${accountBalance.toString()}`);
723
1064
  return accountBalance >= totalAmount;
@@ -731,41 +1072,44 @@ var createWorker4 = (connection2, telemetry2, services) => {
731
1072
  const bridgeableToken = assertEx15(services?.bridgeableToken, () => "bridgeableToken service not provided");
732
1073
  const stateMap = assertEx15(services?.ethTxStateMap, () => "ethTxStateMap service not provided");
733
1074
  const wallet = assertEx15(services?.wallet, () => "wallet service not provided");
1075
+ const logger = assertEx15(services?.logger, () => "logger service not provided");
734
1076
  const worker = new Worker4(
735
1077
  queueName4,
736
1078
  async (job) => {
737
- const { tx, offChainPayloads } = job.data;
738
- const hash = await PayloadBuilder8.hash(tx[0]);
739
- await job.log(`[${hash}] preparing ETH transaction`);
740
- await job.log(`[${hash}] validating liquiditySource has sufficient allowance`);
741
- if (!await validateSufficientLiquiditySourceAllowance(tx, offChainPayloads, bridgeableToken, bridge, job)) {
742
- throw new Error("Liquidity source does not have sufficient allowance for the bridge to execute the transaction");
743
- }
744
- await job.log(`[${hash}] validated liquiditySource has sufficient allowance`);
745
- await job.log(`[${hash}] validating liquiditySource has sufficient balance`);
746
- if (!await validateSufficientLiquiditySourceBalance(tx, offChainPayloads, bridgeableToken, bridge, job)) {
747
- throw new Error("Liquidity source does not have sufficient balance for the bridge to execute the transaction");
748
- }
749
- await job.log(`[${hash}] validated liquiditySource has sufficient balance`);
750
- await job.log(`[${hash}] building ETH transaction`);
751
- const allPayloads = [...tx[1], ...offChainPayloads];
752
- const bridgeIntent = assertEx15(allPayloads.find(isBridgeIntent4), () => "No bridge intent found");
753
- const amount = hexToBigInt10(bridgeIntent.destAmount);
754
- const srcAddress = getAddress(bridgeIntent.srcAddress);
755
- const destAddress = getAddress(bridgeIntent.destAddress);
756
- const nonce = hexToBigInt10(await PayloadBuilder8.hash(tx[0]));
757
- const preparedTx = await bridge.getFunction("bridgeFromRemote").populateTransaction(srcAddress, destAddress, amount, nonce);
758
- await job.log(`[${hash}] built ETH transaction`);
759
- await job.log(`[${hash}] validating tx runner has sufficient ETH for gas`);
760
- if (!await validateSufficientRunnerEthBalanceForGas(preparedTx, wallet, job)) {
761
- throw new Error("Transaction runner does not have sufficient ETH to cover estimated gas (with buffer)");
762
- }
763
- await job.log(`[${hash}] validated tx runner has sufficient ETH for gas`);
764
- await job.log(`[${hash}] storing ETH preparedTx`);
765
- await stateMap.set(hash, { preparedTx });
766
- await job.log(`[${hash}] stored ETH preparedTx`);
767
- await job.log(`[${hash}] prepared ETH transaction`);
768
- return {};
1079
+ return await spanAsync7("bridge:worker:eth-tx-prepare", async () => {
1080
+ const { tx, offChainPayloads } = job.data;
1081
+ const hash = await PayloadBuilder7.hash(tx[0]);
1082
+ await job.log(`[${hash}] preparing ETH transaction`);
1083
+ await job.log(`[${hash}] validating liquiditySource has sufficient allowance`);
1084
+ if (!await validateSufficientLiquiditySourceAllowance(tx, offChainPayloads, bridgeableToken, bridge, job)) {
1085
+ throw new Error("Liquidity source does not have sufficient allowance for the bridge to execute the transaction");
1086
+ }
1087
+ await job.log(`[${hash}] validated liquiditySource has sufficient allowance`);
1088
+ await job.log(`[${hash}] validating liquiditySource has sufficient balance`);
1089
+ if (!await validateSufficientLiquiditySourceBalance(tx, offChainPayloads, bridgeableToken, bridge, job)) {
1090
+ throw new Error("Liquidity source does not have sufficient balance for the bridge to execute the transaction");
1091
+ }
1092
+ await job.log(`[${hash}] validated liquiditySource has sufficient balance`);
1093
+ await job.log(`[${hash}] building ETH transaction`);
1094
+ const allPayloads = [...tx[1], ...offChainPayloads];
1095
+ const bridgeIntent = assertEx15(allPayloads.find(isBridgeIntent5), () => "No bridge intent found");
1096
+ const amount = hexToBigInt9(bridgeIntent.destAmount);
1097
+ const srcAddress = getAddress2(bridgeIntent.srcAddress);
1098
+ const destAddress = getAddress2(bridgeIntent.destAddress);
1099
+ const nonce = hexToBigInt9(await PayloadBuilder7.hash(tx[0]));
1100
+ const preparedTx = await bridge.getFunction("bridgeFromRemote").populateTransaction(srcAddress, destAddress, amount, nonce);
1101
+ await job.log(`[${hash}] built ETH transaction`);
1102
+ await job.log(`[${hash}] validating tx runner has sufficient ETH for gas`);
1103
+ if (!await validateSufficientRunnerEthBalanceForGas(preparedTx, wallet, job)) {
1104
+ throw new Error("Transaction runner does not have sufficient ETH to cover estimated gas (with buffer)");
1105
+ }
1106
+ await job.log(`[${hash}] validated tx runner has sufficient ETH for gas`);
1107
+ await job.log(`[${hash}] storing ETH preparedTx`);
1108
+ await stateMap.set(hash, { preparedTx });
1109
+ await job.log(`[${hash}] stored ETH preparedTx`);
1110
+ await job.log(`[${hash}] prepared ETH transaction`);
1111
+ return {};
1112
+ }, { logger, timeBudgetLimit: 1e3 });
769
1113
  },
770
1114
  {
771
1115
  connection: connection2,
@@ -774,10 +1118,10 @@ var createWorker4 = (connection2, telemetry2, services) => {
774
1118
  }
775
1119
  );
776
1120
  worker.on("failed", (job, err) => {
777
- console.error(`[${name4}] Job ${job?.id} failed:`, err.message);
1121
+ logger.error(`[eth-prep] job ${job?.id} failed: ${err.message}`);
778
1122
  });
779
1123
  worker.on("error", (err) => {
780
- console.error(`[${name4}] Worker error:`, err);
1124
+ logger.error(`[eth-prep] worker error: ${err.message}`);
781
1125
  });
782
1126
  };
783
1127
  var EthTransactionPreparation = {
@@ -787,8 +1131,12 @@ var EthTransactionPreparation = {
787
1131
  };
788
1132
 
789
1133
  // src/services/queue/workers/EthTransactionSubmission.ts
790
- import { assertEx as assertEx16, isDefined as isDefined7 } from "@xylabs/sdk-js";
791
- import { PayloadBuilder as PayloadBuilder9 } from "@xyo-network/sdk-js";
1134
+ import {
1135
+ assertEx as assertEx16,
1136
+ isDefined as isDefined7,
1137
+ spanAsync as spanAsync8
1138
+ } from "@xylabs/sdk-js";
1139
+ import { PayloadBuilder as PayloadBuilder8 } from "@xyo-network/sdk-js";
792
1140
  import { Worker as Worker5 } from "bullmq";
793
1141
  var name5 = "Submit ETH Transaction";
794
1142
  var queueName5 = "eth-tx-submit";
@@ -796,21 +1144,24 @@ var createWorker5 = (connection2, telemetry2, services) => {
796
1144
  const bridge = assertEx16(services?.bridge, () => "bridge service not provided");
797
1145
  const wallet = assertEx16(services?.wallet, () => "wallet service not provided");
798
1146
  const stateMap = assertEx16(services?.ethTxStateMap, () => "ethTxStateMap service not provided");
1147
+ const logger = assertEx16(services?.logger, () => "logger service not provided");
799
1148
  const worker = new Worker5(
800
1149
  queueName5,
801
1150
  async (job) => {
802
- const { tx, offChainPayloads } = job.data;
803
- const hash = await PayloadBuilder9.hash(tx[0]);
804
- const state = assertEx16(await stateMap.get(hash), () => `[${hash}] state not found`);
805
- const { submissionHash: existingSubmissionHash } = state;
806
- if (isDefined7(existingSubmissionHash)) {
807
- await job.log(`[${hash}] Tx already submitted with submission response hash ${existingSubmissionHash}`);
808
- return { submissionHash: existingSubmissionHash };
809
- }
810
- await job.log(`[${hash}] Submitting ETH tx`);
811
- const submissionHash = assertEx16(await submitEthTransaction(tx, offChainPayloads, bridge, wallet), () => `[${hash}] submissionHash not found in receipt`);
812
- await job.log(`[${hash}] Submitted ETH tx and received submission response hash ${submissionHash}`);
813
- return { submissionHash };
1151
+ return await spanAsync8("bridge:worker:eth-tx-submit", async () => {
1152
+ const { tx, offChainPayloads } = job.data;
1153
+ const hash = await PayloadBuilder8.hash(tx[0]);
1154
+ const state = assertEx16(await stateMap.get(hash), () => `[${hash}] state not found`);
1155
+ const { submissionHash: existingSubmissionHash } = state;
1156
+ if (isDefined7(existingSubmissionHash)) {
1157
+ await job.log(`[${hash}] Tx already submitted with submission response hash ${existingSubmissionHash}`);
1158
+ return { submissionHash: existingSubmissionHash };
1159
+ }
1160
+ await job.log(`[${hash}] Submitting ETH tx`);
1161
+ const submissionHash = assertEx16(await submitEthTransaction(tx, offChainPayloads, bridge, wallet), () => `[${hash}] submissionHash not found in receipt`);
1162
+ await job.log(`[${hash}] Submitted ETH tx and received submission response hash ${submissionHash}`);
1163
+ return { submissionHash };
1164
+ }, { logger, timeBudgetLimit: 5e3 });
814
1165
  },
815
1166
  {
816
1167
  connection: connection2,
@@ -820,10 +1171,10 @@ var createWorker5 = (connection2, telemetry2, services) => {
820
1171
  }
821
1172
  );
822
1173
  worker.on("failed", (job, err) => {
823
- console.error(`[${name5}] Job ${job?.id} failed:`, err.message);
1174
+ logger.error(`[eth-submit] job ${job?.id} failed: ${err.message}`);
824
1175
  });
825
1176
  worker.on("error", (err) => {
826
- console.error(`[${name5}] Worker error:`, err);
1177
+ logger.error(`[eth-submit] worker error: ${err.message}`);
827
1178
  });
828
1179
  };
829
1180
  var EthTransactionSubmission = {
@@ -833,34 +1184,41 @@ var EthTransactionSubmission = {
833
1184
  };
834
1185
 
835
1186
  // src/services/queue/workers/EthTransactionSubmissionStorage.ts
836
- import { assertEx as assertEx17, isDefined as isDefined8 } from "@xylabs/sdk-js";
837
- import { PayloadBuilder as PayloadBuilder10 } from "@xyo-network/sdk-js";
1187
+ import {
1188
+ assertEx as assertEx17,
1189
+ isDefined as isDefined8,
1190
+ spanAsync as spanAsync9
1191
+ } from "@xylabs/sdk-js";
1192
+ import { PayloadBuilder as PayloadBuilder9 } from "@xyo-network/sdk-js";
838
1193
  import { Worker as Worker6 } from "bullmq";
839
1194
  var name6 = "Store ETH Transaction Submission";
840
1195
  var queueName6 = "eth-tx-store-submission";
841
1196
  var createWorker6 = (connection2, telemetry2, services) => {
842
1197
  const stateMap = assertEx17(services?.ethTxStateMap, () => "ethTxStateMap service not provided");
1198
+ const logger = assertEx17(services?.logger, () => "logger service not provided");
843
1199
  const worker = new Worker6(
844
1200
  queueName6,
845
1201
  async (job) => {
846
- const { tx } = job.data;
847
- const hash = await PayloadBuilder10.hash(tx[0]);
848
- const state = assertEx17(await stateMap.get(hash), () => `[${hash}] state not found`);
849
- const { submissionHash: existingSubmissionHash } = state;
850
- if (isDefined8(existingSubmissionHash)) {
851
- await job.log(`[${hash}] submissionHash already stored as ${existingSubmissionHash}`);
852
- return { submissionHash: existingSubmissionHash };
853
- }
854
- const childrenValues = await job.getChildrenValues();
855
- const jobKey = `${prefix}:${EthTransactionSubmission.queueName}:${hash}`;
856
- const childValues = childrenValues?.[jobKey];
857
- const submissionHash = childValues?.submissionHash;
858
- const resolvedSubmissionHash = assertEx17(submissionHash, () => `[${hash}] child submissionHash not found in children values`);
859
- await job.log(`[${hash}] Storing ETH submissionHash`);
860
- state.submissionHash = resolvedSubmissionHash;
861
- await stateMap.set(hash, state);
862
- await job.log(`[${hash}] Stored ETH submissionHash`);
863
- return { submissionHash: resolvedSubmissionHash };
1202
+ return await spanAsync9("bridge:worker:eth-tx-store-submission", async () => {
1203
+ const { tx } = job.data;
1204
+ const hash = await PayloadBuilder9.hash(tx[0]);
1205
+ const state = assertEx17(await stateMap.get(hash), () => `[${hash}] state not found`);
1206
+ const { submissionHash: existingSubmissionHash } = state;
1207
+ if (isDefined8(existingSubmissionHash)) {
1208
+ await job.log(`[${hash}] submissionHash already stored as ${existingSubmissionHash}`);
1209
+ return { submissionHash: existingSubmissionHash };
1210
+ }
1211
+ const childrenValues = await job.getChildrenValues();
1212
+ const jobKey = `${prefix}:${EthTransactionSubmission.queueName}:${hash}`;
1213
+ const childValues = childrenValues?.[jobKey];
1214
+ const submissionHash = childValues?.submissionHash;
1215
+ const resolvedSubmissionHash = assertEx17(submissionHash, () => `[${hash}] child submissionHash not found in children values`);
1216
+ await job.log(`[${hash}] Storing ETH submissionHash`);
1217
+ state.submissionHash = resolvedSubmissionHash;
1218
+ await stateMap.set(hash, state);
1219
+ await job.log(`[${hash}] Stored ETH submissionHash`);
1220
+ return { submissionHash: resolvedSubmissionHash };
1221
+ }, { logger, timeBudgetLimit: 500 });
864
1222
  },
865
1223
  {
866
1224
  connection: connection2,
@@ -869,10 +1227,10 @@ var createWorker6 = (connection2, telemetry2, services) => {
869
1227
  }
870
1228
  );
871
1229
  worker.on("failed", (job, err) => {
872
- console.error(`[${name6}] Job ${job?.id} failed:`, err.message);
1230
+ logger.error(`[eth-submit-store] job ${job?.id} failed: ${err.message}`);
873
1231
  });
874
1232
  worker.on("error", (err) => {
875
- console.error(`[${name6}] Worker error:`, err);
1233
+ logger.error(`[eth-submit-store] worker error: ${err.message}`);
876
1234
  });
877
1235
  };
878
1236
  var EthTransactionSubmissionStorage = {
@@ -885,19 +1243,43 @@ var EthTransactionSubmissionStorage = {
885
1243
  import {
886
1244
  assertEx as assertEx18,
887
1245
  delay,
888
- hexToBigInt as hexToBigInt11,
1246
+ hexToBigInt as hexToBigInt10,
889
1247
  isDefined as isDefined9,
890
1248
  isNull as isNull2,
891
- toHex as toHex6
1249
+ spanAsync as spanAsync10
892
1250
  } from "@xylabs/sdk-js";
893
- import { PayloadBuilder as PayloadBuilder11 } from "@xyo-network/sdk-js";
1251
+ import { PayloadBuilder as PayloadBuilder10 } from "@xyo-network/sdk-js";
894
1252
  import { asXL1BlockNumber } from "@xyo-network/xl1-sdk";
895
- import { Worker as Worker7 } from "bullmq";
1253
+ import { UnrecoverableError as UnrecoverableError2, Worker as Worker7 } from "bullmq";
896
1254
  var name7 = "XL1 Reserve Transfer Fulfillment";
897
1255
  var queueName7 = "eth-to-xl1-fulfill";
898
1256
  var EXP_BLOCKS_AHEAD = 1e3;
899
1257
  var POLL_INTERVAL_MS = 5e3;
900
1258
  var CONCURRENCY = 32;
1259
+ async function markFulfilled(ctx, attempt) {
1260
+ const {
1261
+ bridgeFlowMetrics,
1262
+ fulfillmentMap,
1263
+ identity,
1264
+ state,
1265
+ xl1TokenAddress
1266
+ } = ctx;
1267
+ state.fulfilled = attempt;
1268
+ state.currentAttempt = void 0;
1269
+ const intent = assertEx18(state.acceptedIntent, () => `[${identity}] acceptedIntent missing on fulfillment \u2014 Stage 1 did not snapshot`);
1270
+ state.acceptedDestinationObservation = buildAcceptedDestinationObservation(intent, attempt.txHash);
1271
+ await fulfillmentMap.set(identity, state);
1272
+ const observedAt = state.canonical?.observedAt;
1273
+ const latencySeconds = observedAt === void 0 ? void 0 : (attempt.submittedAt - observedAt) / 1e3;
1274
+ const acceptedFees = state.acceptedFees;
1275
+ const feeAmount = acceptedFees === void 0 ? 0n : hexToBigInt10(acceptedFees.feeFixed) + hexToBigInt10(acceptedFees.feeVariable);
1276
+ bridgeFlowMetrics.recordSuccess({
1277
+ direction: "inbound",
1278
+ feeAmount,
1279
+ latencySeconds,
1280
+ token: xl1TokenAddress
1281
+ });
1282
+ }
901
1283
  async function tryResumeCurrentAttempt(ctx) {
902
1284
  const {
903
1285
  fulfillmentMap,
@@ -909,9 +1291,7 @@ async function tryResumeCurrentAttempt(ctx) {
909
1291
  const attempt = assertEx18(state.currentAttempt, () => "tryResumeCurrentAttempt called without currentAttempt");
910
1292
  const found = await viewer.transaction.byHash(attempt.txHash);
911
1293
  if (isDefined9(found) && !isNull2(found)) {
912
- state.fulfilled = attempt;
913
- state.currentAttempt = void 0;
914
- await fulfillmentMap.set(identity, state);
1294
+ await markFulfilled(ctx, attempt);
915
1295
  await job.log(`[${identity}] tx ${attempt.txHash} included on resume`);
916
1296
  return;
917
1297
  }
@@ -939,9 +1319,9 @@ async function buildAndSubmitFreshAttempt(ctx) {
939
1319
  xl1ChainId,
940
1320
  xl1TokenAddress
941
1321
  } = ctx;
942
- const canonical = assertEx18(state.canonical, () => "buildAndSubmitFreshAttempt requires canonical");
943
- const fees = calculateBridgeFees(toHex6(canonical.amount), getFeeStructure(config));
944
- const feesAmount = hexToBigInt11(fees.feeFixed) + hexToBigInt11(fees.feeVariable);
1322
+ const canonical = assertEx18(state.canonical, () => `[${identity}] buildAndSubmitFreshAttempt requires canonical`);
1323
+ const acceptedFees = assertEx18(state.acceptedFees, () => `[${identity}] acceptedFees missing \u2014 Stage 1 did not snapshot`);
1324
+ const feesAmount = hexToBigInt10(acceptedFees.feeFixed) + hexToBigInt10(acceptedFees.feeVariable);
945
1325
  const { feesAddress } = await getTransferAddresses(config);
946
1326
  const sufficient = await validateSufficientXl1ReserveBalance({
947
1327
  amount: canonical.amount,
@@ -949,9 +1329,9 @@ async function buildAndSubmitFreshAttempt(ctx) {
949
1329
  gateway
950
1330
  });
951
1331
  if (!sufficient) {
952
- const msg = `[${identity}] XL1 reserve below required ${canonical.amount.toString()}; will retry`;
1332
+ const msg = `[${identity}] XL1 reserve below required ${canonical.amount.toString()}; terminal \u2014 refill and retry`;
953
1333
  await job.log(msg);
954
- throw new Error(msg);
1334
+ throw new UnrecoverableError2(msg);
955
1335
  }
956
1336
  const head = await viewer.currentBlockNumber();
957
1337
  const nbf = asXL1BlockNumber(head, true);
@@ -973,7 +1353,7 @@ async function buildAndSubmitFreshAttempt(ctx) {
973
1353
  xl1ChainId,
974
1354
  xl1TokenAddress
975
1355
  });
976
- const txHash = await PayloadBuilder11.hash(tx[0]);
1356
+ const txHash = await PayloadBuilder10.hash(tx[0]);
977
1357
  state.currentAttempt = {
978
1358
  exp: head + EXP_BLOCKS_AHEAD,
979
1359
  nbf: head,
@@ -988,6 +1368,7 @@ async function buildAndSubmitFreshAttempt(ctx) {
988
1368
  }
989
1369
  async function pollUntilIncludedOrExpired(ctx) {
990
1370
  const {
1371
+ bridgeFlowMetrics,
991
1372
  fulfillmentMap,
992
1373
  identity,
993
1374
  job,
@@ -998,9 +1379,7 @@ async function pollUntilIncludedOrExpired(ctx) {
998
1379
  while (true) {
999
1380
  const found = await viewer.transaction.byHash(attempt.txHash);
1000
1381
  if (isDefined9(found) && !isNull2(found)) {
1001
- state.fulfilled = attempt;
1002
- state.currentAttempt = void 0;
1003
- await fulfillmentMap.set(identity, state);
1382
+ await markFulfilled(ctx, attempt);
1004
1383
  await job.log(`[${identity}] tx ${attempt.txHash} included`);
1005
1384
  return;
1006
1385
  }
@@ -1009,6 +1388,7 @@ async function pollUntilIncludedOrExpired(ctx) {
1009
1388
  state.previousAttempts.push(attempt);
1010
1389
  state.currentAttempt = void 0;
1011
1390
  await fulfillmentMap.set(identity, state);
1391
+ bridgeFlowMetrics.recordAttemptExpiration({ direction: "inbound" });
1012
1392
  const msg = `[${identity}] attempt ${state.previousAttempts.length} expired (head=${head}, exp=${attempt.exp})`;
1013
1393
  await job.log(msg);
1014
1394
  throw new Error(msg);
@@ -1020,68 +1400,65 @@ var createWorker7 = (connection2, telemetry2, services) => {
1020
1400
  const svc = assertEx18(services, () => "services not provided");
1021
1401
  const {
1022
1402
  account,
1403
+ bridgeFlowMetrics,
1023
1404
  bridgeFulfillmentMap,
1024
1405
  config,
1025
- gateway
1406
+ gateway,
1407
+ logger
1026
1408
  } = svc;
1027
1409
  const worker = new Worker7(
1028
1410
  queueName7,
1029
1411
  async (job) => {
1030
- const {
1031
- bridgeId,
1032
- contractAddress,
1033
- evmChainId,
1034
- identity,
1035
- xl1ChainId,
1036
- xl1TokenAddress
1037
- } = job.data;
1038
- const initialState = await bridgeFulfillmentMap.get(identity);
1039
- if (initialState?.fulfilled) {
1040
- await job.log(`[${identity}] already fulfilled \u2014 skipping`);
1041
- return;
1042
- }
1043
- const state = assertEx18(initialState, () => `[${identity}] no fulfillment state \u2014 Stage 1 did not run`);
1044
- const canonical = assertEx18(state.canonical, () => `[${identity}] canonical missing \u2014 Stage 1 did not write canonical`);
1045
- if (state.failed) {
1046
- state.failed = void 0;
1047
- await bridgeFulfillmentMap.set(identity, state);
1048
- }
1049
- const minBridgeAmount = getMinBridgeAmount(config);
1050
- if (!validateAmountMeetsMinBridgeAmount({ amount: canonical.amount, minBridgeAmount })) {
1051
- state.failed = {
1052
- at: Date.now(),
1053
- reason: `below_min_bridge_amount: amount=${canonical.amount} minBridgeAmount=${minBridgeAmount}`
1054
- };
1055
- await bridgeFulfillmentMap.set(identity, state);
1056
- await job.log(`[${identity}] amount ${canonical.amount} below minBridgeAmount ${minBridgeAmount}; terminal`);
1057
- return;
1058
- }
1059
- const viewer = assertEx18(gateway.connection.viewer, () => `[${identity}] gateway viewer not available`);
1060
- const ctx = {
1061
- fulfillmentMap: bridgeFulfillmentMap,
1062
- identity,
1063
- job,
1064
- state,
1065
- viewer
1066
- };
1067
- if (state.currentAttempt) {
1068
- await tryResumeCurrentAttempt(ctx);
1069
- if (state.fulfilled) return;
1070
- }
1071
- if (!state.currentAttempt) {
1072
- await buildAndSubmitFreshAttempt({
1073
- ...ctx,
1074
- account,
1075
- bridgeId: BigInt(bridgeId),
1076
- config,
1412
+ await spanAsync10("bridge:worker:eth-to-xl1-fulfill", async () => {
1413
+ const {
1414
+ bridgeId,
1077
1415
  contractAddress,
1078
1416
  evmChainId,
1079
- gateway,
1417
+ identity,
1080
1418
  xl1ChainId,
1081
1419
  xl1TokenAddress
1082
- });
1083
- }
1084
- await pollUntilIncludedOrExpired(ctx);
1420
+ } = job.data;
1421
+ const initialState = await bridgeFulfillmentMap.get(identity);
1422
+ if (initialState?.fulfilled) {
1423
+ await job.log(`[${identity}] already fulfilled \u2014 skipping`);
1424
+ return;
1425
+ }
1426
+ const state = assertEx18(initialState, () => `[${identity}] no fulfillment state \u2014 Stage 1 did not run`);
1427
+ const canonical = assertEx18(state.canonical, () => `[${identity}] canonical missing \u2014 Stage 1 did not write canonical`);
1428
+ const minBridgeAmount = getMinBridgeAmount(config);
1429
+ if (!validateAmountMeetsMinBridgeAmount({ amount: canonical.amount, minBridgeAmount })) {
1430
+ await job.log(`[${identity}] amount ${canonical.amount} below minBridgeAmount ${minBridgeAmount}; terminal`);
1431
+ throw new UnrecoverableError2(`[${identity}] below_min_bridge_amount: amount=${canonical.amount} minBridgeAmount=${minBridgeAmount}`);
1432
+ }
1433
+ const viewer = assertEx18(gateway.connection.viewer, () => `[${identity}] gateway viewer not available`);
1434
+ const ctx = {
1435
+ bridgeFlowMetrics,
1436
+ fulfillmentMap: bridgeFulfillmentMap,
1437
+ identity,
1438
+ job,
1439
+ state,
1440
+ viewer,
1441
+ xl1TokenAddress
1442
+ };
1443
+ if (state.currentAttempt) {
1444
+ await tryResumeCurrentAttempt(ctx);
1445
+ if (state.fulfilled) return;
1446
+ }
1447
+ if (!state.currentAttempt) {
1448
+ await buildAndSubmitFreshAttempt({
1449
+ ...ctx,
1450
+ account,
1451
+ bridgeId: BigInt(bridgeId),
1452
+ config,
1453
+ contractAddress,
1454
+ evmChainId,
1455
+ gateway,
1456
+ xl1ChainId,
1457
+ xl1TokenAddress
1458
+ });
1459
+ }
1460
+ await pollUntilIncludedOrExpired(ctx);
1461
+ }, { logger });
1085
1462
  },
1086
1463
  {
1087
1464
  concurrency: CONCURRENCY,
@@ -1090,22 +1467,16 @@ var createWorker7 = (connection2, telemetry2, services) => {
1090
1467
  telemetry: telemetry2
1091
1468
  }
1092
1469
  );
1093
- worker.on("failed", async (job, err) => {
1094
- if (!isDefined9(job)) return;
1095
- const attemptsAllowed = job.opts.attempts ?? Infinity;
1096
- if (job.attemptsMade < attemptsAllowed) return;
1097
- const identity = job.data?.identity;
1098
- if (!isDefined9(identity)) return;
1099
- const state = await bridgeFulfillmentMap.get(identity);
1100
- if (!isDefined9(state)) return;
1101
- state.failed = {
1102
- at: Date.now(),
1103
- reason: `attempts exhausted (${job.attemptsMade}/${attemptsAllowed}): ${err.message}`
1104
- };
1105
- await bridgeFulfillmentMap.set(identity, state);
1470
+ worker.on("failed", (job, err) => {
1471
+ logger.error(`[xl1-fulfill] job ${job?.id} failed: ${err.message}`);
1472
+ if (job === void 0) return;
1473
+ const attemptsAllowed = job.opts.attempts ?? 1;
1474
+ if (job.attemptsMade >= attemptsAllowed) {
1475
+ bridgeFlowMetrics.recordTerminalFailure({ direction: "inbound", reason: bucketFailureReason(err.message) });
1476
+ }
1106
1477
  });
1107
1478
  worker.on("error", (err) => {
1108
- console.error(`[${name7}] Worker error:`, err);
1479
+ logger.error(`[xl1-fulfill] worker error: ${err.message}`);
1109
1480
  });
1110
1481
  };
1111
1482
  var Xl1ReserveTxFulfillment = {
@@ -1115,16 +1486,20 @@ var Xl1ReserveTxFulfillment = {
1115
1486
  };
1116
1487
 
1117
1488
  // src/services/queue/workers/Xl1ToEthBridgeParent.ts
1489
+ import { assertEx as assertEx19, spanAsync as spanAsync11 } from "@xylabs/sdk-js";
1118
1490
  import { Worker as Worker8 } from "bullmq";
1119
1491
  var name8 = "Bridge XL1 to Ethereum";
1120
1492
  var queueName8 = "xl1-to-eth-bridge";
1121
- var createWorker8 = (connection2, telemetry2) => {
1493
+ var createWorker8 = (connection2, telemetry2, services) => {
1494
+ const logger = assertEx19(services?.logger, () => "logger service not provided");
1122
1495
  const worker = new Worker8(
1123
1496
  queueName8,
1124
1497
  async (job) => {
1125
- await job.log(`[${job.name}] start`);
1126
- await job.log(`[${job.name}] done`);
1127
- return {};
1498
+ return await spanAsync11("bridge:worker:xl1-to-eth-bridge", async () => {
1499
+ await job.log(`[${job.name}] start`);
1500
+ await job.log(`[${job.name}] done`);
1501
+ return {};
1502
+ }, { logger, timeBudgetLimit: 5e3 });
1128
1503
  },
1129
1504
  {
1130
1505
  connection: connection2,
@@ -1133,10 +1508,10 @@ var createWorker8 = (connection2, telemetry2) => {
1133
1508
  }
1134
1509
  );
1135
1510
  worker.on("failed", (job, err) => {
1136
- console.error(`[${name8}] Job ${job?.id} failed:`, err.message);
1511
+ logger.error(`[xl1-to-eth-parent] job ${job?.id} failed: ${err.message}`);
1137
1512
  });
1138
1513
  worker.on("error", (err) => {
1139
- console.error(`[${name8}] Worker error:`, err);
1514
+ logger.error(`[xl1-to-eth-parent] worker error: ${err.message}`);
1140
1515
  });
1141
1516
  };
1142
1517
  var Xl1ToEthBridgeParent = {
@@ -1147,41 +1522,45 @@ var Xl1ToEthBridgeParent = {
1147
1522
 
1148
1523
  // src/services/queue/workers/Xl1TransactionMonitor.ts
1149
1524
  import {
1150
- assertEx as assertEx19,
1525
+ assertEx as assertEx20,
1151
1526
  isDefined as isDefined10,
1152
- isNull as isNull3
1527
+ isNull as isNull3,
1528
+ spanAsync as spanAsync12
1153
1529
  } from "@xylabs/sdk-js";
1154
- import { PayloadBuilder as PayloadBuilder12 } from "@xyo-network/sdk-js";
1155
- import { UnrecoverableError as UnrecoverableError2, Worker as Worker9 } from "bullmq";
1530
+ import { PayloadBuilder as PayloadBuilder11 } from "@xyo-network/sdk-js";
1531
+ import { UnrecoverableError as UnrecoverableError3, Worker as Worker9 } from "bullmq";
1156
1532
  var name9 = "Monitor Submitted XL1 Transaction";
1157
1533
  var queueName9 = "xl1-tx-monitor";
1158
1534
  var createWorker9 = (connection2, telemetry2, services) => {
1159
- const gateway = assertEx19(services?.gateway, () => "gateway service not provided");
1160
- const stateMap = assertEx19(services?.xl1TxStateMap, () => "xl1TxStateMap service not provided");
1161
- const viewer = assertEx19(gateway.connection.viewer, () => "viewer not defined on gateway");
1535
+ const gateway = assertEx20(services?.gateway, () => "gateway service not provided");
1536
+ const stateMap = assertEx20(services?.xl1TxStateMap, () => "xl1TxStateMap service not provided");
1537
+ const viewer = assertEx20(gateway.connection.viewer, () => "viewer not defined on gateway");
1538
+ const logger = assertEx20(services?.logger, () => "logger service not provided");
1162
1539
  const worker = new Worker9(
1163
1540
  queueName9,
1164
1541
  async (job) => {
1165
- const { tx } = job.data;
1166
- const hash = await PayloadBuilder12.hash(tx[0]);
1167
- const state = assertEx19(await stateMap.get(hash), () => `[${hash}] state not found`);
1168
- const submissionHash = assertEx19(state.submissionHash, () => `[${hash}] submissionHash not found`);
1169
- await job.log(`[${hash}] Checking for XL1 transaction inclusion on chain`);
1170
- const foundTx = await viewer.transaction.byHash(submissionHash);
1171
- if (isDefined10(foundTx) && !isNull3(foundTx)) {
1172
- await job.log(`[${hash}] Found transaction on chain`);
1173
- const submissionHash2 = await PayloadBuilder12.hash(foundTx[0]);
1174
- return { submissionHash: submissionHash2 };
1175
- }
1176
- const currentBlockNumber = await viewer.currentBlockNumber();
1177
- if (tx[0].exp < currentBlockNumber) {
1178
- await job.log(
1179
- `[${hash}] Transaction expired at block ${tx[0].exp}, current block ${currentBlockNumber}`
1180
- );
1181
- throw new UnrecoverableError2(`[${hash}] Transaction expired and will never be included`);
1182
- }
1183
- await job.log(`[${hash}] Transaction not yet included, retrying later`);
1184
- throw new Error(`[${hash}] Transaction not yet included`);
1542
+ return await spanAsync12("bridge:worker:xl1-tx-monitor", async () => {
1543
+ const { tx } = job.data;
1544
+ const hash = await PayloadBuilder11.hash(tx[0]);
1545
+ const state = assertEx20(await stateMap.get(hash), () => `[${hash}] state not found`);
1546
+ const submissionHash = assertEx20(state.submissionHash, () => `[${hash}] submissionHash not found`);
1547
+ await job.log(`[${hash}] Checking for XL1 transaction inclusion on chain`);
1548
+ const foundTx = await viewer.transaction.byHash(submissionHash);
1549
+ if (isDefined10(foundTx) && !isNull3(foundTx)) {
1550
+ await job.log(`[${hash}] Found transaction on chain`);
1551
+ const submissionHash2 = await PayloadBuilder11.hash(foundTx[0]);
1552
+ return { submissionHash: submissionHash2 };
1553
+ }
1554
+ const currentBlockNumber = await viewer.currentBlockNumber();
1555
+ if (tx[0].exp < currentBlockNumber) {
1556
+ await job.log(
1557
+ `[${hash}] Transaction expired at block ${tx[0].exp}, current block ${currentBlockNumber}`
1558
+ );
1559
+ throw new UnrecoverableError3(`[${hash}] Transaction expired and will never be included`);
1560
+ }
1561
+ await job.log(`[${hash}] Transaction not yet included, retrying later`);
1562
+ throw new Error(`[${hash}] Transaction not yet included`);
1563
+ }, { logger, timeBudgetLimit: 2e3 });
1185
1564
  },
1186
1565
  {
1187
1566
  connection: connection2,
@@ -1190,10 +1569,10 @@ var createWorker9 = (connection2, telemetry2, services) => {
1190
1569
  }
1191
1570
  );
1192
1571
  worker.on("failed", (job, err) => {
1193
- console.error(`[${name9}] Job ${job?.id} failed:`, err.message);
1572
+ logger.error(`[xl1-monitor] job ${job?.id} failed: ${err.message}`);
1194
1573
  });
1195
1574
  worker.on("error", (err) => {
1196
- console.error(`[${name9}] Worker error:`, err);
1575
+ logger.error(`[xl1-monitor] worker error: ${err.message}`);
1197
1576
  });
1198
1577
  };
1199
1578
  var Xl1TransactionMonitor = {
@@ -1203,25 +1582,28 @@ var Xl1TransactionMonitor = {
1203
1582
  };
1204
1583
 
1205
1584
  // src/services/queue/workers/Xl1TransactionPreparation.ts
1206
- import { assertEx as assertEx20 } from "@xylabs/sdk-js";
1207
- import { PayloadBuilder as PayloadBuilder13 } from "@xyo-network/sdk-js";
1585
+ import { assertEx as assertEx21, spanAsync as spanAsync13 } from "@xylabs/sdk-js";
1586
+ import { PayloadBuilder as PayloadBuilder12 } from "@xyo-network/sdk-js";
1208
1587
  import { Worker as Worker10 } from "bullmq";
1209
1588
  var name10 = "Prepare XL1 Transaction";
1210
1589
  var queueName10 = "xl1-tx-prepare";
1211
1590
  var createWorker10 = (connection2, telemetry2, services) => {
1212
- const stateMap = assertEx20(services?.xl1TxStateMap, () => "xl1TxStateMap service not provided");
1591
+ const stateMap = assertEx21(services?.xl1TxStateMap, () => "xl1TxStateMap service not provided");
1592
+ const logger = assertEx21(services?.logger, () => "logger service not provided");
1213
1593
  const worker = new Worker10(
1214
1594
  queueName10,
1215
1595
  async (job) => {
1216
- const { tx, offChainPayloads = [] } = job.data;
1217
- const hash = await PayloadBuilder13.hash(tx[0]);
1218
- await job.log(`[${hash}] preparing XL1 transaction`);
1219
- const preparedTx = tx;
1220
- await job.log(`[${hash}] storing XL1 preparedTx`);
1221
- await stateMap.set(hash, { offChainPayloads, preparedTx });
1222
- await job.log(`[${hash}] stored XL1 preparedTx`);
1223
- await job.log(`[${hash}] prepared XL1 transaction`);
1224
- return { preparedTx };
1596
+ return await spanAsync13("bridge:worker:xl1-tx-prepare", async () => {
1597
+ const { tx, offChainPayloads = [] } = job.data;
1598
+ const hash = await PayloadBuilder12.hash(tx[0]);
1599
+ await job.log(`[${hash}] preparing XL1 transaction`);
1600
+ const preparedTx = tx;
1601
+ await job.log(`[${hash}] storing XL1 preparedTx`);
1602
+ await stateMap.set(hash, { offChainPayloads, preparedTx });
1603
+ await job.log(`[${hash}] stored XL1 preparedTx`);
1604
+ await job.log(`[${hash}] prepared XL1 transaction`);
1605
+ return { preparedTx };
1606
+ }, { logger, timeBudgetLimit: 1e3 });
1225
1607
  },
1226
1608
  {
1227
1609
  connection: connection2,
@@ -1230,10 +1612,10 @@ var createWorker10 = (connection2, telemetry2, services) => {
1230
1612
  }
1231
1613
  );
1232
1614
  worker.on("failed", (job, err) => {
1233
- console.error(`[${name10}] Job ${job?.id} failed:`, err.message);
1615
+ logger.error(`[xl1-prep] job ${job?.id} failed: ${err.message}`);
1234
1616
  });
1235
1617
  worker.on("error", (err) => {
1236
- console.error(`[${name10}] Worker error:`, err);
1618
+ logger.error(`[xl1-prep] worker error: ${err.message}`);
1237
1619
  });
1238
1620
  };
1239
1621
  var Xl1TransactionPreparation = {
@@ -1243,34 +1625,41 @@ var Xl1TransactionPreparation = {
1243
1625
  };
1244
1626
 
1245
1627
  // src/services/queue/workers/Xl1TransactionSubmission.ts
1246
- import { assertEx as assertEx21, isDefined as isDefined11 } from "@xylabs/sdk-js";
1247
- import { PayloadBuilder as PayloadBuilder14 } from "@xyo-network/sdk-js";
1628
+ import {
1629
+ assertEx as assertEx22,
1630
+ isDefined as isDefined11,
1631
+ spanAsync as spanAsync14
1632
+ } from "@xylabs/sdk-js";
1633
+ import { PayloadBuilder as PayloadBuilder13 } from "@xyo-network/sdk-js";
1248
1634
  import { Worker as Worker11 } from "bullmq";
1249
1635
  var name11 = "Submit XL1 Transaction";
1250
1636
  var queueName11 = "xl1-tx-submit";
1251
1637
  var createWorker11 = (connection2, telemetry2, services) => {
1252
- const gateway = assertEx21(services?.gateway, () => "gateway service not provided");
1253
- const stateMap = assertEx21(services?.xl1TxStateMap, () => "xl1TxStateMap service not provided");
1638
+ const gateway = assertEx22(services?.gateway, () => "gateway service not provided");
1639
+ const stateMap = assertEx22(services?.xl1TxStateMap, () => "xl1TxStateMap service not provided");
1640
+ const logger = assertEx22(services?.logger, () => "logger service not provided");
1254
1641
  const worker = new Worker11(
1255
1642
  queueName11,
1256
1643
  async (job) => {
1257
- const { tx } = job.data;
1258
- const hash = await PayloadBuilder14.hash(tx[0]);
1259
- const state = assertEx21(await stateMap.get(hash), () => `[${hash}] state not found`);
1260
- const {
1261
- preparedTx,
1262
- offChainPayloads = [],
1263
- submissionHash: existingSubmissionHash
1264
- } = state;
1265
- if (isDefined11(existingSubmissionHash)) {
1266
- await job.log(`[${hash}] Tx already submitted with submission response hash ${existingSubmissionHash}`);
1267
- return { submissionHash: existingSubmissionHash };
1268
- }
1269
- const txToSubmit = assertEx21(preparedTx, () => `[${hash}] preparedTx not found`);
1270
- await job.log(`[${hash}] Submitting XL1 tx`);
1271
- const [submissionHash] = await submitXl1Transaction(txToSubmit, offChainPayloads, gateway);
1272
- await job.log(`[${hash}] Submitted XL1 tx`);
1273
- return { submissionHash };
1644
+ return await spanAsync14("bridge:worker:xl1-tx-submit", async () => {
1645
+ const { tx } = job.data;
1646
+ const hash = await PayloadBuilder13.hash(tx[0]);
1647
+ const state = assertEx22(await stateMap.get(hash), () => `[${hash}] state not found`);
1648
+ const {
1649
+ preparedTx,
1650
+ offChainPayloads = [],
1651
+ submissionHash: existingSubmissionHash
1652
+ } = state;
1653
+ if (isDefined11(existingSubmissionHash)) {
1654
+ await job.log(`[${hash}] Tx already submitted with submission response hash ${existingSubmissionHash}`);
1655
+ return { submissionHash: existingSubmissionHash };
1656
+ }
1657
+ const txToSubmit = assertEx22(preparedTx, () => `[${hash}] preparedTx not found`);
1658
+ await job.log(`[${hash}] Submitting XL1 tx`);
1659
+ const [submissionHash] = await submitXl1Transaction(txToSubmit, offChainPayloads, gateway);
1660
+ await job.log(`[${hash}] Submitted XL1 tx`);
1661
+ return { submissionHash };
1662
+ }, { logger, timeBudgetLimit: 5e3 });
1274
1663
  },
1275
1664
  {
1276
1665
  connection: connection2,
@@ -1279,10 +1668,10 @@ var createWorker11 = (connection2, telemetry2, services) => {
1279
1668
  }
1280
1669
  );
1281
1670
  worker.on("failed", (job, err) => {
1282
- console.error(`[${name11}] Job ${job?.id} failed:`, err.message);
1671
+ logger.error(`[xl1-submit] job ${job?.id} failed: ${err.message}`);
1283
1672
  });
1284
1673
  worker.on("error", (err) => {
1285
- console.error(`[${name11}] Worker error:`, err);
1674
+ logger.error(`[xl1-submit] worker error: ${err.message}`);
1286
1675
  });
1287
1676
  };
1288
1677
  var Xl1TransactionSubmission = {
@@ -1292,34 +1681,41 @@ var Xl1TransactionSubmission = {
1292
1681
  };
1293
1682
 
1294
1683
  // src/services/queue/workers/Xl1TransactionSubmissionStorage.ts
1295
- import { assertEx as assertEx22, isDefined as isDefined12 } from "@xylabs/sdk-js";
1296
- import { PayloadBuilder as PayloadBuilder15 } from "@xyo-network/sdk-js";
1684
+ import {
1685
+ assertEx as assertEx23,
1686
+ isDefined as isDefined12,
1687
+ spanAsync as spanAsync15
1688
+ } from "@xylabs/sdk-js";
1689
+ import { PayloadBuilder as PayloadBuilder14 } from "@xyo-network/sdk-js";
1297
1690
  import { Worker as Worker12 } from "bullmq";
1298
1691
  var name12 = "Store XL1 Transaction Submission";
1299
1692
  var queueName12 = "xl1-tx-store-submission";
1300
1693
  var createWorker12 = (connection2, telemetry2, services) => {
1301
- const stateMap = assertEx22(services?.xl1TxStateMap, () => "xl1TxStateMap service not provided");
1694
+ const stateMap = assertEx23(services?.xl1TxStateMap, () => "xl1TxStateMap service not provided");
1695
+ const logger = assertEx23(services?.logger, () => "logger service not provided");
1302
1696
  const worker = new Worker12(
1303
1697
  queueName12,
1304
1698
  async (job) => {
1305
- const { tx } = job.data;
1306
- const hash = await PayloadBuilder15.hash(tx[0]);
1307
- const state = assertEx22(await stateMap.get(hash), () => `[${hash}] state not found`);
1308
- const { submissionHash: existingSubmissionHash } = state;
1309
- if (isDefined12(existingSubmissionHash)) {
1310
- await job.log(`[${hash}] submissionHash already stored as ${existingSubmissionHash}`);
1311
- return { submissionHash: existingSubmissionHash };
1312
- }
1313
- const childrenValues = await job.getChildrenValues();
1314
- const jobKey = `${prefix}:${Xl1TransactionSubmission.queueName}:${hash}`;
1315
- const childValues = childrenValues?.[jobKey];
1316
- const submissionHash = childValues?.submissionHash;
1317
- const resolvedSubmissionHash = assertEx22(submissionHash, () => `[${hash}] child submissionHash not found in children values`);
1318
- await job.log(`[${hash}] Storing XL1 submissionHash`);
1319
- state.submissionHash = resolvedSubmissionHash;
1320
- await stateMap.set(hash, state);
1321
- await job.log(`[${hash}] Stored XL1 submissionHash`);
1322
- return { submissionHash: resolvedSubmissionHash };
1699
+ return await spanAsync15("bridge:worker:xl1-tx-store-submission", async () => {
1700
+ const { tx } = job.data;
1701
+ const hash = await PayloadBuilder14.hash(tx[0]);
1702
+ const state = assertEx23(await stateMap.get(hash), () => `[${hash}] state not found`);
1703
+ const { submissionHash: existingSubmissionHash } = state;
1704
+ if (isDefined12(existingSubmissionHash)) {
1705
+ await job.log(`[${hash}] submissionHash already stored as ${existingSubmissionHash}`);
1706
+ return { submissionHash: existingSubmissionHash };
1707
+ }
1708
+ const childrenValues = await job.getChildrenValues();
1709
+ const jobKey = `${prefix}:${Xl1TransactionSubmission.queueName}:${hash}`;
1710
+ const childValues = childrenValues?.[jobKey];
1711
+ const submissionHash = childValues?.submissionHash;
1712
+ const resolvedSubmissionHash = assertEx23(submissionHash, () => `[${hash}] child submissionHash not found in children values`);
1713
+ await job.log(`[${hash}] Storing XL1 submissionHash`);
1714
+ state.submissionHash = resolvedSubmissionHash;
1715
+ await stateMap.set(hash, state);
1716
+ await job.log(`[${hash}] Stored XL1 submissionHash`);
1717
+ return { submissionHash: resolvedSubmissionHash };
1718
+ }, { logger, timeBudgetLimit: 500 });
1323
1719
  },
1324
1720
  {
1325
1721
  connection: connection2,
@@ -1328,10 +1724,10 @@ var createWorker12 = (connection2, telemetry2, services) => {
1328
1724
  }
1329
1725
  );
1330
1726
  worker.on("failed", (job, err) => {
1331
- console.error(`[${name12}] Job ${job?.id} failed:`, err.message);
1727
+ logger.error(`[xl1-submit-store] job ${job?.id} failed: ${err.message}`);
1332
1728
  });
1333
1729
  worker.on("error", (err) => {
1334
- console.error(`[${name12}] Worker error:`, err);
1730
+ logger.error(`[xl1-submit-store] worker error: ${err.message}`);
1335
1731
  });
1336
1732
  };
1337
1733
  var Xl1TransactionSubmissionStorage = {
@@ -1343,13 +1739,13 @@ var Xl1TransactionSubmissionStorage = {
1343
1739
  // src/services/queue/workers/createWorkers.ts
1344
1740
  var createWorkers = (connection2, telemetry2, services) => {
1345
1741
  EthEventVerification.createWorker(connection2, telemetry2, services);
1346
- EthToXl1BridgeParent.createWorker(connection2, telemetry2);
1742
+ EthToXl1BridgeParent.createWorker(connection2, telemetry2, services);
1347
1743
  EthTransactionMonitor.createWorker(connection2, telemetry2, services);
1348
1744
  EthTransactionPreparation.createWorker(connection2, telemetry2, services);
1349
1745
  EthTransactionSubmission.createWorker(connection2, telemetry2, services);
1350
1746
  EthTransactionSubmissionStorage.createWorker(connection2, telemetry2, services);
1351
1747
  Xl1ReserveTxFulfillment.createWorker(connection2, telemetry2, services);
1352
- Xl1ToEthBridgeParent.createWorker(connection2, telemetry2);
1748
+ Xl1ToEthBridgeParent.createWorker(connection2, telemetry2, services);
1353
1749
  Xl1TransactionMonitor.createWorker(connection2, telemetry2, services);
1354
1750
  Xl1TransactionPreparation.createWorker(connection2, telemetry2, services);
1355
1751
  Xl1TransactionSubmission.createWorker(connection2, telemetry2, services);
@@ -1357,8 +1753,9 @@ var createWorkers = (connection2, telemetry2, services) => {
1357
1753
  };
1358
1754
 
1359
1755
  // src/services/queue/flows/createEthToXl1BridgeJob/getJobIdForEthToXl1BridgeJob.ts
1756
+ import { buildBridgeIdentity } from "@xyo-network/chain-bridge-shared";
1360
1757
  function getJobIdForEthToXl1BridgeJob(context) {
1361
- return `evm-${context.evmChainId.toLowerCase()}-${context.contractAddress.toLowerCase()}-${context.bridgeId.toString()}`;
1758
+ return buildBridgeIdentity(context);
1362
1759
  }
1363
1760
 
1364
1761
  // src/services/queue/flows/createEthToXl1BridgeJob/createEthToXl1BridgeJob.ts
@@ -1406,9 +1803,9 @@ var createEthToXl1BridgeJob = async (flowProducer2, context) => {
1406
1803
  };
1407
1804
 
1408
1805
  // src/services/queue/flows/createXl1ToEthBridgeJob/getJobIdForXl1ToEthBridgeJob.ts
1409
- import { PayloadBuilder as PayloadBuilder16 } from "@xyo-network/sdk-js";
1806
+ import { PayloadBuilder as PayloadBuilder15 } from "@xyo-network/sdk-js";
1410
1807
  var getJobIdForXl1ToEthBridgeJob = async (tx) => {
1411
- const jobId = await PayloadBuilder16.hash(tx[0]);
1808
+ const jobId = await PayloadBuilder15.hash(tx[0]);
1412
1809
  return jobId;
1413
1810
  };
1414
1811
 
@@ -1594,6 +1991,107 @@ var getXl1ToEthQueues = (config) => {
1594
1991
  return xl1ToEthQueues;
1595
1992
  };
1596
1993
 
1994
+ // src/services/queue/retryFailedJobs.ts
1995
+ var DEFAULT_LIMIT = 100;
1996
+ function hasAnyFilter(filters) {
1997
+ return filters.bridgeIds !== void 0 && filters.bridgeIds.length > 0 || filters.since !== void 0 || filters.until !== void 0 || filters.reasonContains !== void 0 || filters.srcAddress !== void 0;
1998
+ }
1999
+ var AVAILABLE_BULK_FILTERS = [
2000
+ "bridgeIds",
2001
+ "since",
2002
+ "until",
2003
+ "reasonContains",
2004
+ "srcAddress"
2005
+ ];
2006
+ async function isFilterMatch(job, id, filters, srcAddressLookup) {
2007
+ const {
2008
+ bridgeIds,
2009
+ reasonContains,
2010
+ since,
2011
+ srcAddress,
2012
+ until
2013
+ } = filters;
2014
+ if (bridgeIds !== void 0 && !bridgeIds.includes(id)) return false;
2015
+ if (since !== void 0 || until !== void 0) {
2016
+ const finishedOn = job.finishedOn;
2017
+ if (finishedOn === void 0) return false;
2018
+ if (since !== void 0 && finishedOn < since) return false;
2019
+ if (until !== void 0 && finishedOn > until) return false;
2020
+ }
2021
+ if (reasonContains !== void 0 && !(job.failedReason ?? "").includes(reasonContains)) {
2022
+ return false;
2023
+ }
2024
+ if (srcAddress !== void 0) {
2025
+ if (srcAddressLookup === void 0) return false;
2026
+ const found = await srcAddressLookup(id);
2027
+ if (found === void 0 || found.toLowerCase() !== srcAddress.toLowerCase()) return false;
2028
+ }
2029
+ return true;
2030
+ }
2031
+ async function retryFailedJobs(opts) {
2032
+ const {
2033
+ filters,
2034
+ queue,
2035
+ srcAddressLookup
2036
+ } = opts;
2037
+ const { dryRun = false, limit = DEFAULT_LIMIT } = filters;
2038
+ const failedJobs = await queue.getFailed(0, Math.max(limit - 1, 0));
2039
+ const retried = [];
2040
+ const skipped = [];
2041
+ for (const job of failedJobs) {
2042
+ const id = job.id;
2043
+ if (id === void 0) continue;
2044
+ const matches = await isFilterMatch(job, id, filters, srcAddressLookup);
2045
+ if (!matches) {
2046
+ skipped.push({ id, reason: "filter-mismatch" });
2047
+ continue;
2048
+ }
2049
+ const state = await job.getState();
2050
+ if (state !== "failed") {
2051
+ skipped.push({ id, reason: "not-in-failed-state" });
2052
+ continue;
2053
+ }
2054
+ if (!dryRun) await job.retry();
2055
+ retried.push({ failedReason: job.failedReason, id });
2056
+ }
2057
+ const total = retried.length + skipped.length;
2058
+ return {
2059
+ counts: {
2060
+ retried: retried.length,
2061
+ skipped: skipped.length,
2062
+ total
2063
+ },
2064
+ retried,
2065
+ skipped
2066
+ };
2067
+ }
2068
+
2069
+ // src/services/queue/retrySingleFailedJob.ts
2070
+ async function retrySingleFailedJob(opts) {
2071
+ const {
2072
+ dryRun = false,
2073
+ jobId,
2074
+ queue
2075
+ } = opts;
2076
+ const job = await queue.getJob(jobId);
2077
+ if (job === void 0) {
2078
+ return { bridgeId: jobId, status: "not-found" };
2079
+ }
2080
+ const state = await job.getState();
2081
+ if (state !== "failed") {
2082
+ return { bridgeId: jobId, status: "not-in-failed-state" };
2083
+ }
2084
+ const failedReason = job.failedReason;
2085
+ if (!dryRun) {
2086
+ await job.retry();
2087
+ }
2088
+ return {
2089
+ bridgeId: jobId,
2090
+ failedReason,
2091
+ status: "retried"
2092
+ };
2093
+ }
2094
+
1597
2095
  // src/services/queue/scanner/createEnqueueEthToXl1Bridge.ts
1598
2096
  function createEnqueueEthToXl1Bridge(options2) {
1599
2097
  const {
@@ -1631,6 +2129,7 @@ function createEvmBridgeScanner(options2) {
1631
2129
  chainId,
1632
2130
  confirmationDepth,
1633
2131
  cursors,
2132
+ deployBlock,
1634
2133
  enqueue,
1635
2134
  provider
1636
2135
  } = options2;
@@ -1638,11 +2137,16 @@ function createEvmBridgeScanner(options2) {
1638
2137
  return {
1639
2138
  async scan(options3) {
1640
2139
  const jobLog = options3?.jobLog;
1641
- const blockTag = await resolveEvmBlockTagAtDepth(provider, confirmationDepth);
2140
+ const blockTag = await resolveEvmBlockTagAtDepth(provider, confirmationDepth, deployBlock);
1642
2141
  const confirmedHigh = await bridge.nextBridgeToId({ blockTag });
1643
2142
  const cursor = await cursors.get(cursorKey);
1644
2143
  const lastProcessedId = isUndefined2(cursor) ? 0n : BigInt(cursor.lastProcessedId);
2144
+ const priorProcessedBlock = isUndefined2(cursor?.lastProcessedBlock) ? deployBlock : Number(cursor.lastProcessedBlock);
2145
+ const nextProcessedBlock = typeof blockTag === "number" ? Math.max(priorProcessedBlock, blockTag) : priorProcessedBlock;
1645
2146
  if (confirmedHigh <= lastProcessedId) {
2147
+ if (nextProcessedBlock !== priorProcessedBlock || isUndefined2(cursor)) {
2148
+ await cursors.set(cursorKey, { lastProcessedBlock: nextProcessedBlock.toString(), lastProcessedId: lastProcessedId.toString() });
2149
+ }
1646
2150
  return {
1647
2151
  confirmedHigh,
1648
2152
  enqueued: 0,
@@ -1654,7 +2158,7 @@ function createEvmBridgeScanner(options2) {
1654
2158
  await enqueue(id, jobLog);
1655
2159
  enqueued++;
1656
2160
  }
1657
- await cursors.set(cursorKey, { lastProcessedId: confirmedHigh.toString() });
2161
+ await cursors.set(cursorKey, { lastProcessedBlock: nextProcessedBlock.toString(), lastProcessedId: confirmedHigh.toString() });
1658
2162
  return {
1659
2163
  confirmedHigh,
1660
2164
  enqueued,
@@ -1665,6 +2169,7 @@ function createEvmBridgeScanner(options2) {
1665
2169
  }
1666
2170
 
1667
2171
  // src/services/queue/scanner/EvmBridgeScannerRunner.ts
2172
+ import { spanAsync as spanAsync16 } from "@xylabs/sdk-js";
1668
2173
  import { Queue as Queue3, Worker as Worker13 } from "bullmq";
1669
2174
  var QUEUE_NAME = "eth-to-xl1-scanner";
1670
2175
  var REPEATABLE_JOB_NAME = "scan";
@@ -1673,10 +2178,10 @@ function createEvmBridgeScannerRunner(options2) {
1673
2178
  const {
1674
2179
  connection: connection2,
1675
2180
  intervalMs,
2181
+ logger,
1676
2182
  scanner,
1677
2183
  telemetry: telemetry2
1678
2184
  } = options2;
1679
- const logger = options2.logger ?? console;
1680
2185
  let queue;
1681
2186
  let worker;
1682
2187
  return {
@@ -1687,21 +2192,23 @@ function createEvmBridgeScannerRunner(options2) {
1687
2192
  telemetry: telemetry2
1688
2193
  });
1689
2194
  worker = new Worker13(QUEUE_NAME, async (job) => {
1690
- await job.log("[scanner] tick start");
1691
- const result = await scanner.scan({ jobLog: (msg) => job.log(msg) });
1692
- await job.log(
1693
- `[scanner] tick complete: enqueued=${result.enqueued} confirmedHigh=${result.confirmedHigh.toString()} lastProcessedId=${result.lastProcessedId.toString()}`
1694
- );
2195
+ await spanAsync16("bridge:scanner:tick", async () => {
2196
+ await job.log("[scanner] tick start");
2197
+ const result = await scanner.scan({ jobLog: (msg) => job.log(msg) });
2198
+ await job.log(
2199
+ `[scanner] tick complete: enqueued=${result.enqueued} confirmedHigh=${result.confirmedHigh.toString()} lastProcessedId=${result.lastProcessedId.toString()}`
2200
+ );
2201
+ }, { logger, timeBudgetLimit: 2e3 });
1695
2202
  }, {
1696
2203
  connection: connection2,
1697
2204
  prefix,
1698
2205
  telemetry: telemetry2
1699
2206
  });
1700
2207
  worker.on("failed", (job, err) => {
1701
- logger.error(`[scanner] tick ${job?.id} failed:`, err.message);
2208
+ logger.error(`[scanner] tick ${job?.id} failed: ${err.message}`);
1702
2209
  });
1703
2210
  worker.on("error", (err) => {
1704
- logger.error("[scanner] worker error:", err);
2211
+ logger.error(`[scanner] worker error: ${err.message}`);
1705
2212
  });
1706
2213
  await queue.add(REPEATABLE_JOB_NAME, {}, {
1707
2214
  jobId: REPEATABLE_JOB_ID,
@@ -1723,7 +2230,6 @@ function buildEvmBridgeScannerRunner(options2) {
1723
2230
  config,
1724
2231
  connection: connection2,
1725
2232
  flowProducer: flowProducer2,
1726
- logger,
1727
2233
  services,
1728
2234
  telemetry: telemetry2
1729
2235
  } = options2;
@@ -1745,13 +2251,14 @@ function buildEvmBridgeScannerRunner(options2) {
1745
2251
  chainId: evmChainId,
1746
2252
  confirmationDepth: services.remoteConfirmationDepth,
1747
2253
  cursors: services.evmBridgeCursorMap,
2254
+ deployBlock: services.evmBridgeDeployBlock,
1748
2255
  enqueue,
1749
2256
  provider: services.provider
1750
2257
  });
1751
2258
  return createEvmBridgeScannerRunner({
1752
2259
  connection: connection2,
1753
2260
  intervalMs,
1754
- logger,
2261
+ logger: services.logger,
1755
2262
  scanner,
1756
2263
  telemetry: telemetry2
1757
2264
  });
@@ -1825,8 +2332,7 @@ var addProbeRoutes = (app) => {
1825
2332
 
1826
2333
  // src/server/routes/bridge/routeDefinitions/routes/bridgeConfig.ts
1827
2334
  import { requestHandlerValidator } from "@xylabs/express";
1828
- import { BridgeSettingsZod } from "@xyo-network/chain-orchestration";
1829
- var BridgeConfigResponseZod = BridgeSettingsZod;
2335
+ import { BridgeConfigResponseZod } from "@xyo-network/chain-bridge-shared";
1830
2336
  var validateRequest = requestHandlerValidator({ response: BridgeConfigResponseZod });
1831
2337
  var makeBridgeConfigRoute = (config) => {
1832
2338
  return {
@@ -1865,54 +2371,37 @@ var makeBridgeConfigRoute = (config) => {
1865
2371
  // src/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteEstimate.ts
1866
2372
  import { requestHandlerValidator as requestHandlerValidator2 } from "@xylabs/express";
1867
2373
  import {
1868
- assertEx as assertEx23,
1869
- hexToBigInt as hexToBigInt12,
1870
- toAddress as toAddress3
2374
+ assertEx as assertEx24,
2375
+ hexToBigInt as hexToBigInt11,
2376
+ toAddress as toAddress2
1871
2377
  } from "@xylabs/sdk-js";
1872
- import { PayloadZodStrictOfSchema } from "@xyo-network/sdk-js";
1873
2378
  import {
1874
- BridgeIntentFieldsZod,
1875
- BridgeIntentSchema as BridgeIntentSchema4,
1876
- buildUnsignedTransaction,
1877
- toXL1BlockNumber,
1878
- TransactionBoundWitnessZod,
1879
- TransferZod
1880
- } from "@xyo-network/xl1-sdk";
1881
- import { z } from "zod";
2379
+ BridgeFromRemoteEstimateBodyZod,
2380
+ BridgeFromRemoteEstimateResponseZod,
2381
+ buildEthToXl1BridgePayloads as buildEthToXl1BridgePayloads3,
2382
+ calculateBridgeFees as calculateBridgeFees3
2383
+ } from "@xyo-network/chain-bridge-shared";
2384
+ import { buildUnsignedTransaction, toXL1BlockNumber } from "@xyo-network/xl1-sdk";
2385
+ import { z as z2 } from "zod/mini";
1882
2386
 
1883
2387
  // src/server/routes/bridge/routeDefinitions/pathParams/ChainIdPathParam.ts
1884
2388
  import {
1885
2389
  asHex as asHex3,
1886
2390
  HexZod,
1887
- isUndefined as isUndefined3
2391
+ isDefined as isDefined14
1888
2392
  } from "@xylabs/sdk-js";
2393
+ import { z } from "zod/mini";
1889
2394
  var getRemoteChainIdZod = (config) => {
1890
2395
  const remoteChainId = getRemoteChainId(config);
1891
- return HexZod.superRefine((val, ctx) => {
1892
- const chainId = asHex3(val);
1893
- if (isUndefined3(chainId)) {
1894
- ctx.addIssue("Not a valid chain id");
1895
- return;
1896
- }
1897
- if (chainId !== remoteChainId) {
1898
- ctx.addIssue(`Only ${remoteChainId} is supported`);
1899
- }
1900
- });
2396
+ return HexZod.check(z.refine((val) => {
2397
+ const chainId = asHex3(val);
2398
+ return isDefined14(chainId) && chainId === remoteChainId;
2399
+ }, { error: `Only ${remoteChainId} is supported` }));
1901
2400
  };
1902
2401
 
1903
2402
  // src/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteEstimate.ts
1904
- var BridgeFromRemoteEstimateBodyZod = BridgeIntentFieldsZod.pick({
1905
- destAddress: true,
1906
- srcAddress: true,
1907
- srcAmount: true
1908
- });
1909
- var BridgeFromRemoteEstimateResponseZod = z.tuple([
1910
- TransactionBoundWitnessZod,
1911
- PayloadZodStrictOfSchema(BridgeIntentSchema4).extend(BridgeIntentFieldsZod.shape),
1912
- TransferZod
1913
- ]);
1914
2403
  var makeBridgeFromRemoteEstimateRoute = (config) => {
1915
- const params = z.object({ chainId: getRemoteChainIdZod(config) });
2404
+ const params = z2.object({ chainId: getRemoteChainIdZod(config) });
1916
2405
  const validateRequest2 = requestHandlerValidator2({
1917
2406
  params,
1918
2407
  body: BridgeFromRemoteEstimateBodyZod,
@@ -1934,12 +2423,12 @@ var makeBridgeFromRemoteEstimateRoute = (config) => {
1934
2423
  destAddress
1935
2424
  } = req.body;
1936
2425
  const { maxBridgeAmount, minBridgeAmount } = await getBridgeSettings(config);
1937
- if (hexToBigInt12(srcAmount) < hexToBigInt12(minBridgeAmount) || hexToBigInt12(srcAmount) > hexToBigInt12(maxBridgeAmount)) {
2426
+ if (hexToBigInt11(srcAmount) < hexToBigInt11(minBridgeAmount) || hexToBigInt11(srcAmount) > hexToBigInt11(maxBridgeAmount)) {
1938
2427
  res.status(400).send();
1939
2428
  return;
1940
2429
  }
1941
- const fees = calculateBridgeFees(srcAmount, getFeeStructure(config));
1942
- const feesAmount = hexToBigInt12(fees.feeFixed) + hexToBigInt12(fees.feeVariable);
2430
+ const fees = calculateBridgeFees3(srcAmount, getFeeStructure(config));
2431
+ const feesAmount = hexToBigInt11(fees.feeFixed) + hexToBigInt11(fees.feeVariable);
1943
2432
  const { feesAddress } = await getTransferAddresses(config);
1944
2433
  const currentNextId = await bridge.nextBridgeToId({ blockTag: "latest" });
1945
2434
  const projectedBridgeId = currentNextId + 1n;
@@ -1948,18 +2437,18 @@ var makeBridgeFromRemoteEstimateRoute = (config) => {
1948
2437
  const evmChainId = config.remoteChainId;
1949
2438
  const evmContractAddress = config.remoteBridgeContractAddress;
1950
2439
  const evmTokenAddress = getRemoteTokenAddress(config);
1951
- const viewer = assertEx23(gateway.connection.viewer, () => new Error("Viewer not available on gateway connection"));
2440
+ const viewer = assertEx24(gateway.connection.viewer, () => new Error("Viewer not available on gateway connection"));
1952
2441
  const currentBlockNumber = await viewer.currentBlockNumber();
1953
2442
  const nbf = toXL1BlockNumber(currentBlockNumber, true);
1954
2443
  const exp = toXL1BlockNumber(currentBlockNumber + 1e3, true);
1955
- const { intent, transfer } = buildEthToXl1BridgePayloads({
1956
- amount: hexToBigInt12(srcAmount),
2444
+ const { intent, transfer } = buildEthToXl1BridgePayloads3({
2445
+ amount: hexToBigInt11(srcAmount),
1957
2446
  bridgeAccountAddress: account.address,
1958
2447
  bridgeId: projectedBridgeId,
1959
- destAddress: toAddress3(destAddress),
2448
+ destAddress: toAddress2(destAddress),
1960
2449
  evmChainId,
1961
2450
  evmContractAddress,
1962
- evmSrcAddress: toAddress3(srcAddress),
2451
+ evmSrcAddress: toAddress2(srcAddress),
1963
2452
  evmTokenAddress,
1964
2453
  feesAddress,
1965
2454
  feesAmount,
@@ -1972,71 +2461,104 @@ var makeBridgeFromRemoteEstimateRoute = (config) => {
1972
2461
  };
1973
2462
  };
1974
2463
 
1975
- // src/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteStatus.ts
2464
+ // src/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteRetry.ts
1976
2465
  import { requestHandlerValidator as requestHandlerValidator3 } from "@xylabs/express";
1977
- import {
1978
- hexToBigInt as hexToBigInt13,
1979
- isDefined as isDefined14,
1980
- toHex as toHex7
1981
- } from "@xylabs/sdk-js";
1982
- import { PayloadZodStrictOfSchema as PayloadZodStrictOfSchema2 } from "@xyo-network/sdk-js";
1983
- import {
1984
- BridgeDestinationObservationFieldsZod,
1985
- BridgeDestinationObservationSchema,
1986
- BridgeIntentFieldsZod as BridgeIntentFieldsZod2,
1987
- BridgeIntentSchema as BridgeIntentSchema5,
1988
- BridgeSourceObservationFieldsZod,
1989
- BridgeSourceObservationSchema as BridgeSourceObservationSchema2
1990
- } from "@xyo-network/xl1-sdk";
1991
- import { z as z2 } from "zod";
1992
- var BridgeIntentResponseZod = PayloadZodStrictOfSchema2(BridgeIntentSchema5).extend(BridgeIntentFieldsZod2.shape);
1993
- var BridgeSourceResponseZod = PayloadZodStrictOfSchema2(BridgeSourceObservationSchema2).extend(BridgeSourceObservationFieldsZod.shape);
1994
- var BridgeDestinationResponseZod = PayloadZodStrictOfSchema2(BridgeDestinationObservationSchema).extend(BridgeDestinationObservationFieldsZod.shape);
1995
- var BridgeFromRemoteStatusResponseZod = z2.union([
1996
- z2.tuple([]),
1997
- z2.tuple([BridgeIntentResponseZod]),
1998
- z2.tuple([BridgeIntentResponseZod, BridgeSourceResponseZod]),
1999
- z2.tuple([BridgeIntentResponseZod, BridgeSourceResponseZod, BridgeDestinationResponseZod])
2000
- ]);
2001
- function buildIntentAndSource(ctx) {
2002
- const {
2003
- canonical,
2004
- config,
2005
- nonce
2006
- } = ctx;
2007
- const srcAmountHex = toHex7(canonical.amount);
2008
- const fees = calculateBridgeFees(srcAmountHex, getFeeStructure(config));
2009
- const feesAmount = hexToBigInt13(fees.feeFixed) + hexToBigInt13(fees.feeVariable);
2010
- const destAmountHex = toHex7(canonical.amount - feesAmount);
2011
- const xl1ChainId = getXl1ChainId(config);
2012
- const xl1TokenAddress = getXl1TokenAddress(config);
2013
- const evmChainId = config.remoteChainId;
2014
- const evmTokenAddress = getRemoteTokenAddress(config);
2015
- const intentFields = BridgeIntentFieldsZod2.parse({
2016
- dest: xl1ChainId,
2017
- destAddress: canonical.destAddress,
2018
- destAmount: destAmountHex,
2019
- destToken: xl1TokenAddress,
2020
- nonce,
2021
- src: evmChainId,
2022
- srcAddress: canonical.srcAddress,
2023
- srcAmount: srcAmountHex,
2024
- srcToken: evmTokenAddress
2466
+ import { BridgeRetryBodyZod, BridgeRetryResponseZod } from "@xyo-network/chain-bridge-shared";
2467
+ import { z as z3 } from "zod/mini";
2468
+ var makeBridgeFromRemoteRetryRoute = (config) => {
2469
+ const params = z3.object({
2470
+ chainId: getRemoteChainIdZod(config),
2471
+ nonce: z3.string().check(z3.regex(/^\d+$/, { error: "nonce must be a non-negative integer (the bridge contract's id)" }))
2025
2472
  });
2026
- const intent = { schema: BridgeIntentSchema5, ...intentFields };
2027
- const sourceFields = BridgeSourceObservationFieldsZod.parse({
2028
- ...intentFields,
2029
- srcConfirmation: canonical.evmTxHash
2473
+ const validateRequest2 = requestHandlerValidator3({
2474
+ params,
2475
+ body: BridgeRetryBodyZod,
2476
+ response: BridgeRetryResponseZod
2030
2477
  });
2031
- const source = { schema: BridgeSourceObservationSchema2, ...sourceFields };
2032
- return [intent, source];
2033
- }
2478
+ return {
2479
+ method: "post",
2480
+ path: "/bridge/chains/:chainId/bridgeFromRemote/retry/:nonce",
2481
+ handlers: validateRequest2(async (req, res) => {
2482
+ const bridgeId = BigInt(req.params.nonce);
2483
+ const identity = getJobIdForEthToXl1BridgeJob({
2484
+ bridgeId,
2485
+ contractAddress: config.remoteBridgeContractAddress,
2486
+ evmChainId: config.remoteChainId
2487
+ });
2488
+ const queues = getEthToXl1Queues(config);
2489
+ const result = await retrySingleFailedJob({
2490
+ dryRun: req.body.dryRun,
2491
+ jobId: identity,
2492
+ queue: queues.xl1ReserveTxFulfillment
2493
+ });
2494
+ if (result.status === "retried" && !req.body.dryRun) {
2495
+ req.app.services.bridgeFlowMetrics.recordRetried({ direction: "inbound", trigger: "api_singular" });
2496
+ }
2497
+ const httpStatus = result.status === "not-found" ? 404 : 200;
2498
+ res.status(httpStatus).json(result);
2499
+ })
2500
+ };
2501
+ };
2502
+
2503
+ // src/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteRetryFailed.ts
2504
+ import { requestHandlerValidator as requestHandlerValidator4 } from "@xylabs/express";
2505
+ import { BridgeRetryFailedBodyZod, BridgeRetryFailedResponseZod } from "@xyo-network/chain-bridge-shared";
2506
+ import { StatusCodes } from "http-status-codes";
2507
+ import { z as z4 } from "zod/mini";
2508
+ var makeBridgeFromRemoteRetryFailedRoute = (config) => {
2509
+ const params = z4.object({ chainId: getRemoteChainIdZod(config) });
2510
+ const validateRequest2 = requestHandlerValidator4({
2511
+ params,
2512
+ body: BridgeRetryFailedBodyZod,
2513
+ response: BridgeRetryFailedResponseZod
2514
+ });
2515
+ return {
2516
+ method: "post",
2517
+ path: "/bridge/chains/:chainId/bridgeFromRemote/retryFailed",
2518
+ handlers: validateRequest2(async (req, res) => {
2519
+ const filters = req.body;
2520
+ if (!hasAnyFilter(filters)) {
2521
+ const err = new Error(
2522
+ `At least one filter is required to prevent accidental retry-everything. Available filters: ${AVAILABLE_BULK_FILTERS.join(", ")}.`
2523
+ );
2524
+ err.name = "Bad Request";
2525
+ err.statusCode = StatusCodes.BAD_REQUEST;
2526
+ throw err;
2527
+ }
2528
+ const services = req.app.services;
2529
+ const queues = getEthToXl1Queues(config);
2530
+ const srcAddressLookup = async (jobId) => {
2531
+ const state = await services.bridgeFulfillmentMap.get(jobId);
2532
+ return state?.canonical?.srcAddress;
2533
+ };
2534
+ const result = await retryFailedJobs({
2535
+ filters,
2536
+ queue: queues.xl1ReserveTxFulfillment,
2537
+ srcAddressLookup
2538
+ });
2539
+ if (!filters.dryRun && result.counts.retried > 0) {
2540
+ services.bridgeFlowMetrics.recordRetried({
2541
+ count: result.counts.retried,
2542
+ direction: "inbound",
2543
+ trigger: "api_bulk"
2544
+ });
2545
+ }
2546
+ res.json(result);
2547
+ })
2548
+ };
2549
+ };
2550
+
2551
+ // src/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteStatus.ts
2552
+ import { requestHandlerValidator as requestHandlerValidator5 } from "@xylabs/express";
2553
+ import { assertEx as assertEx25, isDefined as isDefined15 } from "@xylabs/sdk-js";
2554
+ import { BridgeFromRemoteStatusResponseZod } from "@xyo-network/chain-bridge-shared";
2555
+ import { z as z5 } from "zod/mini";
2034
2556
  var makeBridgeFromRemoteStatusRoute = (config) => {
2035
- const params = z2.object({
2557
+ const params = z5.object({
2036
2558
  chainId: getRemoteChainIdZod(config),
2037
- nonce: z2.string().regex(/^\d+$/, "nonce must be a non-negative integer (the bridge contract's id)")
2559
+ nonce: z5.string().check(z5.regex(/^\d+$/, { error: "nonce must be a non-negative integer (the bridge contract's id)" }))
2038
2560
  });
2039
- const validateRequest2 = requestHandlerValidator3({ params, response: BridgeFromRemoteStatusResponseZod });
2561
+ const validateRequest2 = requestHandlerValidator5({ params, response: BridgeFromRemoteStatusResponseZod });
2040
2562
  return {
2041
2563
  method: "get",
2042
2564
  path: "/bridge/chains/:chainId/bridgeFromRemote/status/:nonce",
@@ -2051,55 +2573,77 @@ var makeBridgeFromRemoteStatusRoute = (config) => {
2051
2573
  });
2052
2574
  const services = req.app.services;
2053
2575
  const state = await services.bridgeFulfillmentMap.get(identity);
2054
- if (state?.failed) return res.sendStatus(504);
2055
2576
  if (!state?.canonical) {
2056
2577
  const queues = getEthToXl1Queues(config);
2057
2578
  const parentJob = await queues.ethToXl1BridgeParent.getJob(identity);
2058
- if (!isDefined14(parentJob)) return res.sendStatus(404);
2579
+ if (!isDefined15(parentJob)) return res.sendStatus(404);
2059
2580
  return res.json([]);
2060
2581
  }
2061
- const [intent, source] = buildIntentAndSource({
2062
- canonical: state.canonical,
2063
- config,
2064
- nonce: identity
2065
- });
2582
+ const intent = assertEx25(state.acceptedIntent, () => `[${identity}] acceptedIntent missing despite canonical \u2014 stale state`);
2583
+ const source = assertEx25(state.acceptedSourceObservation, () => `[${identity}] acceptedSourceObservation missing despite canonical \u2014 stale state`);
2066
2584
  if (!state.fulfilled) return res.json([intent, source]);
2067
- const destinationFields = BridgeDestinationObservationFieldsZod.parse({
2068
- ...intent,
2069
- destConfirmation: state.fulfilled.txHash
2070
- });
2071
- const destination = { schema: BridgeDestinationObservationSchema, ...destinationFields };
2585
+ const destination = assertEx25(state.acceptedDestinationObservation, () => `[${identity}] acceptedDestinationObservation missing despite fulfilled \u2014 stale state`);
2072
2586
  res.json([intent, source, destination]);
2073
2587
  })
2074
2588
  };
2075
2589
  };
2076
2590
 
2591
+ // src/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteStatusByTx.ts
2592
+ import { requestHandlerValidator as requestHandlerValidator6 } from "@xylabs/express";
2593
+ import { assertEx as assertEx26 } from "@xylabs/sdk-js";
2594
+ import { BridgeFromRemoteStatusResponseZod as BridgeFromRemoteStatusResponseZod2 } from "@xyo-network/chain-bridge-shared";
2595
+ import { z as z6 } from "zod/mini";
2596
+ async function lookupBridgeFromRemoteStatusByTx(map, evmTxHash) {
2597
+ const needle = evmTxHash.toLowerCase();
2598
+ for await (const [, state] of map) {
2599
+ const canonicalHash = state.canonical?.evmTxHash;
2600
+ if (canonicalHash === void 0) continue;
2601
+ if (canonicalHash.toLowerCase() !== needle) continue;
2602
+ const intent = assertEx26(
2603
+ state.acceptedIntent,
2604
+ () => `[${state.identity}] acceptedIntent missing despite canonical \u2014 stale state`
2605
+ );
2606
+ const source = assertEx26(
2607
+ state.acceptedSourceObservation,
2608
+ () => `[${state.identity}] acceptedSourceObservation missing despite canonical \u2014 stale state`
2609
+ );
2610
+ if (!state.fulfilled) return [intent, source];
2611
+ const destination = assertEx26(
2612
+ state.acceptedDestinationObservation,
2613
+ () => `[${state.identity}] acceptedDestinationObservation missing despite fulfilled \u2014 stale state`
2614
+ );
2615
+ return [intent, source, destination];
2616
+ }
2617
+ return null;
2618
+ }
2619
+ var makeBridgeFromRemoteStatusByTxRoute = (config) => {
2620
+ const params = z6.object({
2621
+ chainId: getRemoteChainIdZod(config),
2622
+ evmTxHash: z6.string().check(z6.regex(/^0x[0-9a-fA-F]{64}$/, { error: "evmTxHash must be a 0x-prefixed 32-byte hex string" }))
2623
+ });
2624
+ const validateRequest2 = requestHandlerValidator6({ params, response: BridgeFromRemoteStatusResponseZod2 });
2625
+ return {
2626
+ method: "get",
2627
+ path: "/bridge/chains/:chainId/bridgeFromRemote/status/byTx/:evmTxHash",
2628
+ handlers: validateRequest2(async (req, res) => {
2629
+ const services = req.app.services;
2630
+ const result = await lookupBridgeFromRemoteStatusByTx(services.bridgeFulfillmentMap, req.params.evmTxHash);
2631
+ if (result === null) return res.sendStatus(404);
2632
+ res.json(result);
2633
+ })
2634
+ };
2635
+ };
2636
+
2077
2637
  // src/server/routes/bridge/routeDefinitions/routes/bridgeToRemote.ts
2078
- import { requestHandlerValidator as requestHandlerValidator4 } from "@xylabs/express";
2079
- import { isDefined as isDefined15 } from "@xylabs/sdk-js";
2080
- import {
2081
- PayloadBuilder as PayloadBuilder17,
2082
- PayloadZodLooseOfSchema,
2083
- PayloadZodStrictOfSchema as PayloadZodStrictOfSchema3
2084
- } from "@xyo-network/sdk-js";
2085
- import {
2086
- BridgeIntentFieldsZod as BridgeIntentFieldsZod3,
2087
- BridgeIntentSchema as BridgeIntentSchema6,
2088
- BridgeSourceObservationFieldsZod as BridgeSourceObservationFieldsZod2,
2089
- BridgeSourceObservationSchema as BridgeSourceObservationSchema3,
2090
- SignedTransactionBoundWitnessZod,
2091
- TransferZod as TransferZod2
2092
- } from "@xyo-network/xl1-sdk";
2093
- import { z as z3 } from "zod";
2094
- var BridgeToRemoteBodyZod = z3.tuple([
2095
- SignedTransactionBoundWitnessZod,
2096
- PayloadZodLooseOfSchema(BridgeIntentSchema6).extend(BridgeIntentFieldsZod3.shape),
2097
- TransferZod2
2098
- ]);
2099
- var BridgeToRemoteResponseZod = PayloadZodStrictOfSchema3(BridgeSourceObservationSchema3).extend(BridgeSourceObservationFieldsZod2.shape);
2638
+ import { requestHandlerValidator as requestHandlerValidator7 } from "@xylabs/express";
2639
+ import { isDefined as isDefined16 } from "@xylabs/sdk-js";
2640
+ import { BridgeToRemoteBodyZod, BridgeToRemoteResponseZod } from "@xyo-network/chain-bridge-shared";
2641
+ import { PayloadBuilder as PayloadBuilder16 } from "@xyo-network/sdk-js";
2642
+ import { BridgeSourceObservationFieldsZod as BridgeSourceObservationFieldsZod2, BridgeSourceObservationSchema as BridgeSourceObservationSchema3 } from "@xyo-network/xl1-sdk";
2643
+ import { z as z7 } from "zod/mini";
2100
2644
  var makeBridgeToRemoteRoute = (config) => {
2101
- const params = z3.object({ chainId: getRemoteChainIdZod(config) });
2102
- const validateRequest2 = requestHandlerValidator4({
2645
+ const params = z7.object({ chainId: getRemoteChainIdZod(config) });
2646
+ const validateRequest2 = requestHandlerValidator7({
2103
2647
  params,
2104
2648
  body: BridgeToRemoteBodyZod,
2105
2649
  response: BridgeToRemoteResponseZod
@@ -2128,16 +2672,16 @@ var makeBridgeToRemoteRoute = (config) => {
2128
2672
  }
2129
2673
  const singedHydratedTransaction = [signedTxBw, [transfer]];
2130
2674
  const existingFlow = await getXl1ToEthBridgeJob(flowProducer2, singedHydratedTransaction);
2131
- if (isDefined15(existingFlow)) {
2675
+ if (isDefined16(existingFlow)) {
2132
2676
  res.status(200).send();
2133
2677
  return;
2134
2678
  }
2135
2679
  await createXl1ToEthBridgeJob(flowProducer2, singedHydratedTransaction, [bridgeIntent]);
2136
- const srcConfirmation = await PayloadBuilder17.hash(signedTxBw);
2137
- const bridgeCommonFieldsZod = z3.object({}).extend(BridgeSourceObservationFieldsZod2.shape);
2680
+ const srcConfirmation = await PayloadBuilder16.hash(signedTxBw);
2681
+ const bridgeCommonFieldsZod = z7.object(BridgeSourceObservationFieldsZod2.shape);
2138
2682
  const bridgeCommonFields = bridgeCommonFieldsZod.parse(bridgeIntent);
2139
2683
  const bridgeObservationFields = { ...bridgeCommonFields, srcConfirmation };
2140
- const bridgeObservation = new PayloadBuilder17(
2684
+ const bridgeObservation = new PayloadBuilder16(
2141
2685
  { schema: BridgeSourceObservationSchema3 }
2142
2686
  ).fields(bridgeObservationFields).build();
2143
2687
  res.status(202).json(bridgeObservation);
@@ -2146,35 +2690,18 @@ var makeBridgeToRemoteRoute = (config) => {
2146
2690
  };
2147
2691
 
2148
2692
  // src/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteEstimate.ts
2149
- import { requestHandlerValidator as requestHandlerValidator5 } from "@xylabs/express";
2693
+ import { requestHandlerValidator as requestHandlerValidator8 } from "@xylabs/express";
2150
2694
  import {
2151
- assertEx as assertEx24,
2152
- hexToBigInt as hexToBigInt14,
2153
- toAddress as toAddress4
2695
+ assertEx as assertEx27,
2696
+ hexToBigInt as hexToBigInt12,
2697
+ toAddress as toAddress3
2154
2698
  } from "@xylabs/sdk-js";
2155
- import { PayloadZodStrictOfSchema as PayloadZodStrictOfSchema4 } from "@xyo-network/sdk-js";
2156
- import {
2157
- BridgeIntentFieldsZod as BridgeIntentFieldsZod4,
2158
- BridgeIntentSchema as BridgeIntentSchema7,
2159
- buildUnsignedTransaction as buildUnsignedTransaction2,
2160
- toXL1BlockNumber as toXL1BlockNumber2,
2161
- TransactionBoundWitnessZod as TransactionBoundWitnessZod2,
2162
- TransferZod as TransferZod3
2163
- } from "@xyo-network/xl1-sdk";
2164
- import { z as z4 } from "zod";
2165
- var BridgeToRemoteEstimateBodyZod = BridgeIntentFieldsZod4.pick({
2166
- destAddress: true,
2167
- srcAddress: true,
2168
- srcAmount: true
2169
- });
2170
- var BridgeToRemoteEstimateResponseZod = z4.tuple([
2171
- TransactionBoundWitnessZod2,
2172
- PayloadZodStrictOfSchema4(BridgeIntentSchema7).extend(BridgeIntentFieldsZod4.shape),
2173
- TransferZod3
2174
- ]);
2699
+ import { BridgeToRemoteEstimateBodyZod, BridgeToRemoteEstimateResponseZod } from "@xyo-network/chain-bridge-shared";
2700
+ import { buildUnsignedTransaction as buildUnsignedTransaction2, toXL1BlockNumber as toXL1BlockNumber2 } from "@xyo-network/xl1-sdk";
2701
+ import { z as z8 } from "zod/mini";
2175
2702
  var makeBridgeToRemoteEstimateRoute = (config, gateway) => {
2176
- const params = z4.object({ chainId: getRemoteChainIdZod(config) });
2177
- const validateRequest2 = requestHandlerValidator5({
2703
+ const params = z8.object({ chainId: getRemoteChainIdZod(config) });
2704
+ const validateRequest2 = requestHandlerValidator8({
2178
2705
  params,
2179
2706
  body: BridgeToRemoteEstimateBodyZod,
2180
2707
  response: BridgeToRemoteEstimateResponseZod
@@ -2190,13 +2717,13 @@ var makeBridgeToRemoteEstimateRoute = (config, gateway) => {
2190
2717
  destAddress
2191
2718
  } = req.body;
2192
2719
  const { maxBridgeAmount, minBridgeAmount } = await getBridgeSettings(config);
2193
- if (hexToBigInt14(srcAmount) < hexToBigInt14(minBridgeAmount) || hexToBigInt14(srcAmount) > hexToBigInt14(maxBridgeAmount)) {
2720
+ if (hexToBigInt12(srcAmount) < hexToBigInt12(minBridgeAmount) || hexToBigInt12(srcAmount) > hexToBigInt12(maxBridgeAmount)) {
2194
2721
  res.status(400).send();
2195
2722
  return;
2196
2723
  }
2197
2724
  const [bridgeIntent, transfer] = await generateBridgeEstimate(srcAddress, srcAmount, destAddress, config);
2198
- const sender = toAddress4(srcAddress);
2199
- const viewer = assertEx24(gateway.connection.viewer, () => new Error("Viewer not available on gateway connection"));
2725
+ const sender = toAddress3(srcAddress);
2726
+ const viewer = assertEx27(gateway.connection.viewer, () => new Error("Viewer not available on gateway connection"));
2200
2727
  const currentBlockNumber = await viewer.currentBlockNumber();
2201
2728
  const nbf = toXL1BlockNumber2(currentBlockNumber, true);
2202
2729
  const exp = toXL1BlockNumber2(currentBlockNumber + 1e3, true);
@@ -2207,36 +2734,23 @@ var makeBridgeToRemoteEstimateRoute = (config, gateway) => {
2207
2734
  };
2208
2735
 
2209
2736
  // src/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteMaxEstimate.ts
2210
- import { requestHandlerValidator as requestHandlerValidator6 } from "@xylabs/express";
2737
+ import { requestHandlerValidator as requestHandlerValidator9 } from "@xylabs/express";
2211
2738
  import {
2212
- assertEx as assertEx25,
2213
- hexToBigInt as hexToBigInt15,
2214
- toAddress as toAddress5,
2215
- toHex as toHex8
2739
+ assertEx as assertEx28,
2740
+ hexToBigInt as hexToBigInt13,
2741
+ toAddress as toAddress4,
2742
+ toHex as toHex4
2216
2743
  } from "@xylabs/sdk-js";
2217
- import { PayloadZodStrictOfSchema as PayloadZodStrictOfSchema5 } from "@xyo-network/sdk-js";
2218
2744
  import {
2219
- BridgeIntentFieldsZod as BridgeIntentFieldsZod5,
2220
- BridgeIntentSchema as BridgeIntentSchema8,
2221
- buildUnsignedTransaction as buildUnsignedTransaction3,
2222
- toXL1BlockNumber as toXL1BlockNumber3,
2223
- TransactionBoundWitnessZod as TransactionBoundWitnessZod3,
2224
- TransferZod as TransferZod4
2225
- } from "@xyo-network/xl1-sdk";
2226
- import { z as z5 } from "zod";
2227
- var BridgeToRemoteMaxEstimateBodyZod = BridgeIntentFieldsZod5.pick({
2228
- destAddress: true,
2229
- srcAddress: true,
2230
- srcAmount: true
2231
- });
2232
- var BridgeToRemoteMaxEstimateResponseZod = z5.tuple([
2233
- TransactionBoundWitnessZod3,
2234
- PayloadZodStrictOfSchema5(BridgeIntentSchema8).extend(BridgeIntentFieldsZod5.shape),
2235
- TransferZod4
2236
- ]);
2745
+ BridgeToRemoteMaxEstimateBodyZod,
2746
+ BridgeToRemoteMaxEstimateResponseZod,
2747
+ calculateMaxBridgeAmount
2748
+ } from "@xyo-network/chain-bridge-shared";
2749
+ import { buildUnsignedTransaction as buildUnsignedTransaction3, toXL1BlockNumber as toXL1BlockNumber3 } from "@xyo-network/xl1-sdk";
2750
+ import { z as z9 } from "zod/mini";
2237
2751
  var makeBridgeToRemoteMaxEstimateRoute = (config, gateway) => {
2238
- const params = z5.object({ chainId: getRemoteChainIdZod(config) });
2239
- const validateRequest2 = requestHandlerValidator6({
2752
+ const params = z9.object({ chainId: getRemoteChainIdZod(config) });
2753
+ const validateRequest2 = requestHandlerValidator9({
2240
2754
  params,
2241
2755
  body: BridgeToRemoteMaxEstimateBodyZod,
2242
2756
  response: BridgeToRemoteMaxEstimateResponseZod
@@ -2257,10 +2771,10 @@ var makeBridgeToRemoteMaxEstimateRoute = (config, gateway) => {
2257
2771
  destAddress
2258
2772
  } = req.body;
2259
2773
  const balanceMax = calculateMaxBridgeAmount(balance, { feeFixed, feeRateBasisPoints });
2260
- const maxBridgeAmount = toHex8(hexToBigInt15(balanceMax) < hexToBigInt15(configMax) ? balanceMax : configMax);
2774
+ const maxBridgeAmount = toHex4(hexToBigInt13(balanceMax) < hexToBigInt13(configMax) ? balanceMax : configMax);
2261
2775
  const [bridgeIntent, transfer] = await generateBridgeEstimate(srcAddress, maxBridgeAmount, destAddress, config);
2262
- const sender = toAddress5(srcAddress);
2263
- const viewer = assertEx25(gateway.connection.viewer, () => new Error("Viewer not available on gateway connection"));
2776
+ const sender = toAddress4(srcAddress);
2777
+ const viewer = assertEx28(gateway.connection.viewer, () => new Error("Viewer not available on gateway connection"));
2264
2778
  const currentBlockNumber = await viewer.currentBlockNumber();
2265
2779
  const nbf = toXL1BlockNumber3(currentBlockNumber, true);
2266
2780
  const exp = toXL1BlockNumber3(currentBlockNumber + 1e3, true);
@@ -2270,36 +2784,110 @@ var makeBridgeToRemoteMaxEstimateRoute = (config, gateway) => {
2270
2784
  };
2271
2785
  };
2272
2786
 
2787
+ // src/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteRetry.ts
2788
+ import { requestHandlerValidator as requestHandlerValidator10 } from "@xylabs/express";
2789
+ import { BridgeRetryBodyZod as BridgeRetryBodyZod2, BridgeRetryResponseZod as BridgeRetryResponseZod2 } from "@xyo-network/chain-bridge-shared";
2790
+ import { z as z10 } from "zod/mini";
2791
+ var makeBridgeToRemoteRetryRoute = (config) => {
2792
+ const params = z10.object({
2793
+ chainId: getRemoteChainIdZod(config),
2794
+ nonce: z10.string().check(z10.minLength(1))
2795
+ });
2796
+ const validateRequest2 = requestHandlerValidator10({
2797
+ params,
2798
+ body: BridgeRetryBodyZod2,
2799
+ response: BridgeRetryResponseZod2
2800
+ });
2801
+ return {
2802
+ method: "post",
2803
+ path: "/bridge/chains/:chainId/bridgeToRemote/retry/:nonce",
2804
+ handlers: validateRequest2(async (req, res) => {
2805
+ const jobId = req.params.nonce;
2806
+ const queues = getXl1ToEthQueues(config);
2807
+ const result = await retrySingleFailedJob({
2808
+ dryRun: req.body.dryRun,
2809
+ jobId,
2810
+ queue: queues.ethTransactionMonitor
2811
+ });
2812
+ if (result.status === "retried" && !req.body.dryRun) {
2813
+ req.app.services.bridgeFlowMetrics.recordRetried({ direction: "outbound", trigger: "api_singular" });
2814
+ }
2815
+ const httpStatus = result.status === "not-found" ? 404 : 200;
2816
+ res.status(httpStatus).json(result);
2817
+ })
2818
+ };
2819
+ };
2820
+
2821
+ // src/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteRetryFailed.ts
2822
+ import { requestHandlerValidator as requestHandlerValidator11 } from "@xylabs/express";
2823
+ import { BridgeRetryFailedBodyZod as BridgeRetryFailedBodyZod2, BridgeRetryFailedResponseZod as BridgeRetryFailedResponseZod2 } from "@xyo-network/chain-bridge-shared";
2824
+ import { isBridgeIntent as isBridgeIntent6 } from "@xyo-network/xl1-sdk";
2825
+ import { StatusCodes as StatusCodes2 } from "http-status-codes";
2826
+ import { z as z11 } from "zod/mini";
2827
+ var makeBridgeToRemoteRetryFailedRoute = (config) => {
2828
+ const params = z11.object({ chainId: getRemoteChainIdZod(config) });
2829
+ const validateRequest2 = requestHandlerValidator11({
2830
+ params,
2831
+ body: BridgeRetryFailedBodyZod2,
2832
+ response: BridgeRetryFailedResponseZod2
2833
+ });
2834
+ return {
2835
+ method: "post",
2836
+ path: "/bridge/chains/:chainId/bridgeToRemote/retryFailed",
2837
+ handlers: validateRequest2(async (req, res) => {
2838
+ const filters = req.body;
2839
+ if (!hasAnyFilter(filters)) {
2840
+ const err = new Error(
2841
+ `At least one filter is required to prevent accidental retry-everything. Available filters: ${AVAILABLE_BULK_FILTERS.join(", ")}.`
2842
+ );
2843
+ err.name = "Bad Request";
2844
+ err.statusCode = StatusCodes2.BAD_REQUEST;
2845
+ throw err;
2846
+ }
2847
+ const queues = getXl1ToEthQueues(config);
2848
+ const srcAddressLookup = async (jobId) => {
2849
+ const job = await queues.ethTransactionMonitor.getJob(jobId);
2850
+ const data = job?.data;
2851
+ const bridgeIntent = data?.tx[1].find(isBridgeIntent6);
2852
+ return bridgeIntent?.srcAddress;
2853
+ };
2854
+ const result = await retryFailedJobs({
2855
+ filters,
2856
+ queue: queues.ethTransactionMonitor,
2857
+ srcAddressLookup
2858
+ });
2859
+ if (!filters.dryRun && result.counts.retried > 0) {
2860
+ req.app.services.bridgeFlowMetrics.recordRetried({
2861
+ count: result.counts.retried,
2862
+ direction: "outbound",
2863
+ trigger: "api_bulk"
2864
+ });
2865
+ }
2866
+ res.json(result);
2867
+ })
2868
+ };
2869
+ };
2870
+
2273
2871
  // src/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteStatus.ts
2274
- import { requestHandlerValidator as requestHandlerValidator7 } from "@xylabs/express";
2275
- import { asHex as asHex4, isDefined as isDefined16 } from "@xylabs/sdk-js";
2276
- import { PayloadBuilder as PayloadBuilder18, PayloadZodStrictOfSchema as PayloadZodStrictOfSchema6 } from "@xyo-network/sdk-js";
2872
+ import { requestHandlerValidator as requestHandlerValidator12 } from "@xylabs/express";
2873
+ import { asHex as asHex4, isDefined as isDefined17 } from "@xylabs/sdk-js";
2874
+ import { BridgeToRemoteStatusResponseZod } from "@xyo-network/chain-bridge-shared";
2875
+ import { PayloadBuilder as PayloadBuilder17 } from "@xyo-network/sdk-js";
2277
2876
  import {
2278
2877
  asBridgeIntent,
2279
2878
  BridgeDestinationObservationFieldsZod as BridgeDestinationObservationFieldsZod2,
2280
2879
  BridgeDestinationObservationSchema as BridgeDestinationObservationSchema2,
2281
- BridgeIntentFieldsZod as BridgeIntentFieldsZod6,
2282
- BridgeIntentSchema as BridgeIntentSchema9,
2283
2880
  BridgeSourceObservationFieldsZod as BridgeSourceObservationFieldsZod3,
2284
2881
  BridgeSourceObservationSchema as BridgeSourceObservationSchema4,
2285
- isBridgeIntent as isBridgeIntent5
2882
+ isBridgeIntent as isBridgeIntent7
2286
2883
  } from "@xyo-network/xl1-sdk";
2287
- import { z as z6 } from "zod";
2288
- var BridgeIntentResponseZod2 = PayloadZodStrictOfSchema6(BridgeIntentSchema9).extend(BridgeIntentFieldsZod6.shape);
2289
- var BridgeSourceResponseZod2 = PayloadZodStrictOfSchema6(BridgeSourceObservationSchema4).extend(BridgeSourceObservationFieldsZod3.shape);
2290
- var BridgeDestinationResponseZod2 = PayloadZodStrictOfSchema6(BridgeDestinationObservationSchema2).extend(BridgeDestinationObservationFieldsZod2.shape);
2291
- var BridgeToRemoteStatusResponseZod = z6.union([
2292
- z6.tuple([]),
2293
- z6.tuple([BridgeIntentResponseZod2]),
2294
- z6.tuple([BridgeIntentResponseZod2, BridgeSourceResponseZod2]),
2295
- z6.tuple([BridgeIntentResponseZod2, BridgeSourceResponseZod2, BridgeDestinationResponseZod2])
2296
- ]);
2884
+ import { z as z12 } from "zod/mini";
2297
2885
  var makeBridgeToRemoteStatusRoute = (config) => {
2298
- const params = z6.object({
2886
+ const params = z12.object({
2299
2887
  chainId: getRemoteChainIdZod(config),
2300
- nonce: z6.string().nonempty()
2888
+ nonce: z12.string().check(z12.minLength(1))
2301
2889
  });
2302
- const validateRequest2 = requestHandlerValidator7({ params, response: BridgeToRemoteStatusResponseZod });
2890
+ const validateRequest2 = requestHandlerValidator12({ params, response: BridgeToRemoteStatusResponseZod });
2303
2891
  return {
2304
2892
  method: "get",
2305
2893
  path: "/bridge/chains/:chainId/bridgeToRemote/status/:nonce",
@@ -2311,14 +2899,14 @@ var makeBridgeToRemoteStatusRoute = (config) => {
2311
2899
  const { tx, offChainPayloads = [] } = statusQueueJobs.xl1ToEthBridgeParentJob?.data ?? {};
2312
2900
  if (!tx) return res.sendStatus(404);
2313
2901
  const allPayloads = [...tx[1], ...offChainPayloads];
2314
- const bridgeIntent = allPayloads.find(isBridgeIntent5);
2902
+ const bridgeIntent = allPayloads.find(isBridgeIntent7);
2315
2903
  if (!bridgeIntent) return res.sendStatus(404);
2316
- result[0] = asBridgeIntent(PayloadBuilder18.omitMeta(bridgeIntent));
2904
+ result[0] = asBridgeIntent(PayloadBuilder17.omitMeta(bridgeIntent));
2317
2905
  const { xl1TransactionMonitorJob } = statusQueueJobs;
2318
2906
  const xl1MonitorState = xl1TransactionMonitorJob ? await xl1TransactionMonitorJob.getState() : void 0;
2319
2907
  if (xl1MonitorState === "completed") {
2320
2908
  const srcConfirmation = xl1TransactionMonitorJob?.returnvalue?.submissionHash;
2321
- if (isDefined16(srcConfirmation)) {
2909
+ if (isDefined17(srcConfirmation)) {
2322
2910
  const schema = BridgeSourceObservationSchema4;
2323
2911
  const bridgeSourceObservationFields = BridgeSourceObservationFieldsZod3.parse(bridgeIntent);
2324
2912
  const observation = {
@@ -2333,7 +2921,7 @@ var makeBridgeToRemoteStatusRoute = (config) => {
2333
2921
  const ethMonitorState = ethTransactionMonitorJob ? await ethTransactionMonitorJob.getState() : void 0;
2334
2922
  if (ethMonitorState === "completed") {
2335
2923
  const destConfirmation = asHex4(ethTransactionMonitorJob?.returnvalue?.submissionHash);
2336
- if (isDefined16(destConfirmation)) {
2924
+ if (isDefined17(destConfirmation)) {
2337
2925
  const schema = BridgeDestinationObservationSchema2;
2338
2926
  const bridgeDestinationObservationFields = BridgeDestinationObservationFieldsZod2.parse(bridgeIntent);
2339
2927
  const observation = {
@@ -2356,9 +2944,14 @@ var getRouteDefinitions = (config, gateway) => {
2356
2944
  makeBridgeToRemoteEstimateRoute(config, gateway),
2357
2945
  makeBridgeToRemoteMaxEstimateRoute(config, gateway),
2358
2946
  makeBridgeToRemoteRoute(config),
2947
+ makeBridgeToRemoteRetryRoute(config),
2948
+ makeBridgeToRemoteRetryFailedRoute(config),
2359
2949
  makeBridgeToRemoteStatusRoute(config),
2360
2950
  makeBridgeFromRemoteEstimateRoute(config),
2361
- makeBridgeFromRemoteStatusRoute(config)
2951
+ makeBridgeFromRemoteRetryRoute(config),
2952
+ makeBridgeFromRemoteRetryFailedRoute(config),
2953
+ makeBridgeFromRemoteStatusRoute(config),
2954
+ makeBridgeFromRemoteStatusByTxRoute(config)
2362
2955
  ];
2363
2956
  };
2364
2957
 
@@ -2388,10 +2981,10 @@ var getApp = (config, gateway) => {
2388
2981
  };
2389
2982
 
2390
2983
  // src/services/getServices.ts
2391
- import { assertEx as assertEx26 } from "@xylabs/sdk-js";
2984
+ import { assertEx as assertEx29 } from "@xylabs/sdk-js";
2392
2985
  import { initEvmProvider, resolveWalletForActor as resolveWalletForActor2 } from "@xyo-network/chain-orchestration";
2393
2986
  import { BridgeableToken__factory, LiquidityPoolBridge__factory } from "@xyo-network/typechain";
2394
- import { getAddress as getAddress2, Wallet } from "ethers";
2987
+ import { getAddress as getAddress3, Wallet } from "ethers";
2395
2988
 
2396
2989
  // src/services/getIterableMap.ts
2397
2990
  import { BaseMongoSdk } from "@xylabs/mongo";
@@ -2426,7 +3019,7 @@ var getIterableMap = async (config, collection) => {
2426
3019
  };
2427
3020
 
2428
3021
  // src/services/getServices.ts
2429
- var getServices = async (context, gateway) => {
3022
+ var getServices = async (context, gateway, logger, meter) => {
2430
3023
  const { config } = context;
2431
3024
  const ethTxStateMap = await getIterableMap(config, "liquidity_bridge_xl1_to_eth_eth_tx_state");
2432
3025
  const xl1TxStateMap = await getIterableMap(config, "liquidity_bridge_xl1_to_eth_xl1_tx_state");
@@ -2441,20 +3034,32 @@ var getServices = async (context, gateway) => {
2441
3034
  } = config;
2442
3035
  const account = await resolveWalletForActor2(config.name, accountPath);
2443
3036
  const wallet = new Wallet(remoteChainWalletPrivateKey, provider);
2444
- const bridgeableToken = BridgeableToken__factory.connect(getAddress2(remoteTokenAddress), wallet);
2445
- const bridge = LiquidityPoolBridge__factory.connect(getAddress2(remoteBridgeContractAddress), wallet);
3037
+ const bridgeableToken = BridgeableToken__factory.connect(getAddress3(remoteTokenAddress), wallet);
3038
+ const bridge = LiquidityPoolBridge__factory.connect(getAddress3(remoteBridgeContractAddress), wallet);
2446
3039
  const bridgeOwner = await bridge.owner();
2447
- assertEx26(bridgeOwner.toLowerCase() === wallet.address.toLowerCase(), () => "Wallet is not the owner of the bridge contract");
3040
+ assertEx29(bridgeOwner.toLowerCase() === wallet.address.toLowerCase(), () => "Wallet is not the owner of the bridge contract");
2448
3041
  const remoteConfirmationDepth = getRemoteConfirmationDepth(config);
3042
+ const evmBridgeDeployBlock = await resolveDeployBlock({
3043
+ configuredDeployBlock: config.remoteBridgeContractDeployBlock,
3044
+ contractAddress: remoteBridgeContractAddress,
3045
+ provider
3046
+ });
3047
+ const bridgeFlowMetrics = createBridgeFlowMetrics({
3048
+ meter,
3049
+ remoteChainId: config.remoteChainId
3050
+ });
2449
3051
  return {
2450
3052
  account,
2451
3053
  bridge,
3054
+ bridgeFlowMetrics,
2452
3055
  bridgeableToken,
2453
3056
  bridgeFulfillmentMap,
2454
3057
  config,
2455
3058
  ethTxStateMap,
2456
3059
  evmBridgeCursorMap,
3060
+ evmBridgeDeployBlock,
2457
3061
  gateway,
3062
+ logger,
2458
3063
  provider,
2459
3064
  remoteConfirmationDepth,
2460
3065
  wallet,
@@ -2471,16 +3076,16 @@ var addWorkers = (config, services) => {
2471
3076
 
2472
3077
  // src/server/server.ts
2473
3078
  var hostname = "::";
2474
- var getServer = async (context, gateway) => {
2475
- const { logger, config } = context;
3079
+ var getServer = async (context, gateway, logger, meter) => {
3080
+ const { config } = context;
2476
3081
  const { port } = config;
2477
3082
  const app = getApp(config, gateway);
2478
- const services = await getServices(context, gateway);
3083
+ const services = await getServices(context, gateway, logger, meter);
2479
3084
  app.services = services;
2480
3085
  addWorkers(config, services);
2481
3086
  const server = await new Promise((resolve, reject) => {
2482
3087
  const srv = app.listen(port, hostname, () => {
2483
- logger?.log(`[Bridge] Server listening at http://${hostname}:${port}`);
3088
+ logger.log(`[Bridge] Server listening at http://${hostname}:${port}`);
2484
3089
  resolve(srv);
2485
3090
  });
2486
3091
  srv.once("error", reject);
@@ -2489,147 +3094,11 @@ var getServer = async (context, gateway) => {
2489
3094
  return { server, services };
2490
3095
  };
2491
3096
 
2492
- // src/telemetry/createBalanceMonitor.ts
2493
- var DEFAULT_INTERVAL_MS = 6e4;
2494
- var WEI_PER_GWEI = 10n ** 9n;
2495
- var TOKEN_DECIMALS = 10n ** 18n;
2496
- function createBalanceMonitor(config) {
2497
- const {
2498
- account,
2499
- bridge,
2500
- bridgeableToken,
2501
- gateway,
2502
- meter,
2503
- provider,
2504
- wallet,
2505
- intervalMs = DEFAULT_INTERVAL_MS
2506
- } = config;
2507
- let timer;
2508
- const ethWalletGasBalance = meter.createGauge(
2509
- "bridge_eth_wallet_gas_balance_gwei",
2510
- { description: "ETH balance of the bridge runner wallet (in gwei)", unit: "gwei" }
2511
- );
2512
- const liquidityTokenBalance = meter.createGauge(
2513
- "bridge_eth_liquidity_token_balance",
2514
- { description: "ERC-20 token balance of the liquidity source (in whole tokens)", unit: "tokens" }
2515
- );
2516
- const liquidityTokenAllowance = meter.createGauge(
2517
- "bridge_eth_liquidity_token_allowance",
2518
- { description: "ERC-20 token allowance from liquidity source to bridge contract (in whole tokens)", unit: "tokens" }
2519
- );
2520
- const xl1AccountBalance = meter.createGauge(
2521
- "bridge_xl1_account_balance",
2522
- { description: "XL1 native balance of the bridge reserve account (in whole XL1)", unit: "xl1" }
2523
- );
2524
- const walletAddress = wallet.address;
2525
- async function poll() {
2526
- try {
2527
- const balance = await provider.getBalance(walletAddress);
2528
- ethWalletGasBalance.record(Number(balance / WEI_PER_GWEI), { address: walletAddress });
2529
- } catch (err) {
2530
- console.error("[BalanceMonitor] Failed to read ETH wallet gas balance:", err);
2531
- }
2532
- try {
2533
- const liquiditySourceAddress = await bridge.liquiditySource();
2534
- const balance = await bridgeableToken.balanceOf(liquiditySourceAddress);
2535
- liquidityTokenBalance.record(Number(balance / TOKEN_DECIMALS), { address: liquiditySourceAddress });
2536
- } catch (err) {
2537
- console.error("[BalanceMonitor] Failed to read liquidity source token balance:", err);
2538
- }
2539
- try {
2540
- const liquiditySourceAddress = await bridge.liquiditySource();
2541
- const bridgeAddress = await bridge.getAddress();
2542
- const allowance = await bridgeableToken.allowance(liquiditySourceAddress, bridgeAddress);
2543
- liquidityTokenAllowance.record(Number(allowance / TOKEN_DECIMALS), { address: liquiditySourceAddress });
2544
- } catch (err) {
2545
- console.error("[BalanceMonitor] Failed to read liquidity source token allowance:", err);
2546
- }
2547
- try {
2548
- const viewer = gateway.connection.viewer;
2549
- if (viewer) {
2550
- const balance = await viewer.account.balance.accountBalance(account.address);
2551
- xl1AccountBalance.record(Number(balance / TOKEN_DECIMALS), { address: account.address.toString() });
2552
- }
2553
- } catch (err) {
2554
- console.error("[BalanceMonitor] Failed to read XL1 account balance:", err);
2555
- }
2556
- }
2557
- return {
2558
- start() {
2559
- void poll();
2560
- timer = setInterval(() => void poll(), intervalMs);
2561
- },
2562
- stop() {
2563
- if (timer) {
2564
- clearInterval(timer);
2565
- timer = void 0;
2566
- }
2567
- }
2568
- };
2569
- }
2570
-
2571
- // src/telemetry/createQueueMetrics.ts
2572
- var DEFAULT_INTERVAL_MS2 = 3e4;
2573
- function createQueueMetrics(config) {
2574
- const {
2575
- meter,
2576
- queues,
2577
- intervalMs = DEFAULT_INTERVAL_MS2
2578
- } = config;
2579
- let timer;
2580
- const waitingGauge = meter.createGauge(
2581
- "bridge_queue_waiting",
2582
- { description: "Number of waiting jobs in the bridge queue" }
2583
- );
2584
- const activeGauge = meter.createGauge(
2585
- "bridge_queue_active",
2586
- { description: "Number of active jobs in the bridge queue" }
2587
- );
2588
- const completedGauge = meter.createGauge(
2589
- "bridge_queue_completed",
2590
- { description: "Number of completed jobs in the bridge queue" }
2591
- );
2592
- const failedGauge = meter.createGauge(
2593
- "bridge_queue_failed",
2594
- { description: "Number of failed jobs in the bridge queue" }
2595
- );
2596
- const delayedGauge = meter.createGauge(
2597
- "bridge_queue_delayed",
2598
- { description: "Number of delayed jobs in the bridge queue" }
2599
- );
2600
- async function poll() {
2601
- for (const [name13, queue] of Object.entries(queues)) {
2602
- try {
2603
- const counts = await queue.getJobCounts("waiting", "active", "completed", "failed", "delayed");
2604
- const attrs = { queue_name: name13 };
2605
- waitingGauge.record(counts.waiting ?? 0, attrs);
2606
- activeGauge.record(counts.active ?? 0, attrs);
2607
- completedGauge.record(counts.completed ?? 0, attrs);
2608
- failedGauge.record(counts.failed ?? 0, attrs);
2609
- delayedGauge.record(counts.delayed ?? 0, attrs);
2610
- } catch (err) {
2611
- console.error(`[QueueMetrics] Failed to read job counts for queue ${name13}:`, err);
2612
- }
2613
- }
2614
- }
2615
- return {
2616
- start() {
2617
- void poll();
2618
- timer = setInterval(() => void poll(), intervalMs);
2619
- },
2620
- stop() {
2621
- if (timer) {
2622
- clearInterval(timer);
2623
- timer = void 0;
2624
- }
2625
- }
2626
- };
2627
- }
2628
-
2629
3097
  // src/BridgeActor.ts
2630
3098
  var BridgeActor = class extends ActorV3 {
2631
3099
  _gatewayRunner;
2632
3100
  _balanceMonitor;
3101
+ _inFlightPoller;
2633
3102
  _queueMetrics;
2634
3103
  _scannerRunner;
2635
3104
  server;
@@ -2650,15 +3119,17 @@ var BridgeActor = class extends ActorV3 {
2650
3119
  await this._scannerRunner?.stop();
2651
3120
  this._scannerRunner = void 0;
2652
3121
  this._balanceMonitor?.stop();
3122
+ this._inFlightPoller?.stop();
2653
3123
  this._queueMetrics?.stop();
2654
3124
  this._balanceMonitor = void 0;
3125
+ this._inFlightPoller = void 0;
2655
3126
  this._queueMetrics = void 0;
2656
3127
  this.stopServer();
2657
3128
  }
2658
3129
  async startServer() {
2659
3130
  const context = asBridgeConfigContext(this.context, true);
2660
3131
  const config = context.config;
2661
- const { server, services } = await getServer(context, this._gatewayRunner);
3132
+ const { server, services } = await getServer(context, this._gatewayRunner, this.logger, this.meter);
2662
3133
  this.server = server;
2663
3134
  const connection2 = getConnection(config);
2664
3135
  const telemetry2 = getTelemetry();
@@ -2667,7 +3138,6 @@ var BridgeActor = class extends ActorV3 {
2667
3138
  config,
2668
3139
  connection: connection2,
2669
3140
  flowProducer: flowProducer2,
2670
- logger: this.logger,
2671
3141
  services,
2672
3142
  telemetry: telemetry2
2673
3143
  });
@@ -2680,6 +3150,7 @@ var BridgeActor = class extends ActorV3 {
2680
3150
  gateway: services.gateway,
2681
3151
  meter: this.meter,
2682
3152
  provider: services.provider,
3153
+ remoteChainId: config.remoteChainId,
2683
3154
  wallet: services.wallet
2684
3155
  });
2685
3156
  this._balanceMonitor.start();
@@ -2689,9 +3160,16 @@ var BridgeActor = class extends ActorV3 {
2689
3160
  };
2690
3161
  this._queueMetrics = createQueueMetrics({
2691
3162
  meter: this.meter,
2692
- queues
3163
+ queues,
3164
+ remoteChainId: config.remoteChainId
2693
3165
  });
2694
3166
  this._queueMetrics.start();
3167
+ this._inFlightPoller = createInFlightPoller({
3168
+ bridgeFulfillmentMap: services.bridgeFulfillmentMap,
3169
+ meter: this.meter,
3170
+ remoteChainId: config.remoteChainId
3171
+ });
3172
+ this._inFlightPoller.start();
2695
3173
  }
2696
3174
  }
2697
3175
  stopServer() {
@@ -2753,7 +3231,10 @@ function bridgeCommand(getConfiguration, getLocatorsFromConfig) {
2753
3231
  export {
2754
3232
  BridgeActor,
2755
3233
  bridgeCommand,
3234
+ bucketFailureReason,
2756
3235
  createBalanceMonitor,
3236
+ createBridgeFlowMetrics,
3237
+ createInFlightPoller,
2757
3238
  createQueueMetrics,
2758
3239
  getBridgeActor,
2759
3240
  runBridge