@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.
- package/dist/node/BridgeActor.d.ts +1 -0
- package/dist/node/BridgeActor.d.ts.map +1 -1
- package/dist/node/index.mjs +1495 -1014
- package/dist/node/index.mjs.map +4 -4
- package/dist/node/server/routes/bridge/routeDefinitions/getRouteDefinitions.d.ts.map +1 -1
- package/dist/node/server/routes/bridge/routeDefinitions/pathParams/ChainIdPathParam.d.ts +2 -1
- package/dist/node/server/routes/bridge/routeDefinitions/pathParams/ChainIdPathParam.d.ts.map +1 -1
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeConfig.d.ts +0 -22
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeConfig.d.ts.map +1 -1
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteEstimate.d.ts +0 -89
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteEstimate.d.ts.map +1 -1
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteRetry.d.ts +10 -0
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteRetry.d.ts.map +1 -0
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteRetryFailed.d.ts +9 -0
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteRetryFailed.d.ts.map +1 -0
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteStatus.d.ts +0 -81
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteStatus.d.ts.map +1 -1
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteStatusByTx.d.ts +28 -0
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteStatusByTx.d.ts.map +1 -0
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemote.d.ts +0 -81
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemote.d.ts.map +1 -1
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteEstimate.d.ts +0 -72
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteEstimate.d.ts.map +1 -1
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteMaxEstimate.d.ts +0 -72
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteMaxEstimate.d.ts.map +1 -1
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteRetry.d.ts +10 -0
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteRetry.d.ts.map +1 -0
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteRetryFailed.d.ts +9 -0
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteRetryFailed.d.ts.map +1 -0
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteStatus.d.ts +0 -81
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteStatus.d.ts.map +1 -1
- package/dist/node/server/routes/bridge/routeDefinitions/routes/index.d.ts +5 -0
- package/dist/node/server/routes/bridge/routeDefinitions/routes/index.d.ts.map +1 -1
- package/dist/node/server/server.d.ts +3 -1
- package/dist/node/server/server.d.ts.map +1 -1
- package/dist/node/services/BridgeFulfillmentState.d.ts +21 -12
- package/dist/node/services/BridgeFulfillmentState.d.ts.map +1 -1
- package/dist/node/services/IBridgeServiceCollection.d.ts +8 -1
- package/dist/node/services/IBridgeServiceCollection.d.ts.map +1 -1
- package/dist/node/services/evm/getFeeStructure.d.ts +1 -1
- package/dist/node/services/evm/getFeeStructure.d.ts.map +1 -1
- package/dist/node/services/evm/index.d.ts +1 -0
- package/dist/node/services/evm/index.d.ts.map +1 -1
- package/dist/node/services/evm/resolveDeployBlock.d.ts +28 -0
- package/dist/node/services/evm/resolveDeployBlock.d.ts.map +1 -0
- package/dist/node/services/getServices.d.ts +3 -1
- package/dist/node/services/getServices.d.ts.map +1 -1
- package/dist/node/services/queue/flows/createEthToXl1BridgeJob/getJobIdForEthToXl1BridgeJob.d.ts +2 -13
- package/dist/node/services/queue/flows/createEthToXl1BridgeJob/getJobIdForEthToXl1BridgeJob.d.ts.map +1 -1
- package/dist/node/services/queue/index.d.ts +2 -0
- package/dist/node/services/queue/index.d.ts.map +1 -1
- package/dist/node/services/queue/retryFailedJobs.d.ts +39 -0
- package/dist/node/services/queue/retryFailedJobs.d.ts.map +1 -0
- package/dist/node/services/queue/retrySingleFailedJob.d.ts +29 -0
- package/dist/node/services/queue/retrySingleFailedJob.d.ts.map +1 -0
- package/dist/node/services/queue/scanner/EvmBridgeCursor.d.ts +1 -0
- package/dist/node/services/queue/scanner/EvmBridgeCursor.d.ts.map +1 -1
- package/dist/node/services/queue/scanner/EvmBridgeScanner.d.ts +1 -0
- package/dist/node/services/queue/scanner/EvmBridgeScanner.d.ts.map +1 -1
- package/dist/node/services/queue/scanner/EvmBridgeScannerRunner.d.ts +1 -2
- package/dist/node/services/queue/scanner/EvmBridgeScannerRunner.d.ts.map +1 -1
- package/dist/node/services/queue/scanner/buildEvmBridgeScannerRunner.d.ts +0 -3
- package/dist/node/services/queue/scanner/buildEvmBridgeScannerRunner.d.ts.map +1 -1
- package/dist/node/services/queue/workers/EthEventVerification.d.ts.map +1 -1
- package/dist/node/services/queue/workers/EthToXl1BridgeParent.d.ts.map +1 -1
- package/dist/node/services/queue/workers/EthTransactionMonitor.d.ts.map +1 -1
- package/dist/node/services/queue/workers/EthTransactionPreparation.d.ts.map +1 -1
- package/dist/node/services/queue/workers/EthTransactionSubmission.d.ts.map +1 -1
- package/dist/node/services/queue/workers/EthTransactionSubmissionStorage.d.ts.map +1 -1
- package/dist/node/services/queue/workers/Xl1ReserveTxFulfillment.d.ts.map +1 -1
- package/dist/node/services/queue/workers/Xl1ToEthBridgeParent.d.ts.map +1 -1
- package/dist/node/services/queue/workers/Xl1TransactionMonitor.d.ts.map +1 -1
- package/dist/node/services/queue/workers/Xl1TransactionPreparation.d.ts.map +1 -1
- package/dist/node/services/queue/workers/Xl1TransactionSubmission.d.ts.map +1 -1
- package/dist/node/services/queue/workers/Xl1TransactionSubmissionStorage.d.ts.map +1 -1
- package/dist/node/services/queue/workers/util/buildAcceptedSnapshot.d.ts +47 -0
- package/dist/node/services/queue/workers/util/buildAcceptedSnapshot.d.ts.map +1 -0
- package/dist/node/services/queue/workers/util/buildEthToXl1ReserveTx.d.ts.map +1 -1
- package/dist/node/services/queue/workers/util/index.d.ts +1 -1
- package/dist/node/services/queue/workers/util/index.d.ts.map +1 -1
- package/dist/node/services/queue/workers/util/resolveEvmBlockTagAtDepth.d.ts +7 -3
- package/dist/node/services/queue/workers/util/resolveEvmBlockTagAtDepth.d.ts.map +1 -1
- package/dist/node/services/queue/workers/util/verifyEthBridgeEvent.d.ts +7 -4
- package/dist/node/services/queue/workers/util/verifyEthBridgeEvent.d.ts.map +1 -1
- package/dist/node/services/util/generateBridgeEstimate.d.ts.map +1 -1
- package/dist/node/services/util/index.d.ts +0 -5
- package/dist/node/services/util/index.d.ts.map +1 -1
- package/dist/node/services/validation/validateBridgeEstimate.d.ts.map +1 -1
- package/dist/node/services/validation/validateSufficientXl1ReserveBalance.d.ts +15 -10
- package/dist/node/services/validation/validateSufficientXl1ReserveBalance.d.ts.map +1 -1
- package/dist/node/telemetry/bucketFailureReason.d.ts +17 -0
- package/dist/node/telemetry/bucketFailureReason.d.ts.map +1 -0
- package/dist/node/telemetry/createBalanceMonitor.d.ts +5 -0
- package/dist/node/telemetry/createBalanceMonitor.d.ts.map +1 -1
- package/dist/node/telemetry/createBridgeFlowMetrics.d.ts +76 -0
- package/dist/node/telemetry/createBridgeFlowMetrics.d.ts.map +1 -0
- package/dist/node/telemetry/createInFlightPoller.d.ts +29 -0
- package/dist/node/telemetry/createInFlightPoller.d.ts.map +1 -0
- package/dist/node/telemetry/createQueueMetrics.d.ts +4 -0
- package/dist/node/telemetry/createQueueMetrics.d.ts.map +1 -1
- package/dist/node/telemetry/index.d.ts +3 -0
- package/dist/node/telemetry/index.d.ts.map +1 -1
- package/package.json +89 -88
- package/dist/node/services/queue/workers/util/buildEthToXl1BridgePayloads.d.ts +0 -44
- package/dist/node/services/queue/workers/util/buildEthToXl1BridgePayloads.d.ts.map +0 -1
- package/dist/node/services/util/BridgeFees.d.ts +0 -13
- package/dist/node/services/util/BridgeFees.d.ts.map +0 -1
- package/dist/node/services/util/bridgeFeesAsBigInt.d.ts +0 -7
- package/dist/node/services/util/bridgeFeesAsBigInt.d.ts.map +0 -1
- package/dist/node/services/util/calculateBridgeFees.d.ts +0 -6
- package/dist/node/services/util/calculateBridgeFees.d.ts.map +0 -1
- package/dist/node/services/util/calculateMaxBridgeAmount.d.ts +0 -11
- package/dist/node/services/util/calculateMaxBridgeAmount.d.ts.map +0 -1
- package/dist/node/services/util/createBridgeTransfer.d.ts +0 -14
- package/dist/node/services/util/createBridgeTransfer.d.ts.map +0 -1
package/dist/node/index.mjs
CHANGED
|
@@ -57,14 +57,475 @@ var getFlowProducer = (connection2, telemetry2) => {
|
|
|
57
57
|
};
|
|
58
58
|
|
|
59
59
|
// src/services/queue/workers/EthEventVerification.ts
|
|
60
|
-
import {
|
|
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/
|
|
64
|
-
|
|
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 {
|
|
67
|
-
|
|
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
|
-
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
|
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 } =
|
|
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 =
|
|
140
|
-
const srcAmountHex =
|
|
141
|
-
const sourceObservation = new PayloadBuilder2({ schema:
|
|
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,
|
|
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
|
|
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 =
|
|
657
|
+
const xl1Transaction = assertEx6(tx[0], () => "No corresponding XL1 transaction found");
|
|
180
658
|
const allPayloads = [...tx[1], ...offChainPayloads];
|
|
181
|
-
const bridgeIntent =
|
|
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 =
|
|
185
|
-
const nonce =
|
|
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,
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
at
|
|
249
|
-
|
|
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(
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
|
|
809
|
+
logger.error(`[eth-to-xl1-parent] job ${job?.id} failed: ${err.message}`);
|
|
346
810
|
});
|
|
347
811
|
worker.on("error", (err) => {
|
|
348
|
-
|
|
812
|
+
logger.error(`[eth-to-xl1-parent] worker error: ${err.message}`);
|
|
349
813
|
});
|
|
350
814
|
};
|
|
351
|
-
var
|
|
352
|
-
createWorker:
|
|
353
|
-
name:
|
|
354
|
-
queueName:
|
|
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/
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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/
|
|
499
|
-
import {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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/
|
|
514
|
-
|
|
515
|
-
|
|
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/
|
|
519
|
-
import { hexToBigInt as
|
|
520
|
-
|
|
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 {
|
|
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
|
-
|
|
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 (
|
|
601
|
-
if (
|
|
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
|
|
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
|
|
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
|
|
622
|
-
import { BridgeIntentSchema
|
|
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, [
|
|
629
|
-
const hashes = await
|
|
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
|
|
640
|
-
import { isBridgeIntent as
|
|
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(
|
|
644
|
-
const amount =
|
|
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
|
|
654
|
-
import { isBridgeIntent as
|
|
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(
|
|
658
|
-
const amount =
|
|
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
|
-
|
|
702
|
-
|
|
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
|
|
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 +
|
|
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
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
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
|
-
|
|
1121
|
+
logger.error(`[eth-prep] job ${job?.id} failed: ${err.message}`);
|
|
778
1122
|
});
|
|
779
1123
|
worker.on("error", (err) => {
|
|
780
|
-
|
|
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 {
|
|
791
|
-
|
|
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
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
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
|
-
|
|
1174
|
+
logger.error(`[eth-submit] job ${job?.id} failed: ${err.message}`);
|
|
824
1175
|
});
|
|
825
1176
|
worker.on("error", (err) => {
|
|
826
|
-
|
|
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 {
|
|
837
|
-
|
|
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
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
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
|
-
|
|
1230
|
+
logger.error(`[eth-submit-store] job ${job?.id} failed: ${err.message}`);
|
|
873
1231
|
});
|
|
874
1232
|
worker.on("error", (err) => {
|
|
875
|
-
|
|
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
|
|
1246
|
+
hexToBigInt as hexToBigInt10,
|
|
889
1247
|
isDefined as isDefined9,
|
|
890
1248
|
isNull as isNull2,
|
|
891
|
-
|
|
1249
|
+
spanAsync as spanAsync10
|
|
892
1250
|
} from "@xylabs/sdk-js";
|
|
893
|
-
import { PayloadBuilder as
|
|
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
|
-
|
|
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, () =>
|
|
943
|
-
const
|
|
944
|
-
const feesAmount =
|
|
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()};
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
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
|
-
|
|
1417
|
+
identity,
|
|
1080
1418
|
xl1ChainId,
|
|
1081
1419
|
xl1TokenAddress
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
|
|
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",
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
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
|
-
|
|
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
|
|
1126
|
-
|
|
1127
|
-
|
|
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
|
-
|
|
1511
|
+
logger.error(`[xl1-to-eth-parent] job ${job?.id} failed: ${err.message}`);
|
|
1137
1512
|
});
|
|
1138
1513
|
worker.on("error", (err) => {
|
|
1139
|
-
|
|
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
|
|
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
|
|
1155
|
-
import { UnrecoverableError as
|
|
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 =
|
|
1160
|
-
const stateMap =
|
|
1161
|
-
const viewer =
|
|
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
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
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
|
-
|
|
1572
|
+
logger.error(`[xl1-monitor] job ${job?.id} failed: ${err.message}`);
|
|
1194
1573
|
});
|
|
1195
1574
|
worker.on("error", (err) => {
|
|
1196
|
-
|
|
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
|
|
1207
|
-
import { PayloadBuilder as
|
|
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 =
|
|
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
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
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
|
-
|
|
1615
|
+
logger.error(`[xl1-prep] job ${job?.id} failed: ${err.message}`);
|
|
1234
1616
|
});
|
|
1235
1617
|
worker.on("error", (err) => {
|
|
1236
|
-
|
|
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 {
|
|
1247
|
-
|
|
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 =
|
|
1253
|
-
const stateMap =
|
|
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
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
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
|
-
|
|
1671
|
+
logger.error(`[xl1-submit] job ${job?.id} failed: ${err.message}`);
|
|
1283
1672
|
});
|
|
1284
1673
|
worker.on("error", (err) => {
|
|
1285
|
-
|
|
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 {
|
|
1296
|
-
|
|
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 =
|
|
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
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
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
|
-
|
|
1727
|
+
logger.error(`[xl1-submit-store] job ${job?.id} failed: ${err.message}`);
|
|
1332
1728
|
});
|
|
1333
1729
|
worker.on("error", (err) => {
|
|
1334
|
-
|
|
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
|
|
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
|
|
1806
|
+
import { PayloadBuilder as PayloadBuilder15 } from "@xyo-network/sdk-js";
|
|
1410
1807
|
var getJobIdForXl1ToEthBridgeJob = async (tx) => {
|
|
1411
|
-
const jobId = await
|
|
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
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
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
|
|
2208
|
+
logger.error(`[scanner] tick ${job?.id} failed: ${err.message}`);
|
|
1702
2209
|
});
|
|
1703
2210
|
worker.on("error", (err) => {
|
|
1704
|
-
logger.error(
|
|
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 {
|
|
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
|
|
1869
|
-
hexToBigInt as
|
|
1870
|
-
toAddress as
|
|
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
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
} from "
|
|
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
|
-
|
|
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.
|
|
1892
|
-
const chainId = asHex3(val);
|
|
1893
|
-
|
|
1894
|
-
|
|
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 =
|
|
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 (
|
|
2426
|
+
if (hexToBigInt11(srcAmount) < hexToBigInt11(minBridgeAmount) || hexToBigInt11(srcAmount) > hexToBigInt11(maxBridgeAmount)) {
|
|
1938
2427
|
res.status(400).send();
|
|
1939
2428
|
return;
|
|
1940
2429
|
}
|
|
1941
|
-
const fees =
|
|
1942
|
-
const feesAmount =
|
|
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 =
|
|
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 } =
|
|
1956
|
-
amount:
|
|
2444
|
+
const { intent, transfer } = buildEthToXl1BridgePayloads3({
|
|
2445
|
+
amount: hexToBigInt11(srcAmount),
|
|
1957
2446
|
bridgeAccountAddress: account.address,
|
|
1958
2447
|
bridgeId: projectedBridgeId,
|
|
1959
|
-
destAddress:
|
|
2448
|
+
destAddress: toAddress2(destAddress),
|
|
1960
2449
|
evmChainId,
|
|
1961
2450
|
evmContractAddress,
|
|
1962
|
-
evmSrcAddress:
|
|
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/
|
|
2464
|
+
// src/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteRetry.ts
|
|
1976
2465
|
import { requestHandlerValidator as requestHandlerValidator3 } from "@xylabs/express";
|
|
1977
|
-
import {
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
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
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2473
|
+
const validateRequest2 = requestHandlerValidator3({
|
|
2474
|
+
params,
|
|
2475
|
+
body: BridgeRetryBodyZod,
|
|
2476
|
+
response: BridgeRetryResponseZod
|
|
2030
2477
|
});
|
|
2031
|
-
|
|
2032
|
-
|
|
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 =
|
|
2557
|
+
const params = z5.object({
|
|
2036
2558
|
chainId: getRemoteChainIdZod(config),
|
|
2037
|
-
nonce:
|
|
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 =
|
|
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 (!
|
|
2579
|
+
if (!isDefined15(parentJob)) return res.sendStatus(404);
|
|
2059
2580
|
return res.json([]);
|
|
2060
2581
|
}
|
|
2061
|
-
const
|
|
2062
|
-
|
|
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
|
|
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
|
|
2079
|
-
import { isDefined as
|
|
2080
|
-
import {
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
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 =
|
|
2102
|
-
const validateRequest2 =
|
|
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 (
|
|
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
|
|
2137
|
-
const bridgeCommonFieldsZod =
|
|
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
|
|
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
|
|
2693
|
+
import { requestHandlerValidator as requestHandlerValidator8 } from "@xylabs/express";
|
|
2150
2694
|
import {
|
|
2151
|
-
assertEx as
|
|
2152
|
-
hexToBigInt as
|
|
2153
|
-
toAddress as
|
|
2695
|
+
assertEx as assertEx27,
|
|
2696
|
+
hexToBigInt as hexToBigInt12,
|
|
2697
|
+
toAddress as toAddress3
|
|
2154
2698
|
} from "@xylabs/sdk-js";
|
|
2155
|
-
import {
|
|
2156
|
-
import {
|
|
2157
|
-
|
|
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 =
|
|
2177
|
-
const validateRequest2 =
|
|
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 (
|
|
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 =
|
|
2199
|
-
const viewer =
|
|
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
|
|
2737
|
+
import { requestHandlerValidator as requestHandlerValidator9 } from "@xylabs/express";
|
|
2211
2738
|
import {
|
|
2212
|
-
assertEx as
|
|
2213
|
-
hexToBigInt as
|
|
2214
|
-
toAddress as
|
|
2215
|
-
toHex as
|
|
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
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
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 =
|
|
2239
|
-
const validateRequest2 =
|
|
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 =
|
|
2774
|
+
const maxBridgeAmount = toHex4(hexToBigInt13(balanceMax) < hexToBigInt13(configMax) ? balanceMax : configMax);
|
|
2261
2775
|
const [bridgeIntent, transfer] = await generateBridgeEstimate(srcAddress, maxBridgeAmount, destAddress, config);
|
|
2262
|
-
const sender =
|
|
2263
|
-
const viewer =
|
|
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
|
|
2275
|
-
import { asHex as asHex4, isDefined as
|
|
2276
|
-
import {
|
|
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
|
|
2882
|
+
isBridgeIntent as isBridgeIntent7
|
|
2286
2883
|
} from "@xyo-network/xl1-sdk";
|
|
2287
|
-
import { z as
|
|
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 =
|
|
2886
|
+
const params = z12.object({
|
|
2299
2887
|
chainId: getRemoteChainIdZod(config),
|
|
2300
|
-
nonce:
|
|
2888
|
+
nonce: z12.string().check(z12.minLength(1))
|
|
2301
2889
|
});
|
|
2302
|
-
const validateRequest2 =
|
|
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(
|
|
2902
|
+
const bridgeIntent = allPayloads.find(isBridgeIntent7);
|
|
2315
2903
|
if (!bridgeIntent) return res.sendStatus(404);
|
|
2316
|
-
result[0] = asBridgeIntent(
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
2445
|
-
const bridge = LiquidityPoolBridge__factory.connect(
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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
|