@xyo-network/chain-bridge 1.19.16 → 1.19.18
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/index.mjs +256 -145
- package/dist/node/index.mjs.map +1 -1
- package/dist/node/queue/flowProducer.d.ts +2 -1
- package/dist/node/queue/flowProducer.d.ts.map +1 -1
- package/dist/node/queue/flows/createXl1ToEthBridgeJob.d.ts.map +1 -1
- package/dist/node/queue/index.d.ts +1 -0
- package/dist/node/queue/index.d.ts.map +1 -1
- package/dist/node/queue/telemetry.d.ts +3 -0
- package/dist/node/queue/telemetry.d.ts.map +1 -0
- package/dist/node/queue/workers/EthTransactionMonitor.d.ts +9 -0
- package/dist/node/queue/workers/EthTransactionMonitor.d.ts.map +1 -1
- package/dist/node/queue/workers/EthTransactionPreparation.d.ts +7 -1
- package/dist/node/queue/workers/EthTransactionPreparation.d.ts.map +1 -1
- package/dist/node/queue/workers/EthTransactionSubmission.d.ts +8 -0
- package/dist/node/queue/workers/EthTransactionSubmission.d.ts.map +1 -1
- package/dist/node/queue/workers/WorkerDescription.d.ts +2 -1
- package/dist/node/queue/workers/WorkerDescription.d.ts.map +1 -1
- package/dist/node/queue/workers/Xl1ToEthBridgeParent.d.ts +3 -0
- package/dist/node/queue/workers/Xl1ToEthBridgeParent.d.ts.map +1 -1
- package/dist/node/queue/workers/Xl1TransactionMonitor.d.ts +6 -0
- package/dist/node/queue/workers/Xl1TransactionMonitor.d.ts.map +1 -1
- package/dist/node/queue/workers/Xl1TransactionPreparation.d.ts +7 -0
- package/dist/node/queue/workers/Xl1TransactionPreparation.d.ts.map +1 -1
- package/dist/node/queue/workers/Xl1TransactionSubmission.d.ts +8 -0
- package/dist/node/queue/workers/Xl1TransactionSubmission.d.ts.map +1 -1
- package/dist/node/queue/workers/createWorkers.d.ts +2 -1
- package/dist/node/queue/workers/createWorkers.d.ts.map +1 -1
- package/dist/node/queue/workers/util/AsyncLogger.d.ts +5 -0
- package/dist/node/queue/workers/util/AsyncLogger.d.ts.map +1 -0
- package/dist/node/queue/workers/util/index.d.ts +3 -2
- package/dist/node/queue/workers/util/index.d.ts.map +1 -1
- package/dist/node/queue/workers/util/{validateSufficientAllowance.d.ts → validateSufficientLiquiditySourceAllowance.d.ts} +3 -7
- package/dist/node/queue/workers/util/validateSufficientLiquiditySourceAllowance.d.ts.map +1 -0
- package/dist/node/queue/workers/util/{validateSufficientBalance.d.ts → validateSufficientLiquiditySourceBalance.d.ts} +3 -7
- package/dist/node/queue/workers/util/validateSufficientLiquiditySourceBalance.d.ts.map +1 -0
- package/dist/node/queue/workers/util/validateSufficientRunnerEthBalanceForGas.d.ts +14 -0
- package/dist/node/queue/workers/util/validateSufficientRunnerEthBalanceForGas.d.ts.map +1 -0
- package/dist/node/server/addFlowProducer.d.ts.map +1 -1
- package/dist/node/server/addWorkers.d.ts.map +1 -1
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteStatus.d.ts.map +1 -1
- package/dist/node/util/BridgeFees.d.ts +7 -1
- package/dist/node/util/BridgeFees.d.ts.map +1 -1
- package/package.json +30 -35
- package/src/config/getBridgeWalletAccount.ts +1 -1
- package/src/config/getGateway.ts +1 -1
- package/src/queue/flowProducer.ts +3 -2
- package/src/queue/flows/createXl1ToEthBridgeJob.ts +12 -2
- package/src/queue/index.ts +1 -0
- package/src/queue/telemetry.ts +12 -0
- package/src/queue/workers/EthTransactionMonitor.ts +10 -6
- package/src/queue/workers/EthTransactionPreparation.ts +17 -9
- package/src/queue/workers/EthTransactionSubmission.ts +8 -28
- package/src/queue/workers/WorkerDescription.ts +2 -1
- package/src/queue/workers/Xl1ToEthBridgeParent.ts +8 -5
- package/src/queue/workers/Xl1TransactionMonitor.ts +7 -5
- package/src/queue/workers/Xl1TransactionPreparation.ts +7 -7
- package/src/queue/workers/Xl1TransactionSubmission.ts +6 -5
- package/src/queue/workers/createWorkers.ts +9 -8
- package/src/queue/workers/util/AsyncLogger.ts +5 -0
- package/src/queue/workers/util/index.ts +3 -2
- package/src/queue/workers/util/{validateSufficientAllowance.ts → validateSufficientLiquiditySourceAllowance.ts} +3 -6
- package/src/queue/workers/util/{validateSufficientBalance.ts → validateSufficientLiquiditySourceBalance.ts} +3 -6
- package/src/queue/workers/util/validateSufficientRunnerEthBalanceForGas.ts +57 -0
- package/src/server/addFlowProducer.ts +5 -2
- package/src/server/addWorkers.ts +6 -2
- package/src/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteStatus.ts +130 -65
- package/src/services/getServices.ts +1 -1
- package/src/util/BridgeFees.ts +10 -1
- package/dist/node/queue/workers/util/validateSufficientAllowance.d.ts.map +0 -1
- package/dist/node/queue/workers/util/validateSufficientBalance.d.ts.map +0 -1
|
@@ -3,38 +3,41 @@ import { PayloadBuilder } from '@xyo-network/sdk-js'
|
|
|
3
3
|
import { isBridgeIntent, type SignedHydratedTransaction } from '@xyo-network/xl1-sdk'
|
|
4
4
|
import type { Job } from 'bullmq'
|
|
5
5
|
import { Worker } from 'bullmq'
|
|
6
|
+
import type { BullMQOtel } from 'bullmq-otel'
|
|
6
7
|
import { getAddress } from 'ethers'
|
|
7
8
|
import type { Redis } from 'ioredis'
|
|
8
9
|
|
|
9
10
|
import type { IBridgeServiceCollection } from '../../services/index.ts'
|
|
10
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
validateSufficientLiquiditySourceAllowance, validateSufficientLiquiditySourceBalance, validateSufficientRunnerEthBalanceForGas,
|
|
13
|
+
} from './util/index.ts'
|
|
11
14
|
import type { WorkerDescription } from './WorkerDescription.ts'
|
|
12
15
|
|
|
13
|
-
type
|
|
14
|
-
|
|
15
|
-
interface ReturnType {}
|
|
16
|
+
export type EthTransactionPrepareJobData = { tx: SignedHydratedTransaction }
|
|
17
|
+
export type EthTransactionPrepareJobReturn = Record<string, never>
|
|
16
18
|
|
|
17
19
|
const name = 'Prepare ETH Transaction'
|
|
18
20
|
const queueName = 'eth-tx-prepare'
|
|
19
21
|
|
|
20
|
-
export const createWorker = (connection: Redis, services?: IBridgeServiceCollection) => {
|
|
22
|
+
export const createWorker = (connection: Redis, telemetry?: BullMQOtel, services?: IBridgeServiceCollection) => {
|
|
21
23
|
const bridge = assertEx(services?.bridge, () => 'bridge service not provided')
|
|
22
24
|
const bridgeableToken = assertEx(services?.bridgeableToken, () => 'bridgeableToken service not provided')
|
|
23
25
|
const stateMap = assertEx(services?.ethTxStateMap, () => 'ethTxStateMap service not provided')
|
|
26
|
+
const wallet = assertEx(services?.wallet, () => 'wallet service not provided')
|
|
24
27
|
|
|
25
28
|
const worker = new Worker(
|
|
26
29
|
queueName,
|
|
27
|
-
async (job: Job<
|
|
30
|
+
async (job: Job<EthTransactionPrepareJobData, EthTransactionPrepareJobReturn>) => {
|
|
28
31
|
const { tx } = job.data
|
|
29
32
|
const hash = await PayloadBuilder.hash(tx[0])
|
|
30
33
|
await job.log(`[${hash}] preparing ETH transaction`)
|
|
31
34
|
await job.log(`[${hash}] validating liquiditySource has sufficient allowance`)
|
|
32
|
-
if (!await
|
|
35
|
+
if (!await validateSufficientLiquiditySourceAllowance(tx, bridgeableToken, bridge, job)) {
|
|
33
36
|
throw new Error('Liquidity source does not have sufficient allowance for the bridge to execute the transaction')
|
|
34
37
|
}
|
|
35
38
|
await job.log(`[${hash}] validated liquiditySource has sufficient allowance`)
|
|
36
39
|
await job.log(`[${hash}] validating liquiditySource has sufficient balance`)
|
|
37
|
-
if (!await
|
|
40
|
+
if (!await validateSufficientLiquiditySourceBalance(tx, bridgeableToken, bridge, job)) {
|
|
38
41
|
throw new Error('Liquidity source does not have sufficient balance for the bridge to execute the transaction')
|
|
39
42
|
}
|
|
40
43
|
await job.log(`[${hash}] validated liquiditySource has sufficient balance`)
|
|
@@ -46,13 +49,18 @@ export const createWorker = (connection: Redis, services?: IBridgeServiceCollect
|
|
|
46
49
|
const nonce = hexToBigInt(await PayloadBuilder.hash(tx[0]))
|
|
47
50
|
const preparedTx = await bridge.getFunction('bridgeFromRemote').populateTransaction(srcAddress, destAddress, amount, nonce)
|
|
48
51
|
await job.log(`[${hash}] built ETH transaction`)
|
|
52
|
+
await job.log(`[${hash}] validating tx runner has sufficient ETH for gas`)
|
|
53
|
+
if (!await validateSufficientRunnerEthBalanceForGas(preparedTx, wallet, job)) {
|
|
54
|
+
throw new Error('Transaction runner does not have sufficient ETH to cover estimated gas (with buffer)')
|
|
55
|
+
}
|
|
56
|
+
await job.log(`[${hash}] validated tx runner has sufficient ETH for gas`)
|
|
49
57
|
await job.log(`[${hash}] storing ETH preparedTx`)
|
|
50
58
|
await stateMap.set(hash, { preparedTx })
|
|
51
59
|
await job.log(`[${hash}] stored ETH preparedTx`)
|
|
52
60
|
await job.log(`[${hash}] prepared ETH transaction`)
|
|
53
61
|
return {}
|
|
54
62
|
},
|
|
55
|
-
{ connection },
|
|
63
|
+
{ connection, telemetry },
|
|
56
64
|
)
|
|
57
65
|
|
|
58
66
|
worker.on('failed', (job, err) => {
|
|
@@ -3,21 +3,22 @@ import { PayloadBuilder } from '@xyo-network/sdk-js'
|
|
|
3
3
|
import { type SignedHydratedTransaction } from '@xyo-network/xl1-sdk'
|
|
4
4
|
import type { Job } from 'bullmq'
|
|
5
5
|
import { Worker } from 'bullmq'
|
|
6
|
+
import type { BullMQOtel } from 'bullmq-otel'
|
|
6
7
|
import type { Redis } from 'ioredis'
|
|
7
8
|
|
|
8
9
|
import type { EthTxState, IBridgeServiceCollection } from '../../services/index.ts'
|
|
9
10
|
import { submitEthTransaction } from './util/index.ts'
|
|
10
11
|
import type { WorkerDescription } from './WorkerDescription.ts'
|
|
11
12
|
|
|
12
|
-
type
|
|
13
|
+
export type EthTransactionSubmissionJobData = { tx: SignedHydratedTransaction }
|
|
13
14
|
|
|
14
|
-
interface
|
|
15
|
+
export interface EthTransactionSubmissionJobReturn {
|
|
15
16
|
submissionHash: Required<EthTxState>['submissionHash']
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
const name = 'Submit ETH Transaction'
|
|
19
20
|
const queueName = 'eth-tx-submit'
|
|
20
|
-
const createWorker = (connection: Redis, services?: IBridgeServiceCollection) => {
|
|
21
|
+
const createWorker = (connection: Redis, telemetry?: BullMQOtel, services?: IBridgeServiceCollection) => {
|
|
21
22
|
const bridge = assertEx(services?.bridge, () => 'bridge service not provided')
|
|
22
23
|
const bridgeableToken = assertEx(services?.bridgeableToken, () => 'bridgeableToken service not provided')
|
|
23
24
|
const wallet = assertEx(services?.wallet, () => 'wallet service not provided')
|
|
@@ -25,11 +26,10 @@ const createWorker = (connection: Redis, services?: IBridgeServiceCollection) =>
|
|
|
25
26
|
|
|
26
27
|
const worker = new Worker(
|
|
27
28
|
queueName,
|
|
28
|
-
async (job: Job<
|
|
29
|
+
async (job: Job<EthTransactionSubmissionJobData, EthTransactionSubmissionJobReturn>) => {
|
|
29
30
|
const { tx } = job.data
|
|
30
31
|
const hash = await PayloadBuilder.hash(tx[0])
|
|
31
32
|
const state = assertEx(await stateMap.get(hash), () => `[${hash}] state not found`)
|
|
32
|
-
const preparedTx = assertEx(state?.preparedTx, () => `[${hash}] preparedTx not found`)
|
|
33
33
|
|
|
34
34
|
// Idempotency check against resubmission
|
|
35
35
|
const { submissionHash: existingSubmissionHash } = state
|
|
@@ -40,8 +40,6 @@ const createWorker = (connection: Redis, services?: IBridgeServiceCollection) =>
|
|
|
40
40
|
|
|
41
41
|
// Submit the transaction to the Ethereum network
|
|
42
42
|
await job.log(`[${hash}] Submitting ETH tx`)
|
|
43
|
-
// const submissionResponse = await wallet.sendTransaction(preparedTx)
|
|
44
|
-
// const submissionHash = submissionResponse.hash
|
|
45
43
|
|
|
46
44
|
const submissionHash = assertEx(await submitEthTransaction(tx, bridgeableToken, bridge, wallet), () => `[${hash}] submissionHash not found in receipt`)
|
|
47
45
|
|
|
@@ -54,28 +52,10 @@ const createWorker = (connection: Redis, services?: IBridgeServiceCollection) =>
|
|
|
54
52
|
await job.log(`[${hash}] Stored ETH submissionHash`)
|
|
55
53
|
|
|
56
54
|
return { submissionHash }
|
|
57
|
-
// const hash = await PayloadBuilder.hash(tx[0])
|
|
58
|
-
// await job.log(`[${hash}] Obtaining bridge intent from tx`)
|
|
59
|
-
// const bridgeIntent = assertEx(tx[1].find(isBridgeIntent), () => 'No bridge intent found in transaction payload')
|
|
60
|
-
// await job.log(`[${hash}] submitting ETH transaction`)
|
|
61
|
-
// const amount = hexToBigInt(bridgeIntent.destAmount)
|
|
62
|
-
// const srcAddress = getAddress(bridgeIntent.srcAddress)
|
|
63
|
-
// const destAddress = getAddress(bridgeIntent.destAddress)
|
|
64
|
-
// const tx = await bridge.bridgeFromRemote(srcAddress, destAddress, amount)
|
|
65
|
-
// // const nonce = await wallet.getNonce()
|
|
66
|
-
// // const tx = await bridge.bridgeFromRemote(srcAddress, destAddress, amount, { nonce })
|
|
67
|
-
// await job.log(`[${hash}] submitted ETH transaction`)
|
|
68
|
-
// const confirmation = await tx.wait()
|
|
69
|
-
// const transactionResponse = await confirmation?.getTransaction()
|
|
70
|
-
// const destConfirmation = asHex(transactionResponse?.hash ?? '', true)
|
|
71
|
-
// const block = await transactionResponse?.getBlock()
|
|
72
|
-
// await job.log(`[${hash}] confirmed ETH transaction with hash ${destConfirmation} in block ${block?.number}`)
|
|
73
|
-
// const { schema, ...rest } = bridgeIntent
|
|
74
|
-
// const result: BridgeDestinationObservation = new PayloadBuilder<BridgeDestinationObservation>({ schema: BridgeDestinationObservationSchema })
|
|
75
|
-
// .fields({ ...rest, destConfirmation }).build()
|
|
76
|
-
// return result
|
|
77
55
|
},
|
|
78
|
-
{
|
|
56
|
+
{
|
|
57
|
+
connection, telemetry, concurrency: 1,
|
|
58
|
+
},
|
|
79
59
|
)
|
|
80
60
|
|
|
81
61
|
worker.on('failed', (job, err) => {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import type { BullMQOtel } from 'bullmq-otel'
|
|
1
2
|
import type { Redis } from 'ioredis'
|
|
2
3
|
|
|
3
4
|
import type { IBridgeServiceCollection } from '../../services/index.ts'
|
|
4
5
|
|
|
5
6
|
export interface WorkerDescription {
|
|
6
|
-
createWorker: (connection: Redis, services?: IBridgeServiceCollection) => void
|
|
7
|
+
createWorker: (connection: Redis, telemetry?: BullMQOtel, services?: IBridgeServiceCollection) => void
|
|
7
8
|
name: string
|
|
8
9
|
queueName: string
|
|
9
10
|
}
|
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
import type { Job } from 'bullmq'
|
|
2
2
|
import { Worker } from 'bullmq'
|
|
3
|
+
import type { BullMQOtel } from 'bullmq-otel'
|
|
3
4
|
import type { Redis } from 'ioredis'
|
|
4
5
|
|
|
5
6
|
import type { WorkerDescription } from './WorkerDescription.ts'
|
|
6
7
|
|
|
8
|
+
export type Xl1ToEthBridgeParentJobData = {}
|
|
9
|
+
export interface Xl1ToEthBridgeParentJobReturn { }
|
|
10
|
+
|
|
7
11
|
const name = 'Bridge XL1 to Ethereum'
|
|
8
12
|
const queueName = 'xl1-to-eth-bridge'
|
|
9
|
-
const createWorker = (connection: Redis) => {
|
|
13
|
+
const createWorker = (connection: Redis, telemetry?: BullMQOtel) => {
|
|
10
14
|
const worker = new Worker(
|
|
11
15
|
queueName,
|
|
12
|
-
async (job: Job) => {
|
|
16
|
+
async (job: Job<Xl1ToEthBridgeParentJobData, Xl1ToEthBridgeParentJobReturn>) => {
|
|
13
17
|
await job.log(`[${job.name}] start`)
|
|
14
18
|
// Parent job has no work other than waiting on children
|
|
15
19
|
await job.log(`[${job.name}] done`)
|
|
16
|
-
|
|
17
|
-
return { ok: true }
|
|
20
|
+
return {}
|
|
18
21
|
},
|
|
19
|
-
{ connection },
|
|
22
|
+
{ connection, telemetry },
|
|
20
23
|
)
|
|
21
24
|
|
|
22
25
|
worker.on('failed', (job, err) => {
|
|
@@ -5,25 +5,26 @@ import { PayloadBuilder } from '@xyo-network/sdk-js'
|
|
|
5
5
|
import type { SignedHydratedTransaction } from '@xyo-network/xl1-sdk'
|
|
6
6
|
import type { Job } from 'bullmq'
|
|
7
7
|
import { UnrecoverableError, Worker } from 'bullmq'
|
|
8
|
+
import type { BullMQOtel } from 'bullmq-otel'
|
|
8
9
|
import type { Redis } from 'ioredis'
|
|
9
10
|
|
|
10
11
|
import type { IBridgeServiceCollection } from '../../services/index.ts'
|
|
11
12
|
import type { WorkerDescription } from './WorkerDescription.ts'
|
|
12
13
|
|
|
13
|
-
type
|
|
14
|
+
export type Xl1TransactionMonitorJobData = { tx: SignedHydratedTransaction }
|
|
14
15
|
|
|
15
|
-
interface
|
|
16
|
+
export interface Xl1TransactionMonitorJobReturn {
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
const name = 'Monitor Submitted XL1 Transaction'
|
|
19
20
|
const queueName = 'xl1-tx-monitor'
|
|
20
|
-
const createWorker = (connection: Redis, services?: IBridgeServiceCollection) => {
|
|
21
|
+
const createWorker = (connection: Redis, telemetry?: BullMQOtel, services?: IBridgeServiceCollection) => {
|
|
21
22
|
const gateway = assertEx(services?.gateway, () => 'gateway service not provided')
|
|
22
23
|
const stateMap = assertEx(services?.xl1TxStateMap, () => 'xl1TxStateMap service not provided')
|
|
23
24
|
|
|
24
25
|
const worker = new Worker(
|
|
25
26
|
queueName,
|
|
26
|
-
async (job: Job<
|
|
27
|
+
async (job: Job<Xl1TransactionMonitorJobData, Xl1TransactionMonitorJobReturn>) => {
|
|
27
28
|
const { tx } = job.data
|
|
28
29
|
// Get the hash of the transaction
|
|
29
30
|
const hash = await PayloadBuilder.hash(tx[0])
|
|
@@ -35,6 +36,7 @@ const createWorker = (connection: Redis, services?: IBridgeServiceCollection) =>
|
|
|
35
36
|
// Check for transaction inclusion on chain
|
|
36
37
|
await job.log(`[${hash}] Checking for XL1 transaction inclusion on chain`)
|
|
37
38
|
const foundTx = await viewer.transactionByHash(submissionHash)
|
|
39
|
+
// const foundTx = await viewer.transaction.byHash(submissionHash)
|
|
38
40
|
|
|
39
41
|
// Transaction found on chain
|
|
40
42
|
if (isDefined(foundTx) && !isNull(foundTx)) {
|
|
@@ -59,7 +61,7 @@ const createWorker = (connection: Redis, services?: IBridgeServiceCollection) =>
|
|
|
59
61
|
await job.log(`[${hash}] Transaction not yet included, retrying later`)
|
|
60
62
|
throw new Error(`[${hash}] Transaction not yet included`)
|
|
61
63
|
},
|
|
62
|
-
{ connection },
|
|
64
|
+
{ connection, telemetry },
|
|
63
65
|
)
|
|
64
66
|
|
|
65
67
|
worker.on('failed', (job, err) => {
|
|
@@ -3,23 +3,23 @@ import { PayloadBuilder } from '@xyo-network/sdk-js'
|
|
|
3
3
|
import type { SignedHydratedTransaction } from '@xyo-network/xl1-sdk'
|
|
4
4
|
import type { Job } from 'bullmq'
|
|
5
5
|
import { Worker } from 'bullmq'
|
|
6
|
+
import type { BullMQOtel } from 'bullmq-otel'
|
|
6
7
|
import type { Redis } from 'ioredis'
|
|
7
8
|
|
|
8
9
|
import type { IBridgeServiceCollection } from '../../services/index.ts'
|
|
9
10
|
import type { WorkerDescription } from './WorkerDescription.ts'
|
|
10
11
|
|
|
11
|
-
type
|
|
12
|
-
|
|
13
|
-
interface ReturnType {}
|
|
12
|
+
export type Xl1TransactionPreparationJobData = { tx: SignedHydratedTransaction }
|
|
13
|
+
export interface Xl1TransactionPreparationJobReturn { preparedTx: SignedHydratedTransaction }
|
|
14
14
|
|
|
15
15
|
const name = 'Prepare XL1 Transaction'
|
|
16
16
|
const queueName = 'xl1-tx-prepare'
|
|
17
|
-
const createWorker = (connection: Redis, services?: IBridgeServiceCollection) => {
|
|
17
|
+
const createWorker = (connection: Redis, telemetry?: BullMQOtel, services?: IBridgeServiceCollection) => {
|
|
18
18
|
const stateMap = assertEx(services?.xl1TxStateMap, () => 'xl1TxStateMap service not provided')
|
|
19
19
|
|
|
20
20
|
const worker = new Worker(
|
|
21
21
|
queueName,
|
|
22
|
-
async (job: Job<
|
|
22
|
+
async (job: Job<Xl1TransactionPreparationJobData, Xl1TransactionPreparationJobReturn>) => {
|
|
23
23
|
const { tx } = job.data
|
|
24
24
|
const hash = await PayloadBuilder.hash(tx[0])
|
|
25
25
|
await job.log(`[${hash}] preparing XL1 transaction`)
|
|
@@ -28,9 +28,9 @@ const createWorker = (connection: Redis, services?: IBridgeServiceCollection) =>
|
|
|
28
28
|
await stateMap.set(hash, { preparedTx })
|
|
29
29
|
await job.log(`[${hash}] stored XL1 preparedTx`)
|
|
30
30
|
await job.log(`[${hash}] prepared XL1 transaction`)
|
|
31
|
-
return {}
|
|
31
|
+
return { preparedTx }
|
|
32
32
|
},
|
|
33
|
-
{ connection },
|
|
33
|
+
{ connection, telemetry },
|
|
34
34
|
)
|
|
35
35
|
|
|
36
36
|
worker.on('failed', (job, err) => {
|
|
@@ -3,27 +3,28 @@ import { PayloadBuilder } from '@xyo-network/sdk-js'
|
|
|
3
3
|
import { type SignedHydratedTransaction } from '@xyo-network/xl1-sdk'
|
|
4
4
|
import type { Job } from 'bullmq'
|
|
5
5
|
import { Worker } from 'bullmq'
|
|
6
|
+
import type { BullMQOtel } from 'bullmq-otel'
|
|
6
7
|
import type { Redis } from 'ioredis'
|
|
7
8
|
|
|
8
9
|
import type { IBridgeServiceCollection, Xl1TxState } from '../../services/index.ts'
|
|
9
10
|
import { submitXl1Transaction } from './util/index.ts'
|
|
10
11
|
import type { WorkerDescription } from './WorkerDescription.ts'
|
|
11
12
|
|
|
12
|
-
type
|
|
13
|
+
export type Xl1TransactionSubmissionJobData = { tx: SignedHydratedTransaction }
|
|
13
14
|
|
|
14
|
-
interface
|
|
15
|
+
export interface Xl1TransactionSubmissionJobReturn {
|
|
15
16
|
submissionHash: Required<Xl1TxState>['submissionHash']
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
const name = 'Submit XL1 Transaction'
|
|
19
20
|
const queueName = 'xl1-tx-submit'
|
|
20
|
-
const createWorker = (connection: Redis, services?: IBridgeServiceCollection) => {
|
|
21
|
+
const createWorker = (connection: Redis, telemetry?: BullMQOtel, services?: IBridgeServiceCollection) => {
|
|
21
22
|
const gateway = assertEx(services?.gateway, () => 'gateway service not provided')
|
|
22
23
|
const stateMap = assertEx(services?.xl1TxStateMap, () => 'xl1TxStateMap service not provided')
|
|
23
24
|
|
|
24
25
|
const worker = new Worker(
|
|
25
26
|
queueName,
|
|
26
|
-
async (job: Job<
|
|
27
|
+
async (job: Job<Xl1TransactionSubmissionJobData, Xl1TransactionSubmissionJobReturn>) => {
|
|
27
28
|
const { tx } = job.data
|
|
28
29
|
// Get the hash of the transaction
|
|
29
30
|
const hash = await PayloadBuilder.hash(tx[0])
|
|
@@ -54,7 +55,7 @@ const createWorker = (connection: Redis, services?: IBridgeServiceCollection) =>
|
|
|
54
55
|
|
|
55
56
|
return { submissionHash }
|
|
56
57
|
},
|
|
57
|
-
{ connection },
|
|
58
|
+
{ connection, telemetry },
|
|
58
59
|
)
|
|
59
60
|
|
|
60
61
|
worker.on('failed', (job, err) => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { BullMQOtel } from 'bullmq-otel'
|
|
1
2
|
import type { Redis } from 'ioredis'
|
|
2
3
|
|
|
3
4
|
import type { IBridgeServiceCollection } from '../../services/index.ts'
|
|
@@ -11,12 +12,12 @@ import {
|
|
|
11
12
|
Xl1TransactionSubmission,
|
|
12
13
|
} from './index.ts'
|
|
13
14
|
|
|
14
|
-
export const createWorkers = (connection: Redis, services: IBridgeServiceCollection) => {
|
|
15
|
-
Xl1ToEthBridgeParent.createWorker(connection)
|
|
16
|
-
Xl1TransactionPreparation.createWorker(connection, services)
|
|
17
|
-
Xl1TransactionSubmission.createWorker(connection, services)
|
|
18
|
-
Xl1TransactionMonitor.createWorker(connection, services)
|
|
19
|
-
EthTransactionPreparation.createWorker(connection, services)
|
|
20
|
-
EthTransactionSubmission.createWorker(connection, services)
|
|
21
|
-
EthTransactionMonitor.createWorker(connection, services)
|
|
15
|
+
export const createWorkers = (connection: Redis, telemetry: BullMQOtel, services: IBridgeServiceCollection) => {
|
|
16
|
+
Xl1ToEthBridgeParent.createWorker(connection, telemetry)
|
|
17
|
+
Xl1TransactionPreparation.createWorker(connection, telemetry, services)
|
|
18
|
+
Xl1TransactionSubmission.createWorker(connection, telemetry, services)
|
|
19
|
+
Xl1TransactionMonitor.createWorker(connection, telemetry, services)
|
|
20
|
+
EthTransactionPreparation.createWorker(connection, telemetry, services)
|
|
21
|
+
EthTransactionSubmission.createWorker(connection, telemetry, services)
|
|
22
|
+
EthTransactionMonitor.createWorker(connection, telemetry, services)
|
|
22
23
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './submitEthTransaction.ts'
|
|
2
2
|
export * from './submitXl1Transaction.ts'
|
|
3
|
-
export * from './
|
|
4
|
-
export * from './
|
|
3
|
+
export * from './validateSufficientLiquiditySourceAllowance.ts'
|
|
4
|
+
export * from './validateSufficientLiquiditySourceBalance.ts'
|
|
5
|
+
export * from './validateSufficientRunnerEthBalanceForGas.ts'
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import type { Promisable } from '@xylabs/sdk-js'
|
|
2
1
|
import { assertEx, hexToBigInt } from '@xylabs/sdk-js'
|
|
3
2
|
import type { BridgeableToken, LiquidityPoolBridge } from '@xyo-network/typechain'
|
|
4
3
|
import { isBridgeIntent, type SignedHydratedTransaction } from '@xyo-network/xl1-sdk'
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
log: (message: string) => Promisable<unknown>
|
|
8
|
-
}
|
|
5
|
+
import type { AsyncLogger } from './AsyncLogger.ts'
|
|
9
6
|
|
|
10
7
|
/**
|
|
11
8
|
* Checks if the liquidity source has sufficient allowance for the bridge to execute the transaction. This doesn't
|
|
@@ -17,11 +14,11 @@ interface IAsyncLogger {
|
|
|
17
14
|
* @param logger Optional logger for asynchronous logging
|
|
18
15
|
* @returns True if the liquidity source allowance is sufficient to execute the bridge
|
|
19
16
|
*/
|
|
20
|
-
export const
|
|
17
|
+
export const validateSufficientLiquiditySourceAllowance = async (
|
|
21
18
|
tx: SignedHydratedTransaction,
|
|
22
19
|
bridgeableToken: BridgeableToken,
|
|
23
20
|
bridge: LiquidityPoolBridge,
|
|
24
|
-
logger?:
|
|
21
|
+
logger?: AsyncLogger,
|
|
25
22
|
) => {
|
|
26
23
|
// Get the amount being bridged from the bridge intent
|
|
27
24
|
const bridgeIntent = assertEx(tx[1].find(isBridgeIntent), () => 'No bridge intent found')
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import type { Promisable } from '@xylabs/sdk-js'
|
|
2
1
|
import { assertEx, hexToBigInt } from '@xylabs/sdk-js'
|
|
3
2
|
import type { BridgeableToken, LiquidityPoolBridge } from '@xyo-network/typechain'
|
|
4
3
|
import { isBridgeIntent, type SignedHydratedTransaction } from '@xyo-network/xl1-sdk'
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
log: (message: string) => Promisable<unknown>
|
|
8
|
-
}
|
|
5
|
+
import type { AsyncLogger } from './AsyncLogger.ts'
|
|
9
6
|
|
|
10
7
|
/**
|
|
11
8
|
* Checks if the liquidity source has sufficient balance for the bridge to execute the transaction. This doesn't
|
|
@@ -17,11 +14,11 @@ interface IAsyncLogger {
|
|
|
17
14
|
* @param logger Optional logger for asynchronous logging
|
|
18
15
|
* @returns True if the liquidity source balance is sufficient to execute the bridge
|
|
19
16
|
*/
|
|
20
|
-
export const
|
|
17
|
+
export const validateSufficientLiquiditySourceBalance = async (
|
|
21
18
|
tx: SignedHydratedTransaction,
|
|
22
19
|
bridgeableToken: BridgeableToken,
|
|
23
20
|
bridge: LiquidityPoolBridge,
|
|
24
|
-
logger?:
|
|
21
|
+
logger?: AsyncLogger,
|
|
25
22
|
) => {
|
|
26
23
|
// Get the amount being bridged from the bridge intent
|
|
27
24
|
const bridgeIntent = assertEx(tx[1].find(isBridgeIntent), () => 'No bridge intent found')
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { assertEx } from '@xylabs/sdk-js'
|
|
2
|
+
import type { ContractTransaction, Wallet } from 'ethers'
|
|
3
|
+
|
|
4
|
+
import type { AsyncLogger } from './AsyncLogger.ts'
|
|
5
|
+
|
|
6
|
+
const DEFAULT_GAS_BUFFER_BPS = 2000n // 20%
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Validates that the wallet (the account that will submit the tx) has enough ETH
|
|
10
|
+
* to cover the estimated gas cost (plus a buffer) for the transaction.
|
|
11
|
+
* @param preparedTx The transaction that is being prepared for submission.
|
|
12
|
+
* This should be a fully populated transaction (to/from/data/value) except for gas fields.
|
|
13
|
+
* @param wallet The wallet that will be used to submit the transaction.
|
|
14
|
+
* @param logger Optional logger for asynchronous logging.
|
|
15
|
+
* @param bufferBps The buffer to apply to the estimated gas cost, in basis points (1/100th of a percent).
|
|
16
|
+
* @returns A boolean indicating whether the wallet has sufficient ETH to cover the estimated gas cost with the buffer applied.
|
|
17
|
+
*/
|
|
18
|
+
export const validateSufficientRunnerEthBalanceForGas = async (
|
|
19
|
+
preparedTx: ContractTransaction,
|
|
20
|
+
wallet: Wallet,
|
|
21
|
+
logger?: AsyncLogger,
|
|
22
|
+
bufferBps: bigint = DEFAULT_GAS_BUFFER_BPS,
|
|
23
|
+
) => {
|
|
24
|
+
const provider = assertEx(wallet.provider, () => 'Wallet provider is not defined')
|
|
25
|
+
|
|
26
|
+
// Fee data (EIP-1559 preferred, gasPrice fallback)
|
|
27
|
+
const feeData = await provider.getFeeData()
|
|
28
|
+
const perGas = feeData.maxFeePerGas ?? feeData.gasPrice
|
|
29
|
+
if (perGas == null) {
|
|
30
|
+
await logger?.log('[gas] unable to resolve gas price / maxFeePerGas from provider')
|
|
31
|
+
return true // don’t hard-fail; provider may not support fee data
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Estimate gas
|
|
35
|
+
const transactionRequest = {
|
|
36
|
+
...preparedTx,
|
|
37
|
+
from: await wallet.getAddress(),
|
|
38
|
+
}
|
|
39
|
+
const estGas = await provider.estimateGas(transactionRequest)
|
|
40
|
+
|
|
41
|
+
// Include any value sent with the tx
|
|
42
|
+
const txValue = preparedTx?.value ?? 0n
|
|
43
|
+
|
|
44
|
+
// Required = (gas * perGas + value) * (1 + buffer)
|
|
45
|
+
const baseRequired = estGas * perGas + txValue
|
|
46
|
+
const required = (baseRequired * (10_000n + bufferBps)) / 10_000n
|
|
47
|
+
|
|
48
|
+
const balance = await provider.getBalance(await wallet.getAddress())
|
|
49
|
+
|
|
50
|
+
await logger?.log(
|
|
51
|
+
`[gas] runner=${await wallet.getAddress()} balance=${balance.toString()} `
|
|
52
|
+
+ `estGas=${estGas.toString()} perGas=${perGas.toString()} value=${txValue.toString()} `
|
|
53
|
+
+ `requiredWithBuffer=${required.toString()} bufferBps=${bufferBps.toString()}`,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return balance >= required
|
|
57
|
+
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { BridgeConfig } from '@xyo-network/chain-orchestration'
|
|
2
2
|
import type { Express } from 'express'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
getConnection, getFlowProducer, getTelemetry,
|
|
6
|
+
} from '../queue/index.ts'
|
|
5
7
|
|
|
6
8
|
export const addFlowProducer = (app: Express, config: BridgeConfig): Express => {
|
|
7
9
|
const connection = getConnection(config)
|
|
8
|
-
const
|
|
10
|
+
const telemetry = getTelemetry()
|
|
11
|
+
const flowProducer = getFlowProducer(connection, telemetry)
|
|
9
12
|
app.flowProducer = flowProducer
|
|
10
13
|
return app
|
|
11
14
|
}
|
package/src/server/addWorkers.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import type { BridgeConfig } from '@xyo-network/chain-orchestration'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createWorkers, getConnection,
|
|
5
|
+
getTelemetry,
|
|
6
|
+
} from '../queue/index.ts'
|
|
4
7
|
import type { IBridgeServiceCollection } from '../services/index.ts'
|
|
5
8
|
|
|
6
9
|
export const addWorkers = (config: BridgeConfig, services: IBridgeServiceCollection) => {
|
|
7
10
|
const connection = getConnection(config)
|
|
8
|
-
|
|
11
|
+
const telemetry = getTelemetry()
|
|
12
|
+
createWorkers(connection, telemetry, services)
|
|
9
13
|
}
|