@xyo-network/chain-bridge 1.17.7 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/dist/node/config/getGateway.d.ts +1 -1
  2. package/dist/node/config/getGateway.d.ts.map +1 -1
  3. package/dist/node/index.mjs +534 -108
  4. package/dist/node/index.mjs.map +1 -1
  5. package/dist/node/interface/service/Observer/ERC20TransferObserver/ERC20TransferObserver.d.ts.map +1 -1
  6. package/dist/node/interface/service/Observer/LiquidityPoolBridgeObserver/LiquidityPoolBridgeObserver.d.ts.map +1 -1
  7. package/dist/node/interface/service/Relay/LiquidityPoolBridgeRelay/LiquidityPoolBridgeRelay.d.ts.map +1 -1
  8. package/dist/node/manifest/getServices.d.ts +13 -0
  9. package/dist/node/manifest/getServices.d.ts.map +1 -0
  10. package/dist/node/manifest/index.d.ts +1 -0
  11. package/dist/node/manifest/index.d.ts.map +1 -1
  12. package/dist/node/modules/EVMLiquidityBridgeTransactionCompletionMonitorSentinel/EVMLiquidityBridgeTransactionCompletionMonitorSentinel.d.ts.map +1 -1
  13. package/dist/node/modules/XL1TransactionCompletionMonitorSentinel/XL1TransactionCompletionMonitorSentinel.d.ts.map +1 -1
  14. package/dist/node/queue/connection.d.ts +4 -0
  15. package/dist/node/queue/connection.d.ts.map +1 -0
  16. package/dist/node/queue/flowProducer.d.ts +4 -0
  17. package/dist/node/queue/flowProducer.d.ts.map +1 -0
  18. package/dist/node/queue/flows/createXl1ToEthBridgeJob.d.ts +4 -0
  19. package/dist/node/queue/flows/createXl1ToEthBridgeJob.d.ts.map +1 -0
  20. package/dist/node/queue/flows/index.d.ts +2 -0
  21. package/dist/node/queue/flows/index.d.ts.map +1 -0
  22. package/dist/node/queue/index.d.ts +5 -0
  23. package/dist/node/queue/index.d.ts.map +1 -0
  24. package/dist/node/queue/workers/WorkerDescription.d.ts +7 -0
  25. package/dist/node/queue/workers/WorkerDescription.d.ts.map +1 -0
  26. package/dist/node/queue/workers/createWorkers.d.ts +3 -0
  27. package/dist/node/queue/workers/createWorkers.d.ts.map +1 -0
  28. package/dist/node/queue/workers/ethTransactionMonitor.d.ts +3 -0
  29. package/dist/node/queue/workers/ethTransactionMonitor.d.ts.map +1 -0
  30. package/dist/node/queue/workers/ethTransactionPreparation.d.ts +5 -0
  31. package/dist/node/queue/workers/ethTransactionPreparation.d.ts.map +1 -0
  32. package/dist/node/queue/workers/ethTransactionSubmission.d.ts +3 -0
  33. package/dist/node/queue/workers/ethTransactionSubmission.d.ts.map +1 -0
  34. package/dist/node/queue/workers/index.d.ts +9 -0
  35. package/dist/node/queue/workers/index.d.ts.map +1 -0
  36. package/dist/node/queue/workers/xl1ToEthBridgeParent.d.ts +3 -0
  37. package/dist/node/queue/workers/xl1ToEthBridgeParent.d.ts.map +1 -0
  38. package/dist/node/queue/workers/xl1TransactionMonitor.d.ts +3 -0
  39. package/dist/node/queue/workers/xl1TransactionMonitor.d.ts.map +1 -0
  40. package/dist/node/queue/workers/xl1TransactionPreparation.d.ts +3 -0
  41. package/dist/node/queue/workers/xl1TransactionPreparation.d.ts.map +1 -0
  42. package/dist/node/queue/workers/xl1TransactionSubmission.d.ts +3 -0
  43. package/dist/node/queue/workers/xl1TransactionSubmission.d.ts.map +1 -0
  44. package/dist/node/server/app.d.ts.map +1 -1
  45. package/dist/node/server/flowProducer.d.ts +4 -0
  46. package/dist/node/server/flowProducer.d.ts.map +1 -0
  47. package/dist/node/server/index.d.ts +4 -0
  48. package/dist/node/server/index.d.ts.map +1 -1
  49. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemote.d.ts.map +1 -1
  50. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteStatus.d.ts.map +1 -1
  51. package/dist/node/server/server.d.ts.map +1 -1
  52. package/dist/node/services/EthTxState.d.ts +5 -0
  53. package/dist/node/services/EthTxState.d.ts.map +1 -0
  54. package/dist/node/services/IBridgeServiceCollection.d.ts +17 -0
  55. package/dist/node/services/IBridgeServiceCollection.d.ts.map +1 -0
  56. package/dist/node/services/TxState.d.ts +22 -0
  57. package/dist/node/services/TxState.d.ts.map +1 -0
  58. package/dist/node/services/Xl1TxState.d.ts +6 -0
  59. package/dist/node/services/Xl1TxState.d.ts.map +1 -0
  60. package/dist/node/services/index.d.ts +5 -0
  61. package/dist/node/services/index.d.ts.map +1 -0
  62. package/dist/node/util/generateBridgeEstimate.d.ts.map +1 -1
  63. package/dist/node/util/validateBridgeTransaction.d.ts.map +1 -1
  64. package/package.json +19 -18
  65. package/src/config/getGateway.ts +10 -3
  66. package/src/interface/service/Observer/ERC20TransferObserver/ERC20TransferObserver.ts +1 -1
  67. package/src/interface/service/Observer/LiquidityPoolBridgeObserver/LiquidityPoolBridgeObserver.ts +1 -2
  68. package/src/interface/service/Relay/LiquidityPoolBridgeRelay/LiquidityPoolBridgeRelay.ts +1 -1
  69. package/src/manifest/getLocator.ts +4 -4
  70. package/src/manifest/getServices.ts +102 -0
  71. package/src/manifest/index.ts +1 -0
  72. package/src/modules/EVMLiquidityBridgeTransactionCompletionMonitorSentinel/EVMLiquidityBridgeTransactionCompletionMonitorSentinel.ts +1 -1
  73. package/src/modules/XL1TransactionCompletionMonitorSentinel/XL1TransactionCompletionMonitorSentinel.ts +1 -1
  74. package/src/queue/connection.ts +16 -0
  75. package/src/queue/flowProducer.ts +11 -0
  76. package/src/queue/flows/createXl1ToEthBridgeJob.ts +60 -0
  77. package/src/queue/flows/index.ts +1 -0
  78. package/src/queue/index.ts +4 -0
  79. package/src/queue/workers/WorkerDescription.ts +7 -0
  80. package/src/queue/workers/createWorkers.ts +15 -0
  81. package/src/queue/workers/ethTransactionMonitor.ts +42 -0
  82. package/src/queue/workers/ethTransactionPreparation.ts +47 -0
  83. package/src/queue/workers/ethTransactionSubmission.ts +77 -0
  84. package/src/queue/workers/index.ts +8 -0
  85. package/src/queue/workers/xl1ToEthBridgeParent.ts +25 -0
  86. package/src/queue/workers/xl1TransactionMonitor.ts +66 -0
  87. package/src/queue/workers/xl1TransactionPreparation.ts +36 -0
  88. package/src/queue/workers/xl1TransactionSubmission.ts +60 -0
  89. package/src/server/app.ts +2 -0
  90. package/src/server/flowProducer.ts +11 -0
  91. package/src/server/index.ts +5 -0
  92. package/src/server/routes/bridge/routeDefinitions/routes/bridgeToRemote.ts +6 -14
  93. package/src/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteStatus.ts +1 -0
  94. package/src/server/server.ts +2 -1
  95. package/src/services/EthTxState.ts +5 -0
  96. package/src/services/IBridgeServiceCollection.ts +18 -0
  97. package/src/services/TxState.ts +32 -0
  98. package/src/services/Xl1TxState.ts +6 -0
  99. package/src/services/index.ts +4 -0
  100. package/src/util/generateBridgeEstimate.ts +1 -2
  101. package/src/util/validateBridgeTransaction.ts +5 -0
  102. package/dist/node/driver/index.d.ts +0 -2
  103. package/dist/node/driver/index.d.ts.map +0 -1
  104. package/dist/node/driver/indexer/ChainBlockIteration/ChainHashIterationService.d.ts +0 -24
  105. package/dist/node/driver/indexer/ChainBlockIteration/ChainHashIterationService.d.ts.map +0 -1
  106. package/dist/node/driver/indexer/ChainBlockIteration/index.d.ts +0 -2
  107. package/dist/node/driver/indexer/ChainBlockIteration/index.d.ts.map +0 -1
  108. package/dist/node/driver/indexer/ChainBlocksObservable.d.ts +0 -6
  109. package/dist/node/driver/indexer/ChainBlocksObservable.d.ts.map +0 -1
  110. package/dist/node/driver/indexer/ChainHydratedBlocksObservable.d.ts +0 -11
  111. package/dist/node/driver/indexer/ChainHydratedBlocksObservable.d.ts.map +0 -1
  112. package/dist/node/driver/indexer/index.d.ts +0 -2
  113. package/dist/node/driver/indexer/index.d.ts.map +0 -1
  114. package/src/driver/index.ts +0 -1
  115. package/src/driver/indexer/ChainBlockIteration/ChainHashIterationService.ts +0 -89
  116. package/src/driver/indexer/ChainBlockIteration/index.ts +0 -1
  117. package/src/driver/indexer/ChainBlocksObservable.ts +0 -46
  118. package/src/driver/indexer/ChainHydratedBlocksObservable.ts +0 -23
  119. package/src/driver/indexer/index.ts +0 -1
@@ -0,0 +1,102 @@
1
+ import { BaseMongoSdk, type BaseMongoSdkPrivateConfig } from '@xylabs/mongo'
2
+ import type { Hash, Logger } from '@xylabs/sdk-js'
3
+ import { assertEx, isDefined } from '@xylabs/sdk-js'
4
+ import { MongoMap } from '@xyo-network/chain-protocol'
5
+ import { initEvmProvider } from '@xyo-network/chain-services'
6
+ import { LiquidityPoolBridge__factory } from '@xyo-network/typechain'
7
+ import { HDWallet } from '@xyo-network/wallet'
8
+ import type {
9
+ Config, IterableMap, XyoGatewayRunner,
10
+ } from '@xyo-network/xl1-protocol-sdk'
11
+ import {
12
+ hasMongoConfig, mapToMapType, SimpleXyoGatewayRunner, SimpleXyoSigner,
13
+ XyoConnectionMoniker,
14
+ } from '@xyo-network/xl1-protocol-sdk'
15
+ import type { SimpleXyoConnection } from '@xyo-network/xl1-providers'
16
+ import { buildJsonRpcProviderLocator } from '@xyo-network/xl1-providers'
17
+ import type { RpcSchemaMap, TransportFactory } from '@xyo-network/xl1-rpc'
18
+ import { HttpRpcTransport } from '@xyo-network/xl1-rpc'
19
+ import { getAddress, Wallet } from 'ethers'
20
+ import type { Document } from 'mongodb'
21
+
22
+ import type {
23
+ EthTxState, IBridgeServiceCollection, Xl1TxState,
24
+ } from '../services/index.ts'
25
+
26
+ export interface GetServicesContext {
27
+ config: Config
28
+ logger?: Logger
29
+ }
30
+
31
+ /**
32
+ * Used for retrieving a service collection
33
+ * @returns A service collection
34
+ */
35
+ export const getServices = async (context: GetServicesContext): Promise<IBridgeServiceCollection> => {
36
+ const { config } = context
37
+
38
+ const gateway = await getGateway(config)
39
+ const ethTxStateMap = await getIterableMap<Hash, EthTxState>(config, 'liquidity_bridge_xl1_to_eth_eth_tx_state')
40
+ const xl1TxStateMap = await getIterableMap<Hash, Xl1TxState>(config, 'liquidity_bridge_xl1_to_eth_xl1_tx_state')
41
+ const provider = await initEvmProvider({ config })
42
+ // TODO: Get from config
43
+ const {
44
+ remoteBridgeContractAddress, remoteChainWalletPrivateKey, mnemonic,
45
+ } = config.bridge
46
+ const account = isDefined(mnemonic) ? await HDWallet.fromPhrase(mnemonic) : await HDWallet.random()
47
+ const wallet = new Wallet(remoteChainWalletPrivateKey, provider)
48
+ const bridge = LiquidityPoolBridge__factory.connect(getAddress(remoteBridgeContractAddress), wallet)
49
+ // Assert we are contract owner so we can call ownable methods
50
+ const bridgeOwner = await bridge.owner()
51
+ assertEx(bridgeOwner.toLowerCase() === wallet.address.toLowerCase(), () => 'Wallet is not the owner of the bridge contract')
52
+ return {
53
+ account,
54
+ bridge,
55
+ ethTxStateMap,
56
+ gateway,
57
+ provider,
58
+ wallet,
59
+ xl1TxStateMap,
60
+ }
61
+ }
62
+
63
+ const getGateway = async (
64
+ config: Config,
65
+ ): Promise<XyoGatewayRunner> => {
66
+ const { mnemonic, chainRpcApiUrl: endpoint } = config.bridge
67
+ const walletPromise = isDefined(mnemonic) ? HDWallet.fromPhrase(mnemonic) : HDWallet.random()
68
+ const account = await walletPromise
69
+ const signer = new SimpleXyoSigner(account)
70
+ const transportFactory: TransportFactory = (schemas: RpcSchemaMap) => new HttpRpcTransport(endpoint, schemas)
71
+ const locator = await buildJsonRpcProviderLocator({ transportFactory })
72
+ const connection = await locator.getInstance<SimpleXyoConnection>(XyoConnectionMoniker)
73
+ const gateway = new SimpleXyoGatewayRunner(connection, signer)
74
+ return gateway
75
+ }
76
+
77
+ /**
78
+ * Gets an iterable map based on the storage instructions in the config
79
+ * @param config The config containing storage instructions
80
+ * @param collection The collection to use if using MongoDB for persistence
81
+ * @returns The iterable map
82
+ */
83
+ const getIterableMap = async <K extends {} = string, V extends Document = Document>
84
+ (config: Config, collection: string): Promise<IterableMap<K, V>> => {
85
+ const mongoConfig = config.storage?.mongo
86
+ if (hasMongoConfig(mongoConfig)) {
87
+ const {
88
+ connectionString: dbConnectionString, database: dbName, domain: dbDomain, password: dbPassword, username: dbUserName,
89
+ } = mongoConfig
90
+ const payloadSdkConfig: BaseMongoSdkPrivateConfig = {
91
+ dbConnectionString, dbDomain, dbName, dbPassword, dbUserName,
92
+ }
93
+ const sdkBalanceSummaryMap = new BaseMongoSdk<V>({ ...payloadSdkConfig, collection })
94
+ const result = await MongoMap.create<MongoMap<K, V>>({
95
+ sdk: sdkBalanceSummaryMap,
96
+ getCache: { enabled: true, maxEntries: 5000 },
97
+ })
98
+ return result
99
+ } else {
100
+ return mapToMapType(new Map<K, V>())
101
+ }
102
+ }
@@ -1,5 +1,6 @@
1
1
  export * from './getLocator.ts'
2
2
  export * from './getNode.ts'
3
+ export * from './getServices.ts'
3
4
  export * from './nodeManifest.ts'
4
5
  export * from './private/index.ts'
5
6
  export * from './public/index.ts'
@@ -109,7 +109,7 @@ export class EVMLiquidityBridgeTransactionCompletionMonitorSentinel<
109
109
  return assertEx(this._wallet, () => 'wallet is required')
110
110
  }
111
111
 
112
- override async createHandler(): Promise<void> {
112
+ override async createHandler() {
113
113
  await super.createHandler()
114
114
  // Create meters for tracking bridge attempts, successes, and errors.
115
115
  this._attemptsCounter = this.meter?.createCounter(
@@ -88,7 +88,7 @@ export class XL1TransactionCompletionMonitorSentinel<
88
88
  return assertEx(this.params.viewer, () => 'Viewer is not defined in params')
89
89
  }
90
90
 
91
- override async createHandler(): Promise<void> {
91
+ override async createHandler() {
92
92
  await super.createHandler()
93
93
  // Create meters for tracking bridge attempts, successes, and errors.
94
94
  this._attemptsCounter = this.meter?.createCounter('xl1_transaction_completion_monitor_sentinel_attempts_total', { description: 'Number of attempts' })
@@ -0,0 +1,16 @@
1
+ import { isDefined } from '@xylabs/sdk-js'
2
+ import type { Config } from '@xyo-network/xl1-protocol-sdk'
3
+ import { Redis } from 'ioredis'
4
+
5
+ let connection: Redis | undefined
6
+
7
+ const maxRetriesPerRequest = null
8
+
9
+ export const getConnection = (config: Config) => {
10
+ if (isDefined(connection)) return connection
11
+ const { redisHost: host, redisPort: port } = config.bridge
12
+ connection = new Redis({
13
+ host, port, maxRetriesPerRequest,
14
+ })
15
+ return connection
16
+ }
@@ -0,0 +1,11 @@
1
+ import { isDefined } from '@xylabs/sdk-js'
2
+ import { FlowProducer } from 'bullmq'
3
+ import type { Redis } from 'ioredis'
4
+
5
+ let flowProducer: FlowProducer | undefined
6
+
7
+ export const getFlowProducer = (connection: Redis) => {
8
+ if (isDefined(flowProducer)) return flowProducer
9
+ flowProducer = new FlowProducer({ connection })
10
+ return flowProducer
11
+ }
@@ -0,0 +1,60 @@
1
+ import type { SignedHydratedTransaction } from '@xyo-network/xl1-protocol'
2
+ import type { FlowProducer } from 'bullmq'
3
+
4
+ import {
5
+ ethTransactionMonitor, ethTransactionPreparation, ethTransactionSubmission, xl1ToEthBridgeParent, xl1TransactionMonitor, xl1TransactionPreparation,
6
+ xl1TransactionSubmission,
7
+ } from '../workers/index.ts'
8
+
9
+ export const createXl1ToEthBridgeJob = async (flowProducer: FlowProducer, tx: SignedHydratedTransaction) => {
10
+ const flow = await flowProducer.add({
11
+ name: xl1ToEthBridgeParent.name,
12
+ queueName: xl1ToEthBridgeParent.queueName,
13
+ data: { tx },
14
+ children: [
15
+ {
16
+ name: xl1TransactionPreparation.name,
17
+ queueName: xl1TransactionPreparation.queueName,
18
+ data: { tx },
19
+ children: [
20
+ {
21
+ name: xl1TransactionSubmission.name,
22
+ queueName: xl1TransactionSubmission.queueName,
23
+ data: { tx },
24
+ children: [
25
+ {
26
+ name: xl1TransactionMonitor.name,
27
+ queueName: xl1TransactionMonitor.queueName,
28
+ data: { tx },
29
+ opts: { attempts: 60, backoff: { type: 'fixed', delay: 5000 } },
30
+ children: [
31
+ {
32
+ name: ethTransactionPreparation.name,
33
+ queueName: ethTransactionPreparation.queueName,
34
+ data: { tx },
35
+ children: [
36
+ {
37
+ name: ethTransactionSubmission.name,
38
+ queueName: ethTransactionSubmission.queueName,
39
+ data: { tx },
40
+ children: [
41
+ {
42
+ name: ethTransactionMonitor.name,
43
+ queueName: ethTransactionMonitor.queueName,
44
+ data: { tx },
45
+ opts: { attempts: 60, backoff: { type: 'fixed', delay: 5000 } },
46
+ },
47
+ ],
48
+ },
49
+ ],
50
+ },
51
+ ],
52
+ },
53
+ ],
54
+ },
55
+ ],
56
+ },
57
+ ],
58
+ })
59
+ return flow
60
+ }
@@ -0,0 +1 @@
1
+ export * from './createXl1ToEthBridgeJob.ts'
@@ -0,0 +1,4 @@
1
+ export * from './connection.ts'
2
+ export * from './flowProducer.ts'
3
+ export * from './flows/index.ts'
4
+ export * from './workers/index.ts'
@@ -0,0 +1,7 @@
1
+ import type { Redis } from 'ioredis'
2
+
3
+ export interface WorkerDescription {
4
+ createWorker: (connection: Redis) => void
5
+ name: string
6
+ queueName: string
7
+ }
@@ -0,0 +1,15 @@
1
+ import type { Redis } from 'ioredis'
2
+
3
+ import {
4
+ ethTransactionMonitor, ethTransactionSubmission, xl1ToEthBridgeParent, xl1TransactionMonitor, xl1TransactionSubmission,
5
+ } from './index.ts'
6
+
7
+ export const createWorkers = (connection: Redis) => {
8
+ return [
9
+ xl1ToEthBridgeParent.createWorker(connection),
10
+ xl1TransactionSubmission.createWorker(connection),
11
+ xl1TransactionMonitor.createWorker(connection),
12
+ ethTransactionSubmission.createWorker(connection),
13
+ ethTransactionMonitor.createWorker(connection),
14
+ ]
15
+ }
@@ -0,0 +1,42 @@
1
+ import { assertEx } from '@xylabs/sdk-js'
2
+ import { PayloadBuilder } from '@xyo-network/payload-builder'
3
+ import type { SignedHydratedTransaction } from '@xyo-network/xl1-protocol'
4
+ import type { Job } from 'bullmq'
5
+ import { Worker } from 'bullmq'
6
+ import type { Redis } from 'ioredis'
7
+
8
+ import type { IBridgeServiceCollection } from '../../services/index.ts'
9
+ import type { WorkerDescription } from './WorkerDescription.ts'
10
+
11
+ type JobData = Pick<IBridgeServiceCollection, 'bridge' | 'ethTxStateMap' | 'provider' | 'wallet'> & { tx: SignedHydratedTransaction }
12
+ interface ReturnType {
13
+ blockHash: string
14
+ blockNumber: number
15
+ }
16
+
17
+ const name = 'Monitor Submitted ETH Transaction'
18
+ const queueName = 'eth-tx-monitor'
19
+ const createWorker = (connection: Redis) => {
20
+ new Worker(
21
+ queueName,
22
+ async (job: Job<JobData, ReturnType>) => {
23
+ const {
24
+ provider, ethTxStateMap: stateMap, tx,
25
+ } = job.data
26
+ const hash = await PayloadBuilder.hash(tx[0])
27
+ const state = assertEx(await stateMap.get(hash), () => 'State not found')
28
+ const submissionHash = assertEx(state?.submissionHash, () => 'submissionHash not found')
29
+ const receipt = assertEx(await provider.getTransactionReceipt(submissionHash), () => 'Transaction receipt not found')
30
+ await job.log(`[${hash}] confirmed ETH tx ${submissionHash} in block ${receipt.blockNumber}`)
31
+ const { blockHash, blockNumber } = receipt
32
+ state.confirmationHash = blockHash
33
+ await stateMap.set(hash, state)
34
+ return { blockHash, blockNumber }
35
+ },
36
+ { connection },
37
+ )
38
+ }
39
+
40
+ export const ethTransactionMonitor: WorkerDescription = {
41
+ createWorker, name, queueName,
42
+ }
@@ -0,0 +1,47 @@
1
+ import { assertEx, hexToBigInt } from '@xylabs/sdk-js'
2
+ import { PayloadBuilder } from '@xyo-network/payload-builder'
3
+ import { isBridgeIntent, type SignedHydratedTransaction } from '@xyo-network/xl1-protocol'
4
+ import type { Job } from 'bullmq'
5
+ import { Worker } from 'bullmq'
6
+ import { getAddress } from 'ethers'
7
+ import type { Redis } from 'ioredis'
8
+
9
+ import type { IBridgeServiceCollection } from '../../services/index.ts'
10
+ import type { WorkerDescription } from './WorkerDescription.ts'
11
+
12
+ type JobData = Pick<IBridgeServiceCollection, 'bridge' | 'ethTxStateMap'> & { tx: SignedHydratedTransaction }
13
+
14
+ interface ReturnType {}
15
+
16
+ const name = 'Prepare ETH Transaction'
17
+ const queueName = 'eth-tx-prepare'
18
+
19
+ export const createWorker = (connection: Redis) => {
20
+ new Worker(
21
+ queueName,
22
+ async (job: Job<JobData, ReturnType>) => {
23
+ const {
24
+ bridge, ethTxStateMap: stateMap, tx,
25
+ } = job.data
26
+ const hash = await PayloadBuilder.hash(tx[0])
27
+ await job.log(`[${hash}] preparing ETH transaction`)
28
+ await job.log(`[${hash}] building ETH transaction`)
29
+ const bridgeIntent = assertEx(tx[1].find(isBridgeIntent), () => 'No bridge intent found')
30
+ const amount = hexToBigInt(bridgeIntent.destAmount)
31
+ const srcAddress = getAddress(bridgeIntent.srcAddress)
32
+ const destAddress = getAddress(bridgeIntent.destAddress)
33
+ const preparedTx = await bridge.getFunction('bridgeFromRemote').populateTransaction(srcAddress, destAddress, amount)
34
+ await job.log(`[${hash}] built ETH transaction`)
35
+ await job.log(`[${hash}] storing ETH preparedTx`)
36
+ await stateMap.set(hash, { preparedTx })
37
+ await job.log(`[${hash}] stored ETH preparedTx`)
38
+ await job.log(`[${hash}] prepared ETH transaction`)
39
+ return {}
40
+ },
41
+ { connection },
42
+ )
43
+ }
44
+
45
+ export const ethTransactionPreparation: WorkerDescription = {
46
+ createWorker, name, queueName,
47
+ }
@@ -0,0 +1,77 @@
1
+ import { assertEx, isDefined } from '@xylabs/sdk-js'
2
+ import { PayloadBuilder } from '@xyo-network/payload-builder'
3
+ import { type SignedHydratedTransaction } from '@xyo-network/xl1-protocol'
4
+ import type { Job } from 'bullmq'
5
+ import { Worker } from 'bullmq'
6
+ import type { Redis } from 'ioredis'
7
+
8
+ import type { EthTxState, IBridgeServiceCollection } from '../../services/index.ts'
9
+ import type { WorkerDescription } from './WorkerDescription.ts'
10
+
11
+ type JobData = Pick<IBridgeServiceCollection, 'bridge' | 'ethTxStateMap' | 'wallet'> & { tx: SignedHydratedTransaction }
12
+
13
+ interface ReturnType {
14
+ submissionHash: Required<EthTxState>['submissionHash']
15
+ }
16
+
17
+ const name = 'Submit ETH Transaction'
18
+ const queueName = 'eth-tx-submit'
19
+ const createWorker = (connection: Redis) => {
20
+ new Worker(
21
+ queueName,
22
+ async (job: Job<JobData, ReturnType>) => {
23
+ const {
24
+ ethTxStateMap: stateMap, tx, wallet,
25
+ } = job.data
26
+ const hash = await PayloadBuilder.hash(tx[0])
27
+ const state = assertEx(await stateMap.get(hash), () => `[${hash}] state not found`)
28
+ const preparedTx = assertEx(state?.preparedTx, () => `[${hash}] preparedTx not found`)
29
+
30
+ // Idempotency check against resubmission
31
+ const { submissionHash: existingSubmissionHash } = state
32
+ if (isDefined(existingSubmissionHash)) {
33
+ await job.log(`[${hash}] Tx already submitted with submission response hash ${existingSubmissionHash}`)
34
+ return { submissionHash: existingSubmissionHash }
35
+ }
36
+
37
+ // Submit the transaction to the Ethereum network
38
+ await job.log(`[${hash}] Submitting ETH tx`)
39
+ const submissionResponse = await wallet.sendTransaction(preparedTx)
40
+ const submissionHash = submissionResponse.hash
41
+ await job.log(`[${hash}] Submitted ETH tx and received submission response hash ${submissionHash}`)
42
+
43
+ // Store the submission hash in the state
44
+ await job.log(`[${hash}] Storing ETH submissionHash`)
45
+ state.submissionHash = submissionHash
46
+ await stateMap.set(hash, state)
47
+ await job.log(`[${hash}] Stored ETH submissionHash`)
48
+
49
+ return { submissionHash }
50
+ // const hash = await PayloadBuilder.hash(tx[0])
51
+ // await job.log(`[${hash}] Obtaining bridge intent from tx`)
52
+ // const bridgeIntent = assertEx(tx[1].find(isBridgeIntent), () => 'No bridge intent found in transaction payload')
53
+ // await job.log(`[${hash}] submitting ETH transaction`)
54
+ // const amount = hexToBigInt(bridgeIntent.destAmount)
55
+ // const srcAddress = getAddress(bridgeIntent.srcAddress)
56
+ // const destAddress = getAddress(bridgeIntent.destAddress)
57
+ // const tx = await bridge.bridgeFromRemote(srcAddress, destAddress, amount)
58
+ // // const nonce = await wallet.getNonce()
59
+ // // const tx = await bridge.bridgeFromRemote(srcAddress, destAddress, amount, { nonce })
60
+ // await job.log(`[${hash}] submitted ETH transaction`)
61
+ // const confirmation = await tx.wait()
62
+ // const transactionResponse = await confirmation?.getTransaction()
63
+ // const destConfirmation = asHex(transactionResponse?.hash ?? '', true)
64
+ // const block = await transactionResponse?.getBlock()
65
+ // await job.log(`[${hash}] confirmed ETH transaction with hash ${destConfirmation} in block ${block?.number}`)
66
+ // const { schema, ...rest } = bridgeIntent
67
+ // const result: BridgeDestinationObservation = new PayloadBuilder<BridgeDestinationObservation>({ schema: BridgeDestinationObservationSchema })
68
+ // .fields({ ...rest, destConfirmation }).build()
69
+ // return result
70
+ },
71
+ { connection, concurrency: 1 },
72
+ )
73
+ }
74
+
75
+ export const ethTransactionSubmission: WorkerDescription = {
76
+ createWorker, name, queueName,
77
+ }
@@ -0,0 +1,8 @@
1
+ export * from './createWorkers.ts'
2
+ export * from './ethTransactionMonitor.ts'
3
+ export * from './ethTransactionPreparation.ts'
4
+ export * from './ethTransactionSubmission.ts'
5
+ export * from './xl1ToEthBridgeParent.ts'
6
+ export * from './xl1TransactionMonitor.ts'
7
+ export * from './xl1TransactionPreparation.ts'
8
+ export * from './xl1TransactionSubmission.ts'
@@ -0,0 +1,25 @@
1
+ import type { Job } from 'bullmq'
2
+ import { Worker } from 'bullmq'
3
+ import type { Redis } from 'ioredis'
4
+
5
+ import type { WorkerDescription } from './WorkerDescription.ts'
6
+
7
+ const name = 'Bridge XL1 to Ethereum'
8
+ const queueName = 'xl1-to-eth-bridge'
9
+ const createWorker = (connection: Redis) => {
10
+ new Worker(
11
+ queueName,
12
+ async (job: Job) => {
13
+ await job.log(`[${job.name}] start`)
14
+ // Parent job has no work other than waiting on children
15
+ await job.log(`[${job.name}] done`)
16
+ // const values = await job.getChildrenValues()
17
+ return { ok: true }
18
+ },
19
+ { connection },
20
+ )
21
+ }
22
+
23
+ export const xl1ToEthBridgeParent: WorkerDescription = {
24
+ createWorker, name, queueName,
25
+ }
@@ -0,0 +1,66 @@
1
+ import {
2
+ assertEx, isDefined, isNull,
3
+ } from '@xylabs/sdk-js'
4
+ import { PayloadBuilder } from '@xyo-network/payload-builder'
5
+ import type { SignedHydratedTransaction } from '@xyo-network/xl1-protocol'
6
+ import type { Job } from 'bullmq'
7
+ import { UnrecoverableError, Worker } from 'bullmq'
8
+ import type { Redis } from 'ioredis'
9
+
10
+ import type { IBridgeServiceCollection } from '../../services/index.ts'
11
+ import type { WorkerDescription } from './WorkerDescription.ts'
12
+
13
+ type JobData = Pick<IBridgeServiceCollection, 'gateway' | 'xl1TxStateMap'> & { tx: SignedHydratedTransaction }
14
+
15
+ interface ReturnType {
16
+ }
17
+
18
+ const name = 'Monitor Submitted XL1 Transaction'
19
+ const queueName = 'xl1-tx-monitor'
20
+ const createWorker = (connection: Redis) => {
21
+ new Worker(
22
+ queueName,
23
+ async (job: Job<JobData, ReturnType>) => {
24
+ const {
25
+ gateway, tx, xl1TxStateMap: stateMap,
26
+ } = job.data
27
+ // Get the hash of the transaction
28
+ const hash = await PayloadBuilder.hash(tx[0])
29
+ // Get the state of the transaction
30
+ const viewer = assertEx(gateway.connectionInstance.viewer, () => `[${hash}] viewer not defined on gateway`)
31
+ const state = assertEx(await stateMap.get(hash), () => `[${hash}] state not found`)
32
+
33
+ // Check for transaction inclusion on chain
34
+ await job.log(`[${hash}] Checking for XL1 transaction inclusion on chain`)
35
+ const foundTx = await viewer.transactionByHash(hash)
36
+
37
+ // Transaction found on chain
38
+ if (isDefined(foundTx) && !isNull(foundTx)) {
39
+ await job.log(`[${hash}] Found transaction on chain`)
40
+ // TODO: For now we just store the same hash as TX hash until we have block hash confirmation capability
41
+ state.confirmationHash = hash
42
+ await stateMap.set(hash, state)
43
+ return {}
44
+ }
45
+
46
+ // Check for transaction expiration
47
+ const currentBlockNumber = await viewer.currentBlockNumber()
48
+ if (tx[0].exp < currentBlockNumber) {
49
+ await job.log(
50
+ `[${hash}] Transaction expired at block ${tx[0].exp}, current block ${currentBlockNumber}`,
51
+ )
52
+ // Throw unrecoverable error to stop retries
53
+ throw new UnrecoverableError(`[${hash}] Transaction expired and will never be included`)
54
+ }
55
+
56
+ // Transaction not yet found, but still valid — retry later
57
+ await job.log(`[${hash}] Transaction not yet included, retrying later`)
58
+ throw new Error(`[${hash}] Transaction not yet included`)
59
+ },
60
+ { connection },
61
+ )
62
+ }
63
+
64
+ export const xl1TransactionMonitor: WorkerDescription = {
65
+ createWorker, name, queueName,
66
+ }
@@ -0,0 +1,36 @@
1
+ import { PayloadBuilder } from '@xyo-network/payload-builder'
2
+ import type { SignedHydratedTransaction } from '@xyo-network/xl1-protocol'
3
+ import type { Job } from 'bullmq'
4
+ import { Worker } from 'bullmq'
5
+ import type { Redis } from 'ioredis'
6
+
7
+ import type { IBridgeServiceCollection } from '../../services/index.ts'
8
+ import type { WorkerDescription } from './WorkerDescription.ts'
9
+
10
+ type JobData = Pick<IBridgeServiceCollection, 'xl1TxStateMap'> & { tx: SignedHydratedTransaction }
11
+
12
+ interface ReturnType {}
13
+
14
+ const name = 'Prepare XL1 Transaction'
15
+ const queueName = 'xl1-tx-prepare'
16
+ const createWorker = (connection: Redis) => {
17
+ new Worker(
18
+ queueName,
19
+ async (job: Job<JobData, ReturnType>) => {
20
+ const { tx, xl1TxStateMap: stateMap } = job.data
21
+ const hash = await PayloadBuilder.hash(tx[0])
22
+ await job.log(`[${hash}] preparing XL1 transaction`)
23
+ const preparedTx = tx
24
+ await job.log(`[${hash}] storing XL1 preparedTx`)
25
+ await stateMap.set(hash, { preparedTx })
26
+ await job.log(`[${hash}] stored XL1 preparedTx`)
27
+ await job.log(`[${hash}] prepared XL1 transaction`)
28
+ return {}
29
+ },
30
+ { connection },
31
+ )
32
+ }
33
+
34
+ export const xl1TransactionPreparation: WorkerDescription = {
35
+ createWorker, name, queueName,
36
+ }
@@ -0,0 +1,60 @@
1
+ import { assertEx, isDefined } from '@xylabs/sdk-js'
2
+ import { PayloadBuilder } from '@xyo-network/payload-builder'
3
+ import type { SignedHydratedTransaction } from '@xyo-network/xl1-protocol'
4
+ import type { Job } from 'bullmq'
5
+ import { Worker } from 'bullmq'
6
+ import type { Redis } from 'ioredis'
7
+
8
+ import type { IBridgeServiceCollection, Xl1TxState } from '../../services/index.ts'
9
+ import type { WorkerDescription } from './WorkerDescription.ts'
10
+
11
+ type JobData = Pick<IBridgeServiceCollection, 'gateway' | 'xl1TxStateMap'> & { tx: SignedHydratedTransaction }
12
+
13
+ interface ReturnType {
14
+ submissionHash: Required<Xl1TxState>['submissionHash']
15
+ }
16
+
17
+ const name = 'Submit XL1 Transaction'
18
+ const queueName = 'xl1-tx-submit'
19
+ const createWorker = (connection: Redis) => {
20
+ new Worker(
21
+ queueName,
22
+ async (job: Job<JobData, ReturnType>) => {
23
+ const {
24
+ gateway, tx, xl1TxStateMap: stateMap,
25
+ } = job.data
26
+ // Get the hash of the transaction
27
+ const hash = await PayloadBuilder.hash(tx[0])
28
+ // Get the state of the transaction
29
+ const state = assertEx(await stateMap.get(hash), () => `[${hash}] state not found`)
30
+ const preparedTx = assertEx(state?.preparedTx, () => `[${hash}] preparedTx not found`)
31
+
32
+ // Idempotency check against resubmission
33
+ const { submissionHash: existingSubmissionHash } = state
34
+ if (isDefined(existingSubmissionHash)) {
35
+ await job.log(`[${hash}] Tx already submitted with submission response hash ${existingSubmissionHash}`)
36
+ return { submissionHash: existingSubmissionHash }
37
+ }
38
+
39
+ // Submit the transaction to the XL1 network
40
+ await job.log(`[${hash}] Submitting XL1 tx`)
41
+ const [submissionHash] = await gateway.addTransactionToChain(preparedTx)
42
+ // Ensure the submission hash matches the expected hash
43
+ assertEx(submissionHash === hash, () => `[${hash}] Submitted transaction hash ${submissionHash} does not match expected hash`)
44
+ await job.log(`[${hash}] Submitted XL1 tx`)
45
+
46
+ // Store the submission hash in the state
47
+ await job.log(`[${hash}] Storing XL1 submissionHash`)
48
+ state.submissionHash = submissionHash
49
+ await stateMap.set(hash, state)
50
+ await job.log(`[${hash}] Stored XL1 submissionHash`)
51
+
52
+ return { submissionHash }
53
+ },
54
+ { connection },
55
+ )
56
+ }
57
+
58
+ export const xl1TransactionSubmission: WorkerDescription = {
59
+ createWorker, name, queueName,
60
+ }
package/src/server/app.ts CHANGED
@@ -9,6 +9,7 @@ import cors from 'cors'
9
9
  import type { Express } from 'express'
10
10
  import express from 'express'
11
11
 
12
+ import { addFlowProducer } from './flowProducer.ts'
12
13
  import { addInstrumentation } from './instrumentation.ts'
13
14
  import { addRoutes } from './routes/index.ts'
14
15
 
@@ -25,6 +26,7 @@ export const getApp = (node: NodeInstance, config: Config): Express => {
25
26
  app.use(customPoweredByHeader)
26
27
  disableCaseSensitiveRouting(app)
27
28
  app.node = node
29
+ addFlowProducer(app, config)
28
30
  addRoutes(app, config)
29
31
  app.use(standardErrors)
30
32
  return app
@@ -0,0 +1,11 @@
1
+ import type { Config } from '@xyo-network/xl1-protocol-sdk'
2
+ import type { Express } from 'express'
3
+
4
+ import { getConnection, getFlowProducer } from '../queue/index.ts'
5
+
6
+ export const addFlowProducer = (app: Express, config: Config): Express => {
7
+ const connection = getConnection(config)
8
+ const flowProducer = getFlowProducer(connection)
9
+ app.flowProducer = flowProducer
10
+ return app
11
+ }