@xyo-network/chain-bridge 1.19.18 → 1.20.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 (134) hide show
  1. package/dist/node/config/getTestGateway.d.ts +4 -0
  2. package/dist/node/config/getTestGateway.d.ts.map +1 -0
  3. package/dist/node/config/index.d.ts +1 -1
  4. package/dist/node/config/index.d.ts.map +1 -1
  5. package/dist/node/index.mjs +700 -506
  6. package/dist/node/index.mjs.map +1 -1
  7. package/dist/node/modules/index.d.ts +0 -1
  8. package/dist/node/modules/index.d.ts.map +1 -1
  9. package/dist/node/queue/flowProducer.d.ts.map +1 -1
  10. package/dist/node/queue/flows/createEthToXl1BridgeJob.d.ts +10 -0
  11. package/dist/node/queue/flows/createEthToXl1BridgeJob.d.ts.map +1 -0
  12. package/dist/node/queue/flows/createXl1ToEthBridgeJob.d.ts.map +1 -1
  13. package/dist/node/queue/flows/index.d.ts +1 -0
  14. package/dist/node/queue/flows/index.d.ts.map +1 -1
  15. package/dist/node/queue/getXl1ToEthQueueJobs.d.ts +20 -0
  16. package/dist/node/queue/getXl1ToEthQueueJobs.d.ts.map +1 -0
  17. package/dist/node/queue/getXl1ToEthQueues.d.ts +14 -0
  18. package/dist/node/queue/getXl1ToEthQueues.d.ts.map +1 -0
  19. package/dist/node/queue/index.d.ts +3 -0
  20. package/dist/node/queue/index.d.ts.map +1 -1
  21. package/dist/node/queue/prefix.d.ts +2 -0
  22. package/dist/node/queue/prefix.d.ts.map +1 -0
  23. package/dist/node/queue/workers/EthToXl1BridgeParent.d.ts +8 -0
  24. package/dist/node/queue/workers/EthToXl1BridgeParent.d.ts.map +1 -0
  25. package/dist/node/queue/workers/EthTransactionMonitor.d.ts +2 -0
  26. package/dist/node/queue/workers/EthTransactionMonitor.d.ts.map +1 -1
  27. package/dist/node/queue/workers/EthTransactionPreparation.d.ts +4 -2
  28. package/dist/node/queue/workers/EthTransactionPreparation.d.ts.map +1 -1
  29. package/dist/node/queue/workers/EthTransactionSubmission.d.ts +2 -0
  30. package/dist/node/queue/workers/EthTransactionSubmission.d.ts.map +1 -1
  31. package/dist/node/queue/workers/EthTransactionSubmissionStorage.d.ts +13 -0
  32. package/dist/node/queue/workers/EthTransactionSubmissionStorage.d.ts.map +1 -0
  33. package/dist/node/queue/workers/Xl1ToEthBridgeParent.d.ts +6 -1
  34. package/dist/node/queue/workers/Xl1ToEthBridgeParent.d.ts.map +1 -1
  35. package/dist/node/queue/workers/Xl1TransactionMonitor.d.ts +4 -0
  36. package/dist/node/queue/workers/Xl1TransactionMonitor.d.ts.map +1 -1
  37. package/dist/node/queue/workers/Xl1TransactionPreparation.d.ts +2 -0
  38. package/dist/node/queue/workers/Xl1TransactionPreparation.d.ts.map +1 -1
  39. package/dist/node/queue/workers/Xl1TransactionSubmission.d.ts +2 -0
  40. package/dist/node/queue/workers/Xl1TransactionSubmission.d.ts.map +1 -1
  41. package/dist/node/queue/workers/Xl1TransactionSubmissionStorage.d.ts +13 -0
  42. package/dist/node/queue/workers/Xl1TransactionSubmissionStorage.d.ts.map +1 -0
  43. package/dist/node/queue/workers/createWorkers.d.ts.map +1 -1
  44. package/dist/node/queue/workers/index.d.ts +3 -0
  45. package/dist/node/queue/workers/index.d.ts.map +1 -1
  46. package/dist/node/queue/workers/util/index.d.ts +0 -3
  47. package/dist/node/queue/workers/util/index.d.ts.map +1 -1
  48. package/dist/node/server/index.d.ts +2 -0
  49. package/dist/node/server/index.d.ts.map +1 -1
  50. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemote.d.ts.map +1 -1
  51. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteEstimate.d.ts +1 -1
  52. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteStatus.d.ts.map +1 -1
  53. package/dist/node/server/server.d.ts.map +1 -1
  54. package/dist/node/services/IBridgeServiceCollection.d.ts +1 -1
  55. package/dist/node/services/IBridgeServiceCollection.d.ts.map +1 -1
  56. package/dist/node/services/TxState.d.ts +0 -7
  57. package/dist/node/services/TxState.d.ts.map +1 -1
  58. package/dist/node/util/index.d.ts +0 -3
  59. package/dist/node/util/index.d.ts.map +1 -1
  60. package/dist/node/validation/AsyncLogger.d.ts.map +1 -0
  61. package/dist/node/validation/index.d.ts +7 -0
  62. package/dist/node/validation/index.d.ts.map +1 -0
  63. package/dist/node/validation/validateBridgeEstimate.d.ts.map +1 -0
  64. package/dist/node/validation/validateBridgeEstimateExact.d.ts.map +1 -0
  65. package/dist/node/validation/validateBridgeTransaction.d.ts.map +1 -0
  66. package/dist/node/validation/validateSufficientLiquiditySourceAllowance.d.ts.map +1 -0
  67. package/dist/node/validation/validateSufficientLiquiditySourceBalance.d.ts.map +1 -0
  68. package/dist/node/validation/validateSufficientRunnerEthBalanceForGas.d.ts.map +1 -0
  69. package/dist/node/validation/validateSufficientXL1SourceAddressBalance.d.ts +15 -0
  70. package/dist/node/validation/validateSufficientXL1SourceAddressBalance.d.ts.map +1 -0
  71. package/package.json +30 -30
  72. package/src/config/getTestGateway.ts +24 -0
  73. package/src/config/index.ts +1 -1
  74. package/src/modules/index.ts +0 -1
  75. package/src/queue/flowProducer.ts +5 -1
  76. package/src/queue/flows/createEthToXl1BridgeJob.ts +71 -0
  77. package/src/queue/flows/createXl1ToEthBridgeJob.ts +48 -20
  78. package/src/queue/flows/index.ts +1 -0
  79. package/src/queue/getXl1ToEthQueueJobs.ts +57 -0
  80. package/src/queue/getXl1ToEthQueues.ts +39 -0
  81. package/src/queue/index.ts +3 -0
  82. package/src/queue/prefix.ts +1 -0
  83. package/src/queue/workers/EthToXl1BridgeParent.ts +40 -0
  84. package/src/queue/workers/EthTransactionMonitor.ts +6 -2
  85. package/src/queue/workers/EthTransactionPreparation.ts +9 -5
  86. package/src/queue/workers/EthTransactionSubmission.ts +4 -12
  87. package/src/queue/workers/EthTransactionSubmissionStorage.ts +76 -0
  88. package/src/queue/workers/Xl1ToEthBridgeParent.ts +8 -3
  89. package/src/queue/workers/Xl1TransactionMonitor.ts +13 -9
  90. package/src/queue/workers/Xl1TransactionPreparation.ts +6 -2
  91. package/src/queue/workers/Xl1TransactionSubmission.ts +8 -9
  92. package/src/queue/workers/Xl1TransactionSubmissionStorage.ts +77 -0
  93. package/src/queue/workers/createWorkers.ts +10 -4
  94. package/src/queue/workers/index.ts +3 -0
  95. package/src/queue/workers/util/index.ts +0 -3
  96. package/src/server/index.ts +3 -2
  97. package/src/server/routes/bridge/routeDefinitions/routes/bridgeToRemote.ts +11 -1
  98. package/src/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteStatus.ts +27 -101
  99. package/src/server/server.ts +1 -0
  100. package/src/services/IBridgeServiceCollection.ts +1 -1
  101. package/src/services/TxState.ts +0 -18
  102. package/src/util/index.ts +0 -3
  103. package/src/validation/index.ts +6 -0
  104. package/src/{util → validation}/validateBridgeEstimate.ts +1 -1
  105. package/src/{util → validation}/validateBridgeEstimateExact.ts +1 -1
  106. package/src/{util → validation}/validateBridgeTransaction.ts +1 -2
  107. package/src/validation/validateSufficientXL1SourceAddressBalance.ts +39 -0
  108. package/dist/node/config/getGateway.d.ts +0 -4
  109. package/dist/node/config/getGateway.d.ts.map +0 -1
  110. package/dist/node/modules/XL1TransactionCompletionMonitorSentinel/XL1TransactionCompletionMonitorSentinel.d.ts +0 -60
  111. package/dist/node/modules/XL1TransactionCompletionMonitorSentinel/XL1TransactionCompletionMonitorSentinel.d.ts.map +0 -1
  112. package/dist/node/modules/XL1TransactionCompletionMonitorSentinel/index.d.ts +0 -2
  113. package/dist/node/modules/XL1TransactionCompletionMonitorSentinel/index.d.ts.map +0 -1
  114. package/dist/node/queue/workers/util/AsyncLogger.d.ts.map +0 -1
  115. package/dist/node/queue/workers/util/validateSufficientLiquiditySourceAllowance.d.ts.map +0 -1
  116. package/dist/node/queue/workers/util/validateSufficientLiquiditySourceBalance.d.ts.map +0 -1
  117. package/dist/node/queue/workers/util/validateSufficientRunnerEthBalanceForGas.d.ts.map +0 -1
  118. package/dist/node/util/validateBridgeEstimate.d.ts.map +0 -1
  119. package/dist/node/util/validateBridgeEstimateExact.d.ts.map +0 -1
  120. package/dist/node/util/validateBridgeTransaction.d.ts.map +0 -1
  121. package/src/config/getGateway.ts +0 -23
  122. package/src/modules/XL1TransactionCompletionMonitorSentinel/XL1TransactionCompletionMonitorSentinel.ts +0 -165
  123. package/src/modules/XL1TransactionCompletionMonitorSentinel/index.ts +0 -1
  124. /package/dist/node/{queue/workers/util → validation}/AsyncLogger.d.ts +0 -0
  125. /package/dist/node/{util → validation}/validateBridgeEstimate.d.ts +0 -0
  126. /package/dist/node/{util → validation}/validateBridgeEstimateExact.d.ts +0 -0
  127. /package/dist/node/{util → validation}/validateBridgeTransaction.d.ts +0 -0
  128. /package/dist/node/{queue/workers/util → validation}/validateSufficientLiquiditySourceAllowance.d.ts +0 -0
  129. /package/dist/node/{queue/workers/util → validation}/validateSufficientLiquiditySourceBalance.d.ts +0 -0
  130. /package/dist/node/{queue/workers/util → validation}/validateSufficientRunnerEthBalanceForGas.d.ts +0 -0
  131. /package/src/{queue/workers/util → validation}/AsyncLogger.ts +0 -0
  132. /package/src/{queue/workers/util → validation}/validateSufficientLiquiditySourceAllowance.ts +0 -0
  133. /package/src/{queue/workers/util → validation}/validateSufficientLiquiditySourceBalance.ts +0 -0
  134. /package/src/{queue/workers/util → validation}/validateSufficientRunnerEthBalanceForGas.ts +0 -0
@@ -0,0 +1,76 @@
1
+ import { assertEx, isDefined } from '@xylabs/sdk-js'
2
+ import { PayloadBuilder } from '@xyo-network/sdk-js'
3
+ import type { SignedHydratedTransaction } from '@xyo-network/xl1-sdk'
4
+ import type { Job } from 'bullmq'
5
+ import { Worker } from 'bullmq'
6
+ import type { BullMQOtel } from 'bullmq-otel'
7
+ import type { Redis } from 'ioredis'
8
+
9
+ import type { EthTxState, IBridgeServiceCollection } from '../../services/index.ts'
10
+ import { prefix } from '../prefix.ts'
11
+ import type { EthTransactionSubmissionJobReturn } from './EthTransactionSubmission.ts'
12
+ import { EthTransactionSubmission } from './EthTransactionSubmission.ts'
13
+ import type { WorkerDescription } from './WorkerDescription.ts'
14
+
15
+ export type EthTransactionSubmissionStorageJobData = { tx: SignedHydratedTransaction }
16
+ export interface EthTransactionSubmissionStorageJobReturn {
17
+ submissionHash: Required<EthTxState>['submissionHash']
18
+ }
19
+ export type EthTransactionSubmissionStorageJob = Job<EthTransactionSubmissionStorageJobData, EthTransactionSubmissionStorageJobReturn>
20
+
21
+ const name = 'Store ETH Transaction Submission'
22
+ const queueName = 'eth-tx-store-submission'
23
+
24
+ const createWorker = (connection: Redis, telemetry?: BullMQOtel, services?: IBridgeServiceCollection) => {
25
+ const stateMap = assertEx(services?.ethTxStateMap, () => 'ethTxStateMap service not provided')
26
+
27
+ const worker = new Worker(
28
+ queueName,
29
+ async (job: EthTransactionSubmissionStorageJob) => {
30
+ const { tx } = job.data
31
+ // Get the hash of the transaction
32
+ const hash = await PayloadBuilder.hash(tx[0])
33
+
34
+ // Get the state of the transaction
35
+ const state = assertEx(await stateMap.get(hash), () => `[${hash}] state not found`)
36
+
37
+ // Idempotency check against re-store
38
+ const { submissionHash: existingSubmissionHash } = state
39
+ if (isDefined(existingSubmissionHash)) {
40
+ await job.log(`[${hash}] submissionHash already stored as ${existingSubmissionHash}`)
41
+ return { submissionHash: existingSubmissionHash }
42
+ }
43
+
44
+ // In a Flow, this job should be the PARENT and the submit job should be a CHILD.
45
+ // That lets us read the child job’s return value(s) here.
46
+ const childrenValues = await job.getChildrenValues()
47
+ const jobKey = `${prefix}:${EthTransactionSubmission.queueName}:${hash}`
48
+ const childValues = childrenValues?.[jobKey] as EthTransactionSubmissionJobReturn | undefined
49
+ const submissionHash = childValues?.submissionHash
50
+
51
+ const resolvedSubmissionHash = assertEx(submissionHash, () => `[${hash}] child submissionHash not found in children values`)
52
+
53
+ // Store the submission hash in the state
54
+ await job.log(`[${hash}] Storing ETH submissionHash`)
55
+ state.submissionHash = resolvedSubmissionHash
56
+ await stateMap.set(hash, state)
57
+ await job.log(`[${hash}] Stored ETH submissionHash`)
58
+ return { submissionHash: resolvedSubmissionHash }
59
+ },
60
+ {
61
+ connection, telemetry, prefix,
62
+ },
63
+ )
64
+
65
+ worker.on('failed', (job, err) => {
66
+ console.error(`[${name}] Job ${job?.id} failed:`, err.message)
67
+ })
68
+
69
+ worker.on('error', (err) => {
70
+ console.error(`[${name}] Worker error:`, err)
71
+ })
72
+ }
73
+
74
+ export const EthTransactionSubmissionStorage: WorkerDescription = {
75
+ createWorker, name, queueName,
76
+ }
@@ -1,25 +1,30 @@
1
+ import type { SignedHydratedTransaction } from '@xyo-network/xl1-sdk'
1
2
  import type { Job } from 'bullmq'
2
3
  import { Worker } from 'bullmq'
3
4
  import type { BullMQOtel } from 'bullmq-otel'
4
5
  import type { Redis } from 'ioredis'
5
6
 
7
+ import { prefix } from '../prefix.ts'
6
8
  import type { WorkerDescription } from './WorkerDescription.ts'
7
9
 
8
- export type Xl1ToEthBridgeParentJobData = {}
10
+ export type Xl1ToEthBridgeParentJobData = { tx: SignedHydratedTransaction }
9
11
  export interface Xl1ToEthBridgeParentJobReturn { }
12
+ export type Xl1ToEthBridgeParentJob = Job<Xl1ToEthBridgeParentJobData, Xl1ToEthBridgeParentJobReturn>
10
13
 
11
14
  const name = 'Bridge XL1 to Ethereum'
12
15
  const queueName = 'xl1-to-eth-bridge'
13
16
  const createWorker = (connection: Redis, telemetry?: BullMQOtel) => {
14
17
  const worker = new Worker(
15
18
  queueName,
16
- async (job: Job<Xl1ToEthBridgeParentJobData, Xl1ToEthBridgeParentJobReturn>) => {
19
+ async (job: Xl1ToEthBridgeParentJob) => {
17
20
  await job.log(`[${job.name}] start`)
18
21
  // Parent job has no work other than waiting on children
19
22
  await job.log(`[${job.name}] done`)
20
23
  return {}
21
24
  },
22
- { connection, telemetry },
25
+ {
26
+ connection, telemetry, prefix,
27
+ },
23
28
  )
24
29
 
25
30
  worker.on('failed', (job, err) => {
@@ -1,3 +1,4 @@
1
+ import type { BrandedHash } from '@xylabs/sdk-js'
1
2
  import {
2
3
  assertEx, isDefined, isNull,
3
4
  } from '@xylabs/sdk-js'
@@ -9,42 +10,43 @@ import type { BullMQOtel } from 'bullmq-otel'
9
10
  import type { Redis } from 'ioredis'
10
11
 
11
12
  import type { IBridgeServiceCollection } from '../../services/index.ts'
13
+ import { prefix } from '../prefix.ts'
12
14
  import type { WorkerDescription } from './WorkerDescription.ts'
13
15
 
14
16
  export type Xl1TransactionMonitorJobData = { tx: SignedHydratedTransaction }
15
-
16
17
  export interface Xl1TransactionMonitorJobReturn {
18
+ submissionHash: BrandedHash
17
19
  }
20
+ export type Xl1TransactionMonitorJob = Job<Xl1TransactionMonitorJobData, Xl1TransactionMonitorJobReturn>
18
21
 
19
22
  const name = 'Monitor Submitted XL1 Transaction'
20
23
  const queueName = 'xl1-tx-monitor'
21
24
  const createWorker = (connection: Redis, telemetry?: BullMQOtel, services?: IBridgeServiceCollection) => {
22
25
  const gateway = assertEx(services?.gateway, () => 'gateway service not provided')
23
26
  const stateMap = assertEx(services?.xl1TxStateMap, () => 'xl1TxStateMap service not provided')
27
+ const viewer = assertEx(gateway.connection.viewer, () => 'viewer not defined on gateway')
24
28
 
25
29
  const worker = new Worker(
26
30
  queueName,
27
- async (job: Job<Xl1TransactionMonitorJobData, Xl1TransactionMonitorJobReturn>) => {
31
+ async (job: Xl1TransactionMonitorJob) => {
28
32
  const { tx } = job.data
29
33
  // Get the hash of the transaction
30
34
  const hash = await PayloadBuilder.hash(tx[0])
31
35
  // Get the state of the transaction
32
- const viewer = assertEx(gateway.connection.viewer, () => `[${hash}] viewer not defined on gateway`)
33
36
  const state = assertEx(await stateMap.get(hash), () => `[${hash}] state not found`)
34
- const submissionHash = assertEx(state?.submissionHash, () => `[${hash}] submissionHash not found`)
37
+ const submissionHash = assertEx(state.submissionHash, () => `[${hash}] submissionHash not found`)
35
38
 
36
39
  // Check for transaction inclusion on chain
37
40
  await job.log(`[${hash}] Checking for XL1 transaction inclusion on chain`)
38
- const foundTx = await viewer.transactionByHash(submissionHash)
41
+ const foundTx = await viewer.transaction.byHash(submissionHash)
39
42
  // const foundTx = await viewer.transaction.byHash(submissionHash)
40
43
 
41
44
  // Transaction found on chain
42
45
  if (isDefined(foundTx) && !isNull(foundTx)) {
43
46
  await job.log(`[${hash}] Found transaction on chain`)
44
47
  // Store the block hash
45
- state.confirmationHash = await PayloadBuilder.hash(foundTx[0])
46
- await stateMap.set(hash, state)
47
- return {}
48
+ const submissionHash = await PayloadBuilder.hash(foundTx[0])
49
+ return { submissionHash }
48
50
  }
49
51
 
50
52
  // Check for transaction expiration
@@ -61,7 +63,9 @@ const createWorker = (connection: Redis, telemetry?: BullMQOtel, services?: IBri
61
63
  await job.log(`[${hash}] Transaction not yet included, retrying later`)
62
64
  throw new Error(`[${hash}] Transaction not yet included`)
63
65
  },
64
- { connection, telemetry },
66
+ {
67
+ connection, telemetry, prefix,
68
+ },
65
69
  )
66
70
 
67
71
  worker.on('failed', (job, err) => {
@@ -7,10 +7,12 @@ import type { BullMQOtel } from 'bullmq-otel'
7
7
  import type { Redis } from 'ioredis'
8
8
 
9
9
  import type { IBridgeServiceCollection } from '../../services/index.ts'
10
+ import { prefix } from '../prefix.ts'
10
11
  import type { WorkerDescription } from './WorkerDescription.ts'
11
12
 
12
13
  export type Xl1TransactionPreparationJobData = { tx: SignedHydratedTransaction }
13
14
  export interface Xl1TransactionPreparationJobReturn { preparedTx: SignedHydratedTransaction }
15
+ export type Xl1TransactionPreparationJob = Job<Xl1TransactionPreparationJobData, Xl1TransactionPreparationJobReturn>
14
16
 
15
17
  const name = 'Prepare XL1 Transaction'
16
18
  const queueName = 'xl1-tx-prepare'
@@ -19,7 +21,7 @@ const createWorker = (connection: Redis, telemetry?: BullMQOtel, services?: IBri
19
21
 
20
22
  const worker = new Worker(
21
23
  queueName,
22
- async (job: Job<Xl1TransactionPreparationJobData, Xl1TransactionPreparationJobReturn>) => {
24
+ async (job: Xl1TransactionPreparationJob) => {
23
25
  const { tx } = job.data
24
26
  const hash = await PayloadBuilder.hash(tx[0])
25
27
  await job.log(`[${hash}] preparing XL1 transaction`)
@@ -30,7 +32,9 @@ const createWorker = (connection: Redis, telemetry?: BullMQOtel, services?: IBri
30
32
  await job.log(`[${hash}] prepared XL1 transaction`)
31
33
  return { preparedTx }
32
34
  },
33
- { connection, telemetry },
35
+ {
36
+ connection, telemetry, prefix,
37
+ },
34
38
  )
35
39
 
36
40
  worker.on('failed', (job, err) => {
@@ -7,24 +7,26 @@ import type { BullMQOtel } from 'bullmq-otel'
7
7
  import type { Redis } from 'ioredis'
8
8
 
9
9
  import type { IBridgeServiceCollection, Xl1TxState } from '../../services/index.ts'
10
+ import { prefix } from '../prefix.ts'
10
11
  import { submitXl1Transaction } from './util/index.ts'
11
12
  import type { WorkerDescription } from './WorkerDescription.ts'
12
13
 
13
14
  export type Xl1TransactionSubmissionJobData = { tx: SignedHydratedTransaction }
14
-
15
15
  export interface Xl1TransactionSubmissionJobReturn {
16
16
  submissionHash: Required<Xl1TxState>['submissionHash']
17
17
  }
18
+ export type Xl1TransactionSubmissionJob = Job<Xl1TransactionSubmissionJobData, Xl1TransactionSubmissionJobReturn>
18
19
 
19
20
  const name = 'Submit XL1 Transaction'
20
21
  const queueName = 'xl1-tx-submit'
22
+
21
23
  const createWorker = (connection: Redis, telemetry?: BullMQOtel, services?: IBridgeServiceCollection) => {
22
24
  const gateway = assertEx(services?.gateway, () => 'gateway service not provided')
23
25
  const stateMap = assertEx(services?.xl1TxStateMap, () => 'xl1TxStateMap service not provided')
24
26
 
25
27
  const worker = new Worker(
26
28
  queueName,
27
- async (job: Job<Xl1TransactionSubmissionJobData, Xl1TransactionSubmissionJobReturn>) => {
29
+ async (job: Xl1TransactionSubmissionJob) => {
28
30
  const { tx } = job.data
29
31
  // Get the hash of the transaction
30
32
  const hash = await PayloadBuilder.hash(tx[0])
@@ -47,15 +49,12 @@ const createWorker = (connection: Redis, telemetry?: BullMQOtel, services?: IBri
47
49
  // assertEx(submissionHash === hash, () => `[${hash}] Submitted transaction hash ${submissionHash} does not match expected hash`)
48
50
  await job.log(`[${hash}] Submitted XL1 tx`)
49
51
 
50
- // Store the submission hash in the state
51
- await job.log(`[${hash}] Storing XL1 submissionHash`)
52
- state.submissionHash = submissionHash
53
- await stateMap.set(hash, state)
54
- await job.log(`[${hash}] Stored XL1 submissionHash`)
55
-
52
+ // This worker intentionally does NOT store state as subsequent steps will do that.
56
53
  return { submissionHash }
57
54
  },
58
- { connection, telemetry },
55
+ {
56
+ connection, telemetry, prefix,
57
+ },
59
58
  )
60
59
 
61
60
  worker.on('failed', (job, err) => {
@@ -0,0 +1,77 @@
1
+ import { assertEx, isDefined } from '@xylabs/sdk-js'
2
+ import { PayloadBuilder } from '@xyo-network/sdk-js'
3
+ import { type SignedHydratedTransaction } from '@xyo-network/xl1-sdk'
4
+ import type { Job } from 'bullmq'
5
+ import { Worker } from 'bullmq'
6
+ import type { BullMQOtel } from 'bullmq-otel'
7
+ import type { Redis } from 'ioredis'
8
+
9
+ import type { IBridgeServiceCollection, Xl1TxState } from '../../services/index.ts'
10
+ import { prefix } from '../prefix.ts'
11
+ import type { WorkerDescription } from './WorkerDescription.ts'
12
+ import type { Xl1TransactionSubmissionJobReturn } from './Xl1TransactionSubmission.ts'
13
+ import { Xl1TransactionSubmission } from './Xl1TransactionSubmission.ts'
14
+
15
+ export type Xl1TransactionSubmissionStorageJobData = { tx: SignedHydratedTransaction }
16
+ export interface Xl1TransactionSubmissionStorageJobReturn {
17
+ submissionHash: Required<Xl1TxState>['submissionHash']
18
+ }
19
+ export type Xl1TransactionSubmissionStorageJob = Job<Xl1TransactionSubmissionStorageJobData, Xl1TransactionSubmissionStorageJobReturn>
20
+
21
+ const name = 'Store XL1 Transaction Submission'
22
+ const queueName = 'xl1-tx-store-submission'
23
+
24
+ const createWorker = (connection: Redis, telemetry?: BullMQOtel, services?: IBridgeServiceCollection) => {
25
+ const stateMap = assertEx(services?.xl1TxStateMap, () => 'xl1TxStateMap service not provided')
26
+
27
+ const worker = new Worker(
28
+ queueName,
29
+ async (job: Xl1TransactionSubmissionStorageJob) => {
30
+ const { tx } = job.data
31
+ // Get the hash of the transaction
32
+ const hash = await PayloadBuilder.hash(tx[0])
33
+
34
+ // Get the state of the transaction
35
+ const state = assertEx(await stateMap.get(hash), () => `[${hash}] state not found`)
36
+
37
+ // Idempotency check against re-store
38
+ const { submissionHash: existingSubmissionHash } = state
39
+ if (isDefined(existingSubmissionHash)) {
40
+ await job.log(`[${hash}] submissionHash already stored as ${existingSubmissionHash}`)
41
+ return { submissionHash: existingSubmissionHash }
42
+ }
43
+
44
+ // In a Flow, this job should be the PARENT and the submit job should be a CHILD.
45
+ // That lets us read the child job’s return value(s) here.
46
+ const childrenValues = await job.getChildrenValues()
47
+ const jobKey = `${prefix}:${Xl1TransactionSubmission.queueName}:${hash}`
48
+ const childValues = childrenValues?.[jobKey] as Xl1TransactionSubmissionJobReturn | undefined
49
+ const submissionHash = childValues?.submissionHash
50
+
51
+ const resolvedSubmissionHash = assertEx(submissionHash, () => `[${hash}] child submissionHash not found in children values`)
52
+
53
+ // Store the submission hash in the state
54
+ await job.log(`[${hash}] Storing XL1 submissionHash`)
55
+ state.submissionHash = resolvedSubmissionHash
56
+ await stateMap.set(hash, state)
57
+ await job.log(`[${hash}] Stored XL1 submissionHash`)
58
+
59
+ return { submissionHash: resolvedSubmissionHash }
60
+ },
61
+ {
62
+ connection, telemetry, prefix,
63
+ },
64
+ )
65
+
66
+ worker.on('failed', (job, err) => {
67
+ console.error(`[${name}] Job ${job?.id} failed:`, err.message)
68
+ })
69
+
70
+ worker.on('error', (err) => {
71
+ console.error(`[${name}] Worker error:`, err)
72
+ })
73
+ }
74
+
75
+ export const Xl1TransactionSubmissionStorage: WorkerDescription = {
76
+ createWorker, name, queueName,
77
+ }
@@ -6,18 +6,24 @@ import {
6
6
  EthTransactionMonitor,
7
7
  EthTransactionPreparation,
8
8
  EthTransactionSubmission,
9
+ EthTransactionSubmissionStorage,
9
10
  Xl1ToEthBridgeParent,
10
11
  Xl1TransactionMonitor,
11
12
  Xl1TransactionPreparation,
12
13
  Xl1TransactionSubmission,
14
+ Xl1TransactionSubmissionStorage,
13
15
  } from './index.ts'
14
16
 
15
17
  export const createWorkers = (connection: Redis, telemetry: BullMQOtel, services: IBridgeServiceCollection) => {
18
+ // TODO: Uncomment when EthToXl1 flow is ready
19
+ // EthToXl1BridgeParent.createWorker(connection, telemetry)
20
+ EthTransactionMonitor.createWorker(connection, telemetry, services)
21
+ EthTransactionPreparation.createWorker(connection, telemetry, services)
22
+ EthTransactionSubmission.createWorker(connection, telemetry, services)
23
+ EthTransactionSubmissionStorage.createWorker(connection, telemetry, services)
16
24
  Xl1ToEthBridgeParent.createWorker(connection, telemetry)
25
+ Xl1TransactionMonitor.createWorker(connection, telemetry, services)
17
26
  Xl1TransactionPreparation.createWorker(connection, telemetry, services)
18
27
  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)
28
+ Xl1TransactionSubmissionStorage.createWorker(connection, telemetry, services)
23
29
  }
@@ -1,8 +1,11 @@
1
1
  export * from './createWorkers.ts'
2
+ export * from './EthToXl1BridgeParent.ts'
2
3
  export * from './EthTransactionMonitor.ts'
3
4
  export * from './EthTransactionPreparation.ts'
4
5
  export * from './EthTransactionSubmission.ts'
6
+ export * from './EthTransactionSubmissionStorage.ts'
5
7
  export * from './Xl1ToEthBridgeParent.ts'
6
8
  export * from './Xl1TransactionMonitor.ts'
7
9
  export * from './Xl1TransactionPreparation.ts'
8
10
  export * from './Xl1TransactionSubmission.ts'
11
+ export * from './Xl1TransactionSubmissionStorage.ts'
@@ -1,5 +1,2 @@
1
1
  export * from './submitEthTransaction.ts'
2
2
  export * from './submitXl1Transaction.ts'
3
- export * from './validateSufficientLiquiditySourceAllowance.ts'
4
- export * from './validateSufficientLiquiditySourceBalance.ts'
5
- export * from './validateSufficientRunnerEthBalanceForGas.ts'
@@ -4,6 +4,8 @@ export * from './server.ts'
4
4
  // import type { NodeInstance } from '@xyo-network/sdk-js'
5
5
  import type { FlowProducer } from 'bullmq'
6
6
 
7
+ import type { IBridgeServiceCollection } from '../services/index.ts'
8
+
7
9
  // import type { IBridgeServiceCollection } from '../services/index.ts'
8
10
 
9
11
  declare global {
@@ -11,8 +13,7 @@ declare global {
11
13
  namespace Express {
12
14
  interface Application {
13
15
  flowProducer: FlowProducer
14
- // node: NodeInstance
15
- // services: IBridgeServiceCollection
16
+ services: IBridgeServiceCollection
16
17
  }
17
18
  }
18
19
  }
@@ -13,7 +13,9 @@ import {
13
13
  import { z } from 'zod'
14
14
 
15
15
  import { createXl1ToEthBridgeJob } from '../../../../../queue/index.ts'
16
- import { validateBridgeEstimateExact, validateBridgeTransaction } from '../../../../../util/index.ts'
16
+ import {
17
+ validateBridgeEstimateExact, validateBridgeTransaction, validateSufficientXL1SourceAddressBalance,
18
+ } from '../../../../../validation/index.ts'
17
19
  import { getRemoteChainIdZod } from '../pathParams/index.ts'
18
20
 
19
21
  export const BridgeToRemoteBodyZod = z.tuple([
@@ -38,6 +40,7 @@ export const makeBridgeToRemoteRoute = (config: BridgeConfig): RouteDefinition =
38
40
  handlers: validateRequest(async (req, res) => {
39
41
  const [signedTxBw, bridgeIntent, transfer] = req.body
40
42
  const { flowProducer } = req.app
43
+ const { gateway } = req.app.services
41
44
 
42
45
  // Validate request
43
46
  const transactionValid = await validateBridgeTransaction(signedTxBw, bridgeIntent, transfer, config)
@@ -53,6 +56,13 @@ export const makeBridgeToRemoteRoute = (config: BridgeConfig): RouteDefinition =
53
56
  return
54
57
  }
55
58
 
59
+ // Validate sufficient balance
60
+ const sufficientBalance = await validateSufficientXL1SourceAddressBalance(bridgeIntent, gateway, config)
61
+ if (!sufficientBalance) {
62
+ res.status(400).send()
63
+ return
64
+ }
65
+
56
66
  // Submit to job queue
57
67
  const singedHydratedTransaction: SignedHydratedTransaction = [signedTxBw, [transfer, bridgeIntent]]
58
68
  await createXl1ToEthBridgeJob(flowProducer, singedHydratedTransaction)
@@ -3,21 +3,14 @@ import { requestHandlerValidator } from '@xylabs/express'
3
3
  import { asHex, isDefined } from '@xylabs/sdk-js'
4
4
  import type { BridgeConfig } from '@xyo-network/chain-orchestration'
5
5
  import { PayloadBuilder, PayloadZodStrictOfSchema } from '@xyo-network/sdk-js'
6
- import type {
7
- BridgeDestinationObservation, BridgeSourceObservation, SignedHydratedTransaction,
8
- } from '@xyo-network/xl1-sdk'
6
+ import type { BridgeDestinationObservation, BridgeSourceObservation } from '@xyo-network/xl1-sdk'
9
7
  import {
10
8
  asBridgeIntent, BridgeDestinationObservationFieldsZod, BridgeDestinationObservationSchema, BridgeIntentFieldsZod, BridgeIntentSchema,
11
9
  BridgeSourceObservationFieldsZod, BridgeSourceObservationSchema, isBridgeIntent,
12
10
  } from '@xyo-network/xl1-sdk'
13
- import type { Job } from 'bullmq'
14
- import { Queue } from 'bullmq'
15
11
  import { z } from 'zod'
16
12
 
17
- import {
18
- EthTransactionMonitor, EthTransactionPreparation, EthTransactionSubmission, getConnection, Xl1ToEthBridgeParent, Xl1TransactionMonitor,
19
- Xl1TransactionPreparation, Xl1TransactionSubmission,
20
- } from '../../../../../queue/index.ts'
13
+ import { getStatusQueueJobs, getXl1ToEthQueues } from '../../../../../queue/index.ts'
21
14
  import { getRemoteChainIdZod } from '../pathParams/index.ts'
22
15
 
23
16
  const BridgeIntentResponseZod = PayloadZodStrictOfSchema(BridgeIntentSchema)
@@ -37,78 +30,6 @@ export const BridgeToRemoteStatusResponseZod = z.union([
37
30
  ])
38
31
  export type BridgeToRemoteStatusResponse = z.infer<typeof BridgeToRemoteStatusResponseZod>
39
32
 
40
- interface StatusQueues {
41
- ethTransactionMonitor: Queue
42
- ethTransactionPreparation: Queue
43
- ethTransactionSubmission: Queue
44
- xl1ToEthBridgeParent: Queue
45
- xl1TransactionMonitor: Queue
46
- xl1TransactionPreparation: Queue
47
- xl1TransactionSubmission: Queue
48
- }
49
-
50
- let statusQueues: StatusQueues | undefined
51
-
52
- const getStatusQueues = (config: BridgeConfig): StatusQueues => {
53
- if (statusQueues) return statusQueues
54
- const connection = getConnection(config)
55
- statusQueues = {
56
- ethTransactionMonitor: new Queue(EthTransactionMonitor.queueName, { connection }),
57
- ethTransactionPreparation: new Queue(EthTransactionPreparation.queueName, { connection }),
58
- ethTransactionSubmission: new Queue(EthTransactionSubmission.queueName, { connection }),
59
- xl1ToEthBridgeParent: new Queue(Xl1ToEthBridgeParent.queueName, { connection }),
60
- xl1TransactionMonitor: new Queue(Xl1TransactionMonitor.queueName, { connection }),
61
- xl1TransactionPreparation: new Queue(Xl1TransactionPreparation.queueName, { connection }),
62
- xl1TransactionSubmission: new Queue(Xl1TransactionSubmission.queueName, { connection }),
63
- }
64
- return statusQueues
65
- }
66
-
67
- interface StatusQueueJobs {
68
- ethTransactionMonitorJob?: Job
69
- ethTransactionPreparationJob?: Job
70
- ethTransactionSubmissionJob?: Job
71
- xl1ToEthBridgeParentJob?: Job
72
- xl1TransactionMonitorJob?: Job
73
- xl1TransactionPreparationJob?: Job
74
- xl1TransactionSubmissionJob?: Job
75
- }
76
-
77
- /**
78
- * Checks all the relevant status queues for a job with the given id and returns the jobs found in an object
79
- * @param queues The status queues to check for jobs
80
- * @param jobId The job id to look for in the queues, which is the same as the tx hash of the bridge transaction
81
- * @returns An object containing the jobs found in the status queues corresponding to the given job id
82
- */
83
- const getStatusQueueJobs = async (queues: StatusQueues, jobId: string): Promise<StatusQueueJobs> => {
84
- const [
85
- ethTransactionMonitorJob,
86
- ethTransactionPreparationJob,
87
- ethTransactionSubmissionJob,
88
- xl1ToEthBridgeParentJob,
89
- xl1TransactionMonitorJob,
90
- xl1TransactionPreparationJob,
91
- xl1TransactionSubmissionJob,
92
- ] = await Promise.all([
93
- queues.ethTransactionMonitor.getJob(jobId),
94
- queues.ethTransactionPreparation.getJob(jobId),
95
- queues.ethTransactionSubmission.getJob(jobId),
96
- queues.xl1ToEthBridgeParent.getJob(jobId),
97
- queues.xl1TransactionMonitor.getJob(jobId),
98
- queues.xl1TransactionPreparation.getJob(jobId),
99
- queues.xl1TransactionSubmission.getJob(jobId),
100
- ])
101
- return {
102
- ethTransactionMonitorJob,
103
- ethTransactionPreparationJob,
104
- ethTransactionSubmissionJob,
105
- xl1ToEthBridgeParentJob,
106
- xl1TransactionMonitorJob,
107
- xl1TransactionPreparationJob,
108
- xl1TransactionSubmissionJob,
109
- }
110
- }
111
-
112
33
  export const makeBridgeToRemoteStatusRoute = (config: BridgeConfig): RouteDefinition => {
113
34
  const params = z.object({
114
35
  chainId: getRemoteChainIdZod(config),
@@ -122,18 +43,18 @@ export const makeBridgeToRemoteStatusRoute = (config: BridgeConfig): RouteDefini
122
43
  path: '/bridge/chains/:chainId/bridgeToRemote/status/:nonce',
123
44
  handlers: validateRequest(async (req, res) => {
124
45
  const jobId = req.params.nonce
125
- const result: z.infer<typeof BridgeToRemoteStatusResponseZod> = [] as unknown as z.infer<typeof BridgeToRemoteStatusResponseZod>
126
- const queues = getStatusQueues(config)
46
+ const result: z.infer<typeof BridgeToRemoteStatusResponseZod> = [] as z.infer<typeof BridgeToRemoteStatusResponseZod>
47
+ const queues = getXl1ToEthQueues(config)
127
48
 
128
49
  const statusQueueJobs = await getStatusQueueJobs(queues, jobId)
129
50
 
130
- // Check for the transaction hash in any of the queues
131
- const tx = Object.values(statusQueueJobs).map(job => job?.data?.tx as SignedHydratedTransaction | undefined).find(tx => isDefined(tx))
51
+ // Check for the transaction hash in the parent job
52
+ const tx = statusQueueJobs.xl1ToEthBridgeParentJob?.data?.tx
132
53
 
133
- // If the transaction does not exist in any of them return Not Found
54
+ // If the job does not exist return Not Found
134
55
  if (!tx) return res.sendStatus(404)
135
56
 
136
- // If the transaction is not the right shape return Not Found
57
+ // If the tx is not the right shape for bridging return Not Found
137
58
  const bridgeIntent = tx[1].find(isBridgeIntent)
138
59
  if (!bridgeIntent) return res.sendStatus(404)
139
60
 
@@ -143,26 +64,31 @@ export const makeBridgeToRemoteStatusRoute = (config: BridgeConfig): RouteDefini
143
64
  // Check the state of the XL1 monitor job to determine if we can include the source observation
144
65
  const { xl1TransactionMonitorJob } = statusQueueJobs
145
66
  const xl1MonitorState = xl1TransactionMonitorJob ? await xl1TransactionMonitorJob.getState() : undefined
146
- const srcConfirmation = asHex(jobId)
147
- if (xl1MonitorState === 'completed' && isDefined(srcConfirmation)) {
148
- const bridgeCommonFieldsZod = z.object({}).extend(BridgeSourceObservationFieldsZod.shape)
149
- const bridgeCommonFields = bridgeCommonFieldsZod.parse(bridgeIntent)
150
- const observation: BridgeSourceObservation = {
151
- schema: BridgeSourceObservationSchema, ...bridgeCommonFields, srcConfirmation,
67
+ if (xl1MonitorState === 'completed') {
68
+ const srcConfirmation = xl1TransactionMonitorJob?.returnvalue?.submissionHash
69
+ if (isDefined(srcConfirmation)) {
70
+ const schema = BridgeSourceObservationSchema
71
+ const bridgeSourceObservationFields = BridgeSourceObservationFieldsZod.parse(bridgeIntent)
72
+ const observation: BridgeSourceObservation = {
73
+ schema, ...bridgeSourceObservationFields, srcConfirmation,
74
+ }
75
+ result[1] = observation
152
76
  }
153
- result[1] = observation
154
77
  }
155
78
 
156
79
  // Check the state of the ETH monitor job to determine if we can include the destination observation
157
80
  const { ethTransactionMonitorJob } = statusQueueJobs
158
81
  const ethMonitorState = ethTransactionMonitorJob ? await ethTransactionMonitorJob.getState() : undefined
159
- const submissionHash = (ethTransactionMonitorJob?.returnvalue as { submissionHash?: string } | undefined)?.submissionHash
160
- const destConfirmation = asHex(submissionHash)
161
- if (ethMonitorState === 'completed' && isDefined(submissionHash)) {
162
- const bridgeDestinationFieldsZod = z.object({}).extend(BridgeDestinationObservationFieldsZod.shape)
163
- const bridgeDestinationFields = bridgeDestinationFieldsZod.parse({ ...bridgeIntent, destConfirmation })
164
- const observation: BridgeDestinationObservation = { schema: BridgeDestinationObservationSchema, ...bridgeDestinationFields }
165
- result[2] = observation
82
+ if (ethMonitorState === 'completed') {
83
+ const destConfirmation = asHex(ethTransactionMonitorJob?.returnvalue?.submissionHash)
84
+ if (isDefined(destConfirmation)) {
85
+ const schema = BridgeDestinationObservationSchema
86
+ const bridgeDestinationObservationFields = BridgeDestinationObservationFieldsZod.parse(bridgeIntent)
87
+ const observation: BridgeDestinationObservation = {
88
+ schema, ...bridgeDestinationObservationFields, destConfirmation,
89
+ }
90
+ result[2] = observation
91
+ }
166
92
  }
167
93
 
168
94
  res.json(result)
@@ -13,6 +13,7 @@ export const getServer = async (context: BridgeConfigContext, gateway: XyoGatewa
13
13
  const { port } = config
14
14
  const app = getApp(config, gateway)
15
15
  const services = await getServices(context, gateway)
16
+ app.services = services
16
17
  addWorkers(config, services)
17
18
  const server = app.listen(port, hostname, () => logger?.log(`[Bridge] Server listening at http://${hostname}:${port}`))
18
19
  server.setTimeout(20_000)
@@ -1,5 +1,5 @@
1
1
  import type { Hash } from '@xylabs/sdk-js'
2
- import type { AccountInstance } from '@xyo-network/account-model'
2
+ import type { AccountInstance } from '@xyo-network/sdk-js'
3
3
  import type { BridgeableToken, LiquidityPoolBridge } from '@xyo-network/typechain'
4
4
  import type { IterableMap, XyoGatewayRunner } from '@xyo-network/xl1-sdk'
5
5
  import type { Provider, Wallet } from 'ethers'
@@ -1,5 +1,3 @@
1
- import { isDefined } from '@xylabs/sdk-js'
2
-
3
1
  export interface TxState<TTx, TTxHash extends string = string, TConfHash extends string = string> {
4
2
  /**
5
3
  * The confirmation hash of the transaction
@@ -14,19 +12,3 @@ export interface TxState<TTx, TTxHash extends string = string, TConfHash extends
14
12
  */
15
13
  submissionHash?: TTxHash
16
14
  }
17
-
18
- export type TxCompletionStates = 'pendingSubmission' | 'submitted' | 'confirmed' | 'failed'
19
-
20
- /**
21
- * Determines the transaction state based on the TxState object
22
- * @param state The transaction state
23
- * @returns The state of the transaction
24
- */
25
- export const getTransactionCompletionState = <T>(state: TxState<T>): TxCompletionStates => {
26
- // Prepared, submitted, and confirmed
27
- if (isDefined(state.preparedTx) && isDefined(state.submissionHash) && isDefined(state.confirmationHash)) return 'confirmed'
28
- // Prepared and submitted
29
- if (isDefined(state.preparedTx) && isDefined(state.submissionHash)) return 'submitted'
30
- // Prepared
31
- return 'pendingSubmission'
32
- }