@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.
- package/dist/node/config/getGateway.d.ts +1 -1
- package/dist/node/config/getGateway.d.ts.map +1 -1
- package/dist/node/index.mjs +534 -108
- package/dist/node/index.mjs.map +1 -1
- package/dist/node/interface/service/Observer/ERC20TransferObserver/ERC20TransferObserver.d.ts.map +1 -1
- package/dist/node/interface/service/Observer/LiquidityPoolBridgeObserver/LiquidityPoolBridgeObserver.d.ts.map +1 -1
- package/dist/node/interface/service/Relay/LiquidityPoolBridgeRelay/LiquidityPoolBridgeRelay.d.ts.map +1 -1
- package/dist/node/manifest/getServices.d.ts +13 -0
- package/dist/node/manifest/getServices.d.ts.map +1 -0
- package/dist/node/manifest/index.d.ts +1 -0
- package/dist/node/manifest/index.d.ts.map +1 -1
- package/dist/node/modules/EVMLiquidityBridgeTransactionCompletionMonitorSentinel/EVMLiquidityBridgeTransactionCompletionMonitorSentinel.d.ts.map +1 -1
- package/dist/node/modules/XL1TransactionCompletionMonitorSentinel/XL1TransactionCompletionMonitorSentinel.d.ts.map +1 -1
- package/dist/node/queue/connection.d.ts +4 -0
- package/dist/node/queue/connection.d.ts.map +1 -0
- package/dist/node/queue/flowProducer.d.ts +4 -0
- package/dist/node/queue/flowProducer.d.ts.map +1 -0
- package/dist/node/queue/flows/createXl1ToEthBridgeJob.d.ts +4 -0
- package/dist/node/queue/flows/createXl1ToEthBridgeJob.d.ts.map +1 -0
- package/dist/node/queue/flows/index.d.ts +2 -0
- package/dist/node/queue/flows/index.d.ts.map +1 -0
- package/dist/node/queue/index.d.ts +5 -0
- package/dist/node/queue/index.d.ts.map +1 -0
- package/dist/node/queue/workers/WorkerDescription.d.ts +7 -0
- package/dist/node/queue/workers/WorkerDescription.d.ts.map +1 -0
- package/dist/node/queue/workers/createWorkers.d.ts +3 -0
- package/dist/node/queue/workers/createWorkers.d.ts.map +1 -0
- package/dist/node/queue/workers/ethTransactionMonitor.d.ts +3 -0
- package/dist/node/queue/workers/ethTransactionMonitor.d.ts.map +1 -0
- package/dist/node/queue/workers/ethTransactionPreparation.d.ts +5 -0
- package/dist/node/queue/workers/ethTransactionPreparation.d.ts.map +1 -0
- package/dist/node/queue/workers/ethTransactionSubmission.d.ts +3 -0
- package/dist/node/queue/workers/ethTransactionSubmission.d.ts.map +1 -0
- package/dist/node/queue/workers/index.d.ts +9 -0
- package/dist/node/queue/workers/index.d.ts.map +1 -0
- package/dist/node/queue/workers/xl1ToEthBridgeParent.d.ts +3 -0
- package/dist/node/queue/workers/xl1ToEthBridgeParent.d.ts.map +1 -0
- package/dist/node/queue/workers/xl1TransactionMonitor.d.ts +3 -0
- package/dist/node/queue/workers/xl1TransactionMonitor.d.ts.map +1 -0
- package/dist/node/queue/workers/xl1TransactionPreparation.d.ts +3 -0
- package/dist/node/queue/workers/xl1TransactionPreparation.d.ts.map +1 -0
- package/dist/node/queue/workers/xl1TransactionSubmission.d.ts +3 -0
- package/dist/node/queue/workers/xl1TransactionSubmission.d.ts.map +1 -0
- package/dist/node/server/app.d.ts.map +1 -1
- package/dist/node/server/flowProducer.d.ts +4 -0
- package/dist/node/server/flowProducer.d.ts.map +1 -0
- package/dist/node/server/index.d.ts +4 -0
- package/dist/node/server/index.d.ts.map +1 -1
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemote.d.ts.map +1 -1
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteStatus.d.ts.map +1 -1
- package/dist/node/server/server.d.ts.map +1 -1
- package/dist/node/services/EthTxState.d.ts +5 -0
- package/dist/node/services/EthTxState.d.ts.map +1 -0
- package/dist/node/services/IBridgeServiceCollection.d.ts +17 -0
- package/dist/node/services/IBridgeServiceCollection.d.ts.map +1 -0
- package/dist/node/services/TxState.d.ts +22 -0
- package/dist/node/services/TxState.d.ts.map +1 -0
- package/dist/node/services/Xl1TxState.d.ts +6 -0
- package/dist/node/services/Xl1TxState.d.ts.map +1 -0
- package/dist/node/services/index.d.ts +5 -0
- package/dist/node/services/index.d.ts.map +1 -0
- package/dist/node/util/generateBridgeEstimate.d.ts.map +1 -1
- package/dist/node/util/validateBridgeTransaction.d.ts.map +1 -1
- package/package.json +19 -18
- package/src/config/getGateway.ts +10 -3
- package/src/interface/service/Observer/ERC20TransferObserver/ERC20TransferObserver.ts +1 -1
- package/src/interface/service/Observer/LiquidityPoolBridgeObserver/LiquidityPoolBridgeObserver.ts +1 -2
- package/src/interface/service/Relay/LiquidityPoolBridgeRelay/LiquidityPoolBridgeRelay.ts +1 -1
- package/src/manifest/getLocator.ts +4 -4
- package/src/manifest/getServices.ts +102 -0
- package/src/manifest/index.ts +1 -0
- package/src/modules/EVMLiquidityBridgeTransactionCompletionMonitorSentinel/EVMLiquidityBridgeTransactionCompletionMonitorSentinel.ts +1 -1
- package/src/modules/XL1TransactionCompletionMonitorSentinel/XL1TransactionCompletionMonitorSentinel.ts +1 -1
- package/src/queue/connection.ts +16 -0
- package/src/queue/flowProducer.ts +11 -0
- package/src/queue/flows/createXl1ToEthBridgeJob.ts +60 -0
- package/src/queue/flows/index.ts +1 -0
- package/src/queue/index.ts +4 -0
- package/src/queue/workers/WorkerDescription.ts +7 -0
- package/src/queue/workers/createWorkers.ts +15 -0
- package/src/queue/workers/ethTransactionMonitor.ts +42 -0
- package/src/queue/workers/ethTransactionPreparation.ts +47 -0
- package/src/queue/workers/ethTransactionSubmission.ts +77 -0
- package/src/queue/workers/index.ts +8 -0
- package/src/queue/workers/xl1ToEthBridgeParent.ts +25 -0
- package/src/queue/workers/xl1TransactionMonitor.ts +66 -0
- package/src/queue/workers/xl1TransactionPreparation.ts +36 -0
- package/src/queue/workers/xl1TransactionSubmission.ts +60 -0
- package/src/server/app.ts +2 -0
- package/src/server/flowProducer.ts +11 -0
- package/src/server/index.ts +5 -0
- package/src/server/routes/bridge/routeDefinitions/routes/bridgeToRemote.ts +6 -14
- package/src/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteStatus.ts +1 -0
- package/src/server/server.ts +2 -1
- package/src/services/EthTxState.ts +5 -0
- package/src/services/IBridgeServiceCollection.ts +18 -0
- package/src/services/TxState.ts +32 -0
- package/src/services/Xl1TxState.ts +6 -0
- package/src/services/index.ts +4 -0
- package/src/util/generateBridgeEstimate.ts +1 -2
- package/src/util/validateBridgeTransaction.ts +5 -0
- package/dist/node/driver/index.d.ts +0 -2
- package/dist/node/driver/index.d.ts.map +0 -1
- package/dist/node/driver/indexer/ChainBlockIteration/ChainHashIterationService.d.ts +0 -24
- package/dist/node/driver/indexer/ChainBlockIteration/ChainHashIterationService.d.ts.map +0 -1
- package/dist/node/driver/indexer/ChainBlockIteration/index.d.ts +0 -2
- package/dist/node/driver/indexer/ChainBlockIteration/index.d.ts.map +0 -1
- package/dist/node/driver/indexer/ChainBlocksObservable.d.ts +0 -6
- package/dist/node/driver/indexer/ChainBlocksObservable.d.ts.map +0 -1
- package/dist/node/driver/indexer/ChainHydratedBlocksObservable.d.ts +0 -11
- package/dist/node/driver/indexer/ChainHydratedBlocksObservable.d.ts.map +0 -1
- package/dist/node/driver/indexer/index.d.ts +0 -2
- package/dist/node/driver/indexer/index.d.ts.map +0 -1
- package/src/driver/index.ts +0 -1
- package/src/driver/indexer/ChainBlockIteration/ChainHashIterationService.ts +0 -89
- package/src/driver/indexer/ChainBlockIteration/index.ts +0 -1
- package/src/driver/indexer/ChainBlocksObservable.ts +0 -46
- package/src/driver/indexer/ChainHydratedBlocksObservable.ts +0 -23
- 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
|
+
}
|
package/src/manifest/index.ts
CHANGED
|
@@ -109,7 +109,7 @@ export class EVMLiquidityBridgeTransactionCompletionMonitorSentinel<
|
|
|
109
109
|
return assertEx(this._wallet, () => 'wallet is required')
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
override async createHandler()
|
|
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()
|
|
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,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
|
+
}
|