@xyo-network/chain-bridge 1.15.2

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 (188) hide show
  1. package/LICENSE +165 -0
  2. package/README.md +96 -0
  3. package/dist/node/driver/index.d.ts +2 -0
  4. package/dist/node/driver/index.d.ts.map +1 -0
  5. package/dist/node/driver/indexer/ChainBlockIteration/ChainHashIterationService.d.ts +24 -0
  6. package/dist/node/driver/indexer/ChainBlockIteration/ChainHashIterationService.d.ts.map +1 -0
  7. package/dist/node/driver/indexer/ChainBlockIteration/index.d.ts +2 -0
  8. package/dist/node/driver/indexer/ChainBlockIteration/index.d.ts.map +1 -0
  9. package/dist/node/driver/indexer/ChainBlocksObservable.d.ts +5 -0
  10. package/dist/node/driver/indexer/ChainBlocksObservable.d.ts.map +1 -0
  11. package/dist/node/driver/indexer/ChainHydratedBlocksObservable.d.ts +11 -0
  12. package/dist/node/driver/indexer/ChainHydratedBlocksObservable.d.ts.map +1 -0
  13. package/dist/node/driver/indexer/index.d.ts +2 -0
  14. package/dist/node/driver/indexer/index.d.ts.map +1 -0
  15. package/dist/node/driver/indexer/spec/ChainBlocksObservable.spec.d.ts +2 -0
  16. package/dist/node/driver/indexer/spec/ChainBlocksObservable.spec.d.ts.map +1 -0
  17. package/dist/node/driver/indexer/spec/ChainHydratedBlocksObservable.spec.d.ts +2 -0
  18. package/dist/node/driver/indexer/spec/ChainHydratedBlocksObservable.spec.d.ts.map +1 -0
  19. package/dist/node/driver/mongo/MongoMap.d.ts +17 -0
  20. package/dist/node/driver/mongo/MongoMap.d.ts.map +1 -0
  21. package/dist/node/driver/mongo/index.d.ts +2 -0
  22. package/dist/node/driver/mongo/index.d.ts.map +1 -0
  23. package/dist/node/driver/mongo/spec/MongoMap.spec.d.ts +2 -0
  24. package/dist/node/driver/mongo/spec/MongoMap.spec.d.ts.map +1 -0
  25. package/dist/node/index.d.ts +2 -0
  26. package/dist/node/index.d.ts.map +1 -0
  27. package/dist/node/index.mjs +751 -0
  28. package/dist/node/index.mjs.map +1 -0
  29. package/dist/node/interface/index.d.ts +5 -0
  30. package/dist/node/interface/index.d.ts.map +1 -0
  31. package/dist/node/interface/interface/ChainBridgeRelayInterface.d.ts +9 -0
  32. package/dist/node/interface/interface/ChainBridgeRelayInterface.d.ts.map +1 -0
  33. package/dist/node/interface/interface/IntentIndexerInterface.d.ts +6 -0
  34. package/dist/node/interface/interface/IntentIndexerInterface.d.ts.map +1 -0
  35. package/dist/node/interface/interface/LockingProcessorInterface.d.ts +9 -0
  36. package/dist/node/interface/interface/LockingProcessorInterface.d.ts.map +1 -0
  37. package/dist/node/interface/interface/ObservationIndexerInterface.d.ts +7 -0
  38. package/dist/node/interface/interface/ObservationIndexerInterface.d.ts.map +1 -0
  39. package/dist/node/interface/interface/Params.d.ts +26 -0
  40. package/dist/node/interface/interface/Params.d.ts.map +1 -0
  41. package/dist/node/interface/interface/RelayInterface.d.ts +8 -0
  42. package/dist/node/interface/interface/RelayInterface.d.ts.map +1 -0
  43. package/dist/node/interface/interface/index.d.ts +7 -0
  44. package/dist/node/interface/interface/index.d.ts.map +1 -0
  45. package/dist/node/interface/repository/RepositoryInterface.d.ts +22 -0
  46. package/dist/node/interface/repository/RepositoryInterface.d.ts.map +1 -0
  47. package/dist/node/interface/repository/index.d.ts +2 -0
  48. package/dist/node/interface/repository/index.d.ts.map +1 -0
  49. package/dist/node/interface/service/ChainBridgeRelay/ChainBridgeRelayInterface.d.ts +9 -0
  50. package/dist/node/interface/service/ChainBridgeRelay/ChainBridgeRelayInterface.d.ts.map +1 -0
  51. package/dist/node/interface/service/ChainBridgeRelay/ChainBridgeRelayService.d.ts +31 -0
  52. package/dist/node/interface/service/ChainBridgeRelay/ChainBridgeRelayService.d.ts.map +1 -0
  53. package/dist/node/interface/service/ChainBridgeRelay/index.d.ts +2 -0
  54. package/dist/node/interface/service/ChainBridgeRelay/index.d.ts.map +1 -0
  55. package/dist/node/interface/service/ChainBridgeRelay/spec/ChainBridgeRelayService.spec.d.ts +2 -0
  56. package/dist/node/interface/service/ChainBridgeRelay/spec/ChainBridgeRelayService.spec.d.ts.map +1 -0
  57. package/dist/node/interface/service/Observer/Observer.d.ts +15 -0
  58. package/dist/node/interface/service/Observer/Observer.d.ts.map +1 -0
  59. package/dist/node/interface/service/Observer/index.d.ts +2 -0
  60. package/dist/node/interface/service/Observer/index.d.ts.map +1 -0
  61. package/dist/node/interface/service/index.d.ts +3 -0
  62. package/dist/node/interface/service/index.d.ts.map +1 -0
  63. package/dist/node/interface/util/getBridgeIntentIdentifier.d.ts +5 -0
  64. package/dist/node/interface/util/getBridgeIntentIdentifier.d.ts.map +1 -0
  65. package/dist/node/interface/util/index.d.ts +2 -0
  66. package/dist/node/interface/util/index.d.ts.map +1 -0
  67. package/dist/node/manifest/getLocator.d.ts +14 -0
  68. package/dist/node/manifest/getLocator.d.ts.map +1 -0
  69. package/dist/node/manifest/getNode.d.ts +15 -0
  70. package/dist/node/manifest/getNode.d.ts.map +1 -0
  71. package/dist/node/manifest/index.d.ts +6 -0
  72. package/dist/node/manifest/index.d.ts.map +1 -0
  73. package/dist/node/manifest/nodeManifest.d.ts +6 -0
  74. package/dist/node/manifest/nodeManifest.d.ts.map +1 -0
  75. package/dist/node/manifest/private/index.d.ts +5 -0
  76. package/dist/node/manifest/private/index.d.ts.map +1 -0
  77. package/dist/node/manifest/public/index.d.ts +14 -0
  78. package/dist/node/manifest/public/index.d.ts.map +1 -0
  79. package/dist/node/manifest/public/spec/Node.spec.d.ts +2 -0
  80. package/dist/node/manifest/public/spec/Node.spec.d.ts.map +1 -0
  81. package/dist/node/server/app.d.ts +4 -0
  82. package/dist/node/server/app.d.ts.map +1 -0
  83. package/dist/node/server/index.d.ts +11 -0
  84. package/dist/node/server/index.d.ts.map +1 -0
  85. package/dist/node/server/instrumentation.d.ts +9 -0
  86. package/dist/node/server/instrumentation.d.ts.map +1 -0
  87. package/dist/node/server/routes/addRoutes.d.ts +3 -0
  88. package/dist/node/server/routes/addRoutes.d.ts.map +1 -0
  89. package/dist/node/server/routes/address/AddressPathParams.d.ts +4 -0
  90. package/dist/node/server/routes/address/AddressPathParams.d.ts.map +1 -0
  91. package/dist/node/server/routes/address/addNodeRoutes.d.ts +3 -0
  92. package/dist/node/server/routes/address/addNodeRoutes.d.ts.map +1 -0
  93. package/dist/node/server/routes/address/get/get.d.ts +4 -0
  94. package/dist/node/server/routes/address/get/get.d.ts.map +1 -0
  95. package/dist/node/server/routes/address/get/index.d.ts +2 -0
  96. package/dist/node/server/routes/address/get/index.d.ts.map +1 -0
  97. package/dist/node/server/routes/address/index.d.ts +2 -0
  98. package/dist/node/server/routes/address/index.d.ts.map +1 -0
  99. package/dist/node/server/routes/address/post/getQueryConfig.d.ts +6 -0
  100. package/dist/node/server/routes/address/post/getQueryConfig.d.ts.map +1 -0
  101. package/dist/node/server/routes/address/post/index.d.ts +2 -0
  102. package/dist/node/server/routes/address/post/index.d.ts.map +1 -0
  103. package/dist/node/server/routes/address/post/post.d.ts +8 -0
  104. package/dist/node/server/routes/address/post/post.d.ts.map +1 -0
  105. package/dist/node/server/routes/dataLake/addDataLakeRoutes.d.ts +3 -0
  106. package/dist/node/server/routes/dataLake/addDataLakeRoutes.d.ts.map +1 -0
  107. package/dist/node/server/routes/dataLake/archivistMiddleware.d.ts +10 -0
  108. package/dist/node/server/routes/dataLake/archivistMiddleware.d.ts.map +1 -0
  109. package/dist/node/server/routes/dataLake/index.d.ts +2 -0
  110. package/dist/node/server/routes/dataLake/index.d.ts.map +1 -0
  111. package/dist/node/server/routes/healthz/get.d.ts +3 -0
  112. package/dist/node/server/routes/healthz/get.d.ts.map +1 -0
  113. package/dist/node/server/routes/healthz/index.d.ts +2 -0
  114. package/dist/node/server/routes/healthz/index.d.ts.map +1 -0
  115. package/dist/node/server/routes/index.d.ts +5 -0
  116. package/dist/node/server/routes/index.d.ts.map +1 -0
  117. package/dist/node/server/routes/rpc/index.d.ts +2 -0
  118. package/dist/node/server/routes/rpc/index.d.ts.map +1 -0
  119. package/dist/node/server/routes/rpc/routes/addRpcRoutes.d.ts +3 -0
  120. package/dist/node/server/routes/rpc/routes/addRpcRoutes.d.ts.map +1 -0
  121. package/dist/node/server/routes/rpc/routes/index.d.ts +2 -0
  122. package/dist/node/server/routes/rpc/routes/index.d.ts.map +1 -0
  123. package/dist/node/server/server.d.ts +11 -0
  124. package/dist/node/server/server.d.ts.map +1 -0
  125. package/package.json +125 -0
  126. package/src/driver/index.ts +1 -0
  127. package/src/driver/indexer/ChainBlockIteration/ChainHashIterationService.ts +87 -0
  128. package/src/driver/indexer/ChainBlockIteration/index.ts +1 -0
  129. package/src/driver/indexer/ChainBlocksObservable.ts +47 -0
  130. package/src/driver/indexer/ChainHydratedBlocksObservable.ts +23 -0
  131. package/src/driver/indexer/index.ts +1 -0
  132. package/src/driver/indexer/spec/ChainBlocksObservable.spec.ts +58 -0
  133. package/src/driver/indexer/spec/ChainHydratedBlocksObservable.spec.ts +58 -0
  134. package/src/driver/mongo/MongoMap.ts +62 -0
  135. package/src/driver/mongo/index.ts +1 -0
  136. package/src/driver/mongo/spec/MongoMap.spec.ts +67 -0
  137. package/src/index.ts +1 -0
  138. package/src/interface/index.ts +4 -0
  139. package/src/interface/interface/ChainBridgeRelayInterface.ts +9 -0
  140. package/src/interface/interface/IntentIndexerInterface.ts +7 -0
  141. package/src/interface/interface/LockingProcessorInterface.ts +10 -0
  142. package/src/interface/interface/ObservationIndexerInterface.ts +12 -0
  143. package/src/interface/interface/Params.ts +26 -0
  144. package/src/interface/interface/RelayInterface.ts +8 -0
  145. package/src/interface/interface/index.ts +6 -0
  146. package/src/interface/repository/RepositoryInterface.ts +28 -0
  147. package/src/interface/repository/index.ts +1 -0
  148. package/src/interface/service/ChainBridgeRelay/ChainBridgeRelayInterface.ts +11 -0
  149. package/src/interface/service/ChainBridgeRelay/ChainBridgeRelayService.ts +116 -0
  150. package/src/interface/service/ChainBridgeRelay/index.ts +1 -0
  151. package/src/interface/service/ChainBridgeRelay/spec/ChainBridgeRelayService.spec.ts +264 -0
  152. package/src/interface/service/Observer/Observer.ts +48 -0
  153. package/src/interface/service/Observer/index.ts +1 -0
  154. package/src/interface/service/index.ts +2 -0
  155. package/src/interface/util/getBridgeIntentIdentifier.ts +18 -0
  156. package/src/interface/util/index.ts +1 -0
  157. package/src/manifest/getLocator.ts +105 -0
  158. package/src/manifest/getNode.ts +32 -0
  159. package/src/manifest/index.ts +5 -0
  160. package/src/manifest/node.json +17 -0
  161. package/src/manifest/nodeManifest.ts +8 -0
  162. package/src/manifest/private/index.ts +4 -0
  163. package/src/manifest/public/Chain.json +138 -0
  164. package/src/manifest/public/Pending.json +35 -0
  165. package/src/manifest/public/index.ts +20 -0
  166. package/src/manifest/public/spec/Node.spec.ts +32 -0
  167. package/src/server/app.ts +37 -0
  168. package/src/server/index.ts +13 -0
  169. package/src/server/instrumentation.ts +15 -0
  170. package/src/server/routes/addRoutes.ts +11 -0
  171. package/src/server/routes/address/AddressPathParams.ts +3 -0
  172. package/src/server/routes/address/addNodeRoutes.ts +21 -0
  173. package/src/server/routes/address/get/get.ts +33 -0
  174. package/src/server/routes/address/get/index.ts +1 -0
  175. package/src/server/routes/address/index.ts +1 -0
  176. package/src/server/routes/address/post/getQueryConfig.ts +23 -0
  177. package/src/server/routes/address/post/index.ts +1 -0
  178. package/src/server/routes/address/post/post.ts +77 -0
  179. package/src/server/routes/dataLake/addDataLakeRoutes.ts +9 -0
  180. package/src/server/routes/dataLake/archivistMiddleware.ts +86 -0
  181. package/src/server/routes/dataLake/index.ts +1 -0
  182. package/src/server/routes/healthz/get.ts +20 -0
  183. package/src/server/routes/healthz/index.ts +1 -0
  184. package/src/server/routes/index.ts +5 -0
  185. package/src/server/routes/rpc/index.ts +1 -0
  186. package/src/server/routes/rpc/routes/addRpcRoutes.ts +22 -0
  187. package/src/server/routes/rpc/routes/index.ts +1 -0
  188. package/src/server/server.ts +59 -0
@@ -0,0 +1,62 @@
1
+ import { assertEx } from '@xylabs/assert'
2
+ import {
3
+ AbstractCreatable, creatable, CreatableParams,
4
+ } from '@xylabs/creatable'
5
+ import { BaseMongoSdk } from '@xylabs/mongo'
6
+ import { isNull } from '@xylabs/typeof'
7
+ import { AsynchronousMap } from '@xyo-network/chain-protocol'
8
+ import {
9
+ Document, Filter, OptionalUnlessRequiredId, WithId,
10
+ } from 'mongodb'
11
+
12
+ export interface MongoMapParams<TData extends Document = Document> extends CreatableParams {
13
+ sdk: BaseMongoSdk<TData>
14
+ }
15
+
16
+ function stripMongoId<V extends Document>(doc: WithId<V>): V {
17
+ const { _id, ...rest } = doc
18
+ return rest as unknown as V
19
+ }
20
+
21
+ @creatable()
22
+ export class MongoMap<K = string, V extends Document = Document>
23
+ extends AbstractCreatable<MongoMapParams<V>>
24
+ implements AsynchronousMap<K, V> {
25
+ get sdk(): BaseMongoSdk<V> {
26
+ return assertEx(this.params.sdk, () => 'No sdk specified')
27
+ }
28
+
29
+ async clear(): Promise<void> {
30
+ await this.sdk.deleteMany({})
31
+ }
32
+
33
+ async delete(id: K): Promise<boolean> {
34
+ const filter = { _id: id } as Filter<V>
35
+ const result = await this.sdk.deleteOne(filter)
36
+ return result.deletedCount > 0
37
+ }
38
+
39
+ async get(id: K): Promise<V | undefined> {
40
+ const filter = { _id: id } as Filter<V>
41
+ const doc = await this.sdk.findOne(filter)
42
+ return isNull(doc) ? undefined : stripMongoId(doc)
43
+ }
44
+
45
+ async has(id: K): Promise<boolean> {
46
+ const filter = { _id: id } as Filter<V>
47
+ const exists = await this.sdk.findOne(filter)
48
+ return isNull(exists) ? false : true
49
+ }
50
+
51
+ async set(id: K, data: V): Promise<this> {
52
+ const filter = { _id: id } as Filter<V>
53
+ const value = { ...data, _id: id } as OptionalUnlessRequiredId<V>
54
+ await this.sdk.replaceOne(filter, value, { upsert: true })
55
+ return this
56
+ }
57
+
58
+ override async startHandler(): Promise<void> {
59
+ await super.startHandler()
60
+ // TODO: Ensure index
61
+ }
62
+ }
@@ -0,0 +1 @@
1
+ export * from './MongoMap.ts'
@@ -0,0 +1,67 @@
1
+ import { BaseMongoSdk } from '@xylabs/mongo'
2
+ import { getBaseMongoSdkPrivateConfig, hasMongoDBConfig } from '@xyo-network/module-abstract-mongodb'
3
+ import {
4
+ beforeAll, beforeEach, describe, expect, it,
5
+ } from 'vitest'
6
+
7
+ import { MongoMap } from '../MongoMap.ts'
8
+
9
+ describe.runIf(hasMongoDBConfig())('MongoMap', () => {
10
+ interface TestDoc {
11
+ name: string
12
+ value: number
13
+ }
14
+ const collection = 'test_mongo_map'
15
+ let sdk: BaseMongoSdk<TestDoc>
16
+ let map: MongoMap<string, TestDoc>
17
+ beforeAll(async () => {
18
+ const payloadSdkConfig = getBaseMongoSdkPrivateConfig()
19
+ sdk = new BaseMongoSdk<TestDoc>({ ...payloadSdkConfig, collection })
20
+ map = await MongoMap.create<MongoMap<string, TestDoc>>({ sdk })
21
+ await map.start()
22
+ })
23
+
24
+ beforeEach(async () => {
25
+ await map.clear()
26
+ })
27
+
28
+ it('can set and get a value', async () => {
29
+ await map.set('foo', { name: 'Test', value: 42 })
30
+ const result = await map.get('foo')
31
+ expect(result).toEqual({ name: 'Test', value: 42 })
32
+ })
33
+
34
+ it('returns undefined for missing key', async () => {
35
+ const result = await map.get('missing')
36
+ expect(result).toBeUndefined()
37
+ })
38
+
39
+ it('can detect if key exists', async () => {
40
+ await map.set('exists', { name: 'Check', value: 100 })
41
+ const hasExists = await map.has('exists')
42
+ const hasMissing = await map.has('does-not-exist')
43
+ expect(hasExists).toBe(true)
44
+ expect(hasMissing).toBe(false)
45
+ })
46
+
47
+ it('can delete a key', async () => {
48
+ await map.set('delete-me', { name: 'ToDelete', value: 1 })
49
+ const deleted = await map.delete('delete-me')
50
+ const stillExists = await map.has('delete-me')
51
+ expect(deleted).toBe(true)
52
+ expect(stillExists).toBe(false)
53
+ })
54
+
55
+ it('delete returns false for non-existent key', async () => {
56
+ const deleted = await map.delete('non-existent')
57
+ expect(deleted).toBe(false)
58
+ })
59
+
60
+ it('can clear all entries', async () => {
61
+ await map.set('one', { name: 'A', value: 1 })
62
+ await map.set('two', { name: 'B', value: 2 })
63
+ await map.clear()
64
+ expect(await map.has('one')).toBe(false)
65
+ expect(await map.has('two')).toBe(false)
66
+ })
67
+ })
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './server/index.ts'
@@ -0,0 +1,4 @@
1
+ export * from './interface/index.ts'
2
+ export * from './repository/index.ts'
3
+ export * from './service/index.ts'
4
+ export * from './util/index.ts'
@@ -0,0 +1,9 @@
1
+ import type { BridgeDestinationObservation, BridgeIntent } from '@xyo-network/xl1-protocol'
2
+
3
+ import type { AsynchronousRelayInterface, SynchronousRelayInterface } from './RelayInterface.ts'
4
+
5
+ export interface AsynchronousChainBridgeRelayInterface extends AsynchronousRelayInterface<BridgeIntent, BridgeDestinationObservation> {}
6
+
7
+ export interface SynchronousChainBridgeRelayInterface extends SynchronousRelayInterface<BridgeIntent, BridgeDestinationObservation> {}
8
+
9
+ export interface ChainBridgeRelayInterface extends AsynchronousChainBridgeRelayInterface, SynchronousChainBridgeRelayInterface {}
@@ -0,0 +1,7 @@
1
+ import type { Address } from '@xylabs/hex'
2
+
3
+ export interface IntentIndexerInterface<T> {
4
+ // TODO: Hex or string instead to handle alternative addressing schemes
5
+ getIntent(src: Address, nonce: string): Promise<T | null>
6
+ getIntents(src: Address): Promise<T[]>
7
+ }
@@ -0,0 +1,10 @@
1
+ import type { Address } from '@xylabs/hex'
2
+
3
+ export interface LockingProcessorInterface<T> {
4
+ isLocked(intent: T): Promise<Address | null>
5
+ lock(processor: Address, intent: T): Promise<boolean>
6
+ }
7
+
8
+ export interface UnlockingProcessorInterface<T> extends LockingProcessorInterface<T> {
9
+ unlock(processor: Address, intent: T): Promise<boolean>
10
+ }
@@ -0,0 +1,12 @@
1
+ import type {
2
+ BridgeDestinationObservation, BridgeIntent, BridgeSourceObservation,
3
+ } from '@xyo-network/xl1-protocol'
4
+
5
+ export interface ObservationIndexerInterface<
6
+ TObservation extends BridgeSourceObservation | BridgeDestinationObservation,
7
+ TIntent extends BridgeIntent = BridgeIntent,
8
+ > {
9
+ addObservation(observation: TObservation, intent: TIntent): Promise<boolean>
10
+ getIntentForObservation(observation: TObservation): Promise<TIntent | null>
11
+ getObservationForIntent(intent: TIntent): Promise<TObservation | null>
12
+ }
@@ -0,0 +1,26 @@
1
+ import { type BaseAccountableServiceParams } from '@xyo-network/chain-services'
2
+ import type {
3
+ BridgeDestinationObservation, BridgeIntent, BridgeSourceObservation,
4
+ } from '@xyo-network/xl1-protocol'
5
+
6
+ import type { ChainBridgeRelayInterface } from './ChainBridgeRelayInterface.ts'
7
+ import type { IntentIndexerInterface } from './IntentIndexerInterface.ts'
8
+ import type { LockingProcessorInterface, UnlockingProcessorInterface } from './LockingProcessorInterface.ts'
9
+ import type { ObservationIndexerInterface } from './ObservationIndexerInterface.ts'
10
+
11
+ export interface BridgeIntentIndexerInterface extends IntentIndexerInterface<BridgeIntent> {}
12
+ export interface BridgeSourceObservationIndexerInterface extends ObservationIndexerInterface<BridgeSourceObservation> {}
13
+ export interface BridgeDestinationObservationIndexerInterface extends ObservationIndexerInterface<BridgeDestinationObservation> {}
14
+ export interface LockingBridgeIntentProcessorInterface extends LockingProcessorInterface<BridgeIntent> {}
15
+ export interface UnlockingBridgeIntentProcessorInterface extends UnlockingProcessorInterface<BridgeIntent> {}
16
+
17
+ export type BridgeServiceCollection = {
18
+ destinationObservations: BridgeDestinationObservationIndexerInterface
19
+ destinationRelay: ChainBridgeRelayInterface
20
+ intentProcessed: LockingBridgeIntentProcessorInterface
21
+ intentProcessing: UnlockingBridgeIntentProcessorInterface
22
+ intents: BridgeIntentIndexerInterface
23
+ sourceObservations: BridgeSourceObservationIndexerInterface
24
+ }
25
+
26
+ export type BridgeServiceParams = BaseAccountableServiceParams & BridgeServiceCollection
@@ -0,0 +1,8 @@
1
+ export interface SynchronousRelayInterface<T, U> {
2
+ relaySync(message: T): Promise<U | null>
3
+ }
4
+
5
+ export interface AsynchronousRelayInterface<T, U> {
6
+ beginRelay(message: T): Promise<boolean>
7
+ onDestinationObservation(observation: U): Promise<boolean>
8
+ }
@@ -0,0 +1,6 @@
1
+ export * from './ChainBridgeRelayInterface.ts'
2
+ export * from './IntentIndexerInterface.ts'
3
+ export * from './LockingProcessorInterface.ts'
4
+ export * from './ObservationIndexerInterface.ts'
5
+ export * from './Params.ts'
6
+ export * from './RelayInterface.ts'
@@ -0,0 +1,28 @@
1
+ import type { Address } from '@xylabs/hex'
2
+ import type {
3
+ BridgeDestinationObservation, BridgeIntent, BridgeSourceObservation,
4
+ } from '@xyo-network/xl1-protocol'
5
+
6
+ export interface IntentRepository {
7
+ getBridgeIntents(src: Address, nonce?: string): Promise<BridgeIntent[]>
8
+ }
9
+
10
+ export interface SourceObservationRepository {
11
+ getBridgeSourceObservation(intent: BridgeIntent): Promise<BridgeSourceObservation[]>
12
+ }
13
+
14
+ export interface DestinationObservationRepository {
15
+ getBridgeDestinationObservation(intent: BridgeIntent): Promise<BridgeDestinationObservation[]>
16
+ }
17
+
18
+ export interface IntentProcessingRepository {
19
+ isIntentProcessing(intent: BridgeIntent): Promise<boolean>
20
+ markIntentProcessed(intent: BridgeIntent): Promise<void>
21
+ markIntentProcessing(intent: BridgeIntent): Promise<void>
22
+ unmarkIntentProcessing(intent: BridgeIntent): Promise<void>
23
+ }
24
+
25
+ export interface IntentProcessedRepository {
26
+ isIntentProcessed(intent: BridgeIntent): Promise<boolean>
27
+ markIntentProcessed(intent: BridgeIntent): Promise<void>
28
+ }
@@ -0,0 +1 @@
1
+ export * from './RepositoryInterface.ts'
@@ -0,0 +1,11 @@
1
+ import type { BridgeDestinationObservation, BridgeIntent } from '@xyo-network/xl1-protocol'
2
+
3
+ import type { AsynchronousRelayInterface, SynchronousRelayInterface } from '../../interface/index.ts'
4
+
5
+ export interface BlockingChainBridgeRelay extends SynchronousRelayInterface<BridgeIntent, BridgeDestinationObservation> {}
6
+
7
+ export interface ChainBridgeRelay extends AsynchronousRelayInterface<BridgeIntent, BridgeDestinationObservation> {}
8
+
9
+ export interface ChainBridgeRelayInterface extends
10
+ AsynchronousRelayInterface<BridgeIntent, BridgeDestinationObservation>,
11
+ SynchronousRelayInterface<BridgeIntent, BridgeDestinationObservation> {}
@@ -0,0 +1,116 @@
1
+ import { assertEx } from '@xylabs/assert'
2
+ import { isNull } from '@xylabs/typeof'
3
+ import { BaseAccountableService } from '@xyo-network/chain-services'
4
+ import type { BridgeDestinationObservation, BridgeIntent } from '@xyo-network/xl1-protocol'
5
+
6
+ import type {
7
+ BridgeDestinationObservationIndexerInterface, BridgeIntentIndexerInterface,
8
+ BridgeServiceParams,
9
+ BridgeSourceObservationIndexerInterface, ChainBridgeRelayInterface, LockingBridgeIntentProcessorInterface,
10
+ UnlockingBridgeIntentProcessorInterface,
11
+ } from '../../interface/index.ts'
12
+
13
+ export class ChainBridgeRelayService<TParams extends BridgeServiceParams = BridgeServiceParams>
14
+ extends BaseAccountableService<TParams> implements ChainBridgeRelayInterface {
15
+ protected get account() {
16
+ return assertEx(this.params.account, () => 'account is required')
17
+ }
18
+
19
+ protected get destinationObservations(): BridgeDestinationObservationIndexerInterface {
20
+ return assertEx(this.params.destinationObservations, () => 'destinationObservations is required')
21
+ }
22
+
23
+ protected get destinationRelay(): ChainBridgeRelayInterface {
24
+ return assertEx(this.params.destinationRelay, () => 'destinationRelay is required')
25
+ }
26
+
27
+ protected get intentProcessed(): LockingBridgeIntentProcessorInterface {
28
+ return assertEx(this.params.intentProcessed, () => 'intentProcessed is required')
29
+ }
30
+
31
+ protected get intentProcessing(): UnlockingBridgeIntentProcessorInterface {
32
+ return assertEx(this.params.intentProcessing, () => 'intentProcessing is required')
33
+ }
34
+
35
+ protected get intentResourceAccess(): BridgeIntentIndexerInterface {
36
+ return assertEx(this.params.intents, () => 'intents is required')
37
+ }
38
+
39
+ protected get sourceObservations(): BridgeSourceObservationIndexerInterface {
40
+ return assertEx(this.params.sourceObservations, () => 'sourceObservations is required')
41
+ }
42
+
43
+ /**
44
+ * Begins the relay process for a given BridgeIntent.
45
+ * @param bridgeIntent The bridgeIntent to begin relaying
46
+ * @returns True if the relay was started, false otherwise
47
+ */
48
+ async beginRelay(bridgeIntent: BridgeIntent): Promise<boolean> {
49
+ try {
50
+ // Ensure source observation exists
51
+ const bridgeSourceObservation = await this.sourceObservations.getObservationForIntent(bridgeIntent)
52
+ if (!isNull(bridgeSourceObservation)) {
53
+ const canProcess = await this.intentProcessing.lock(this.account.address, bridgeIntent)
54
+ if (canProcess) {
55
+ await this.destinationRelay.beginRelay(bridgeIntent)
56
+ return true
57
+ }
58
+ }
59
+ } catch {
60
+ await this.intentProcessing.unlock(this.account.address, bridgeIntent)
61
+ }
62
+ return false
63
+ }
64
+
65
+ /**
66
+ * Handles a BridgeDestinationObservation.
67
+ * @param bridgeDestinationObservation The BridgeDestinationObservation to process
68
+ * @returns True if the observation was processed as a completion for an outstanding relay, false otherwise
69
+ */
70
+ async onDestinationObservation(bridgeDestinationObservation: BridgeDestinationObservation): Promise<boolean> {
71
+ // Ensure intent exists
72
+ const bridgeIntent = await this.destinationObservations.getIntentForObservation(bridgeDestinationObservation)
73
+ if (!isNull(bridgeIntent)) {
74
+ // Ensure not already processed
75
+ const processed = await this.intentProcessed.isLocked(bridgeIntent)
76
+ if (isNull(processed)) {
77
+ // Ensure we are the processor
78
+ const processor = await this.intentProcessing.isLocked(bridgeIntent)
79
+ if (!isNull(processor) && processor === this.account.address) {
80
+ // Mark as completed
81
+ await this.intentProcessed.lock(this.account.address, bridgeIntent)
82
+ await this.intentProcessing.unlock(this.account.address, bridgeIntent)
83
+ return true
84
+ }
85
+ }
86
+ }
87
+ return false
88
+ }
89
+
90
+ /**
91
+ * Relays a given BridgeIntent.
92
+ * @param bridgeIntent The BridgeIntent to relay
93
+ * @returns Relays the intent in a blocking manner, returning the resulting BridgeDestinationObservation if successful
94
+ */
95
+ async relaySync(bridgeIntent: BridgeIntent): Promise<BridgeDestinationObservation | null> {
96
+ let canProcess = isNull(await this.intentProcessing.isLocked(bridgeIntent))
97
+ try {
98
+ canProcess = await this.intentProcessing.lock(this.account.address, bridgeIntent)
99
+ if (canProcess) {
100
+ const result = await this.destinationRelay.relaySync(bridgeIntent)
101
+ if (!isNull(result)) {
102
+ // TODO: How to handle partial success here where relay succeeded but we didn't record result successfully?
103
+ // Prefer async observer pattern instead?
104
+ await this.destinationObservations.addObservation(result, bridgeIntent)
105
+ await this.intentProcessed.lock(this.account.address, bridgeIntent)
106
+ return result
107
+ }
108
+ }
109
+ } finally {
110
+ if (!isNull(bridgeIntent)) {
111
+ await this.intentProcessing.unlock(this.account.address, bridgeIntent)
112
+ }
113
+ }
114
+ return null
115
+ }
116
+ }
@@ -0,0 +1 @@
1
+ export * from './ChainBridgeRelayService.ts'
@@ -0,0 +1,264 @@
1
+ import { toAddress, toHex } from '@xylabs/hex'
2
+ import { HDWallet } from '@xyo-network/wallet'
3
+ import type { WalletInstance } from '@xyo-network/wallet-model'
4
+ import type {
5
+ BridgeDestinationObservation, BridgeIntent, BridgeSourceObservation, Chain,
6
+ } from '@xyo-network/xl1-protocol'
7
+ import {
8
+ AttoXL1ConvertFactor, BridgeDestinationObservationSchema, BridgeIntentSchema,
9
+ BridgeSourceObservationSchema,
10
+ } from '@xyo-network/xl1-protocol'
11
+ import type { Config } from '@xyo-network/xl1-protocol-sdk'
12
+ import { getDefaultConfig } from '@xyo-network/xl1-protocol-sdk'
13
+ import {
14
+ beforeAll, describe, expect, it, vi,
15
+ } from 'vitest'
16
+
17
+ import type {
18
+ BridgeDestinationObservationIndexerInterface, BridgeIntentIndexerInterface, BridgeServiceParams,
19
+ BridgeSourceObservationIndexerInterface,
20
+ ChainBridgeRelayInterface,
21
+ LockingBridgeIntentProcessorInterface, UnlockingBridgeIntentProcessorInterface,
22
+ } from '../../../interface/index.ts'
23
+ import { ChainBridgeRelayService } from '../ChainBridgeRelayService.ts'
24
+
25
+ describe('ChainBridgeRelayService', () => {
26
+ let account: WalletInstance
27
+ let config: Config
28
+ let destinationObservations: BridgeDestinationObservationIndexerInterface
29
+ let destinationRelay: ChainBridgeRelayInterface
30
+ let intentProcessed: LockingBridgeIntentProcessorInterface
31
+ let intentProcessing: UnlockingBridgeIntentProcessorInterface
32
+ let intents: BridgeIntentIndexerInterface
33
+ let sourceObservations: BridgeSourceObservationIndexerInterface
34
+ let relay: ChainBridgeRelayService
35
+
36
+ const srcAmount = toHex(100n * AttoXL1ConvertFactor.xl1) // 100 XL1 in AttoXL1
37
+ const destAmount = srcAmount // 1:1 for test
38
+
39
+ const xl1ChainId: Chain = toHex('dd381fbb392c85160d8b0453e446757b12384046')
40
+ const ethChainId = toHex('0x1')
41
+
42
+ const xl1Address = toAddress('1111111111111111111111111111111111111111')
43
+ const ethAddress = toAddress('0x2222222222222222222222222222222222222222')
44
+
45
+ const bridgeableTokenContract = toHex('0x3333333333333333333333333333333333333333')
46
+
47
+ const nonce = 'd5eeff33-d5bc-4aca-9ecb-3406c02a5dc4'
48
+
49
+ const xl1TxHash = toHex('0x4444444444444444444444444444444444444444444444444444444444444444') // Some XL1 tx hash
50
+ const ethTxHash = toHex('0x5555555555555555555555555555555555555555555555555555555555555555') // Some Eth tx hash
51
+
52
+ const intent: BridgeIntent = {
53
+ // Source
54
+ src: xl1ChainId, // From XL1
55
+ srcAddress: xl1Address, // From XL1 sender
56
+ srcAmount,
57
+ srcToken: xl1ChainId, // In XL1
58
+
59
+ // Destination
60
+ dest: ethChainId, // To Ethereum
61
+ destAddress: ethAddress,
62
+ destAmount,
63
+ destToken: bridgeableTokenContract,
64
+
65
+ // Details
66
+ nonce,
67
+
68
+ schema: BridgeIntentSchema,
69
+ }
70
+
71
+ const srcObservation: BridgeSourceObservation = {
72
+ // Source
73
+ src: xl1ChainId, // From XL1
74
+ srcAddress: xl1Address, // From XL1 sender
75
+ srcAmount,
76
+ srcToken: xl1ChainId, // In XL1
77
+
78
+ // Destination
79
+ dest: ethChainId, // To Ethereum
80
+ destAddress: ethAddress,
81
+ destAmount,
82
+ destToken: bridgeableTokenContract,
83
+
84
+ // Observation
85
+ srcConfirmation: xl1TxHash,
86
+
87
+ schema: BridgeSourceObservationSchema,
88
+ }
89
+
90
+ const destObservation: BridgeDestinationObservation = {
91
+ // Source
92
+ src: xl1ChainId, // From XL1
93
+ srcAddress: xl1Address, // From XL1 sender
94
+ srcAmount,
95
+ srcToken: xl1ChainId, // In XL1
96
+
97
+ // Destination
98
+ dest: ethChainId, // To Ethereum
99
+ destAddress: ethAddress,
100
+ destAmount,
101
+ destToken: bridgeableTokenContract,
102
+
103
+ // Observation
104
+ destConfirmation: ethTxHash,
105
+
106
+ schema: BridgeDestinationObservationSchema,
107
+ }
108
+
109
+ beforeAll(async () => {
110
+ account = await HDWallet.random()
111
+ config = getDefaultConfig()
112
+
113
+ intents = {
114
+ getIntent: vi.fn().mockResolvedValue(null),
115
+ getIntents: vi.fn().mockResolvedValue([]),
116
+ }
117
+
118
+ sourceObservations = {
119
+ addObservation: vi.fn().mockResolvedValue(true),
120
+ getIntentForObservation: vi.fn().mockResolvedValue(null),
121
+ getObservationForIntent: vi.fn().mockResolvedValue(null),
122
+ }
123
+
124
+ destinationObservations = {
125
+ addObservation: vi.fn().mockResolvedValue(true),
126
+ getIntentForObservation: vi.fn().mockResolvedValue(null),
127
+ getObservationForIntent: vi.fn().mockResolvedValue(null),
128
+ }
129
+
130
+ intentProcessed = {
131
+ isLocked: vi.fn().mockResolvedValue(null),
132
+ lock: vi.fn().mockResolvedValue(true),
133
+ }
134
+
135
+ intentProcessing = {
136
+ isLocked: vi.fn().mockResolvedValue(null),
137
+ lock: vi.fn().mockResolvedValue(true),
138
+ unlock: vi.fn().mockResolvedValue(true),
139
+ }
140
+
141
+ destinationRelay = {
142
+ beginRelay: vi.fn().mockResolvedValue(true),
143
+ relaySync: vi.fn().mockResolvedValue(null),
144
+ onDestinationObservation: vi.fn().mockResolvedValue(false),
145
+ }
146
+
147
+ const params: BridgeServiceParams = {
148
+ account,
149
+ config,
150
+ destinationObservations,
151
+ destinationRelay,
152
+ intentProcessed,
153
+ intentProcessing,
154
+ intents,
155
+ sourceObservations,
156
+ }
157
+ relay = await ChainBridgeRelayService.create(params)
158
+ })
159
+ describe('beginRelay', () => {
160
+ it('should do nothing if source observation is null', async () => {
161
+ // Arrange
162
+ sourceObservations.getObservationForIntent = vi.fn().mockResolvedValue(null)
163
+
164
+ // Act
165
+ const result = await relay.beginRelay(intent)
166
+
167
+ // Assert
168
+ expect(intentProcessing.lock).not.toHaveBeenCalled()
169
+ expect(result).toBe(false)
170
+ })
171
+
172
+ it('should lock and call destinationRelay.beginRelay if observation exists', async () => {
173
+ // Arrange
174
+ sourceObservations.getObservationForIntent = vi.fn().mockResolvedValue(srcObservation)
175
+ intentProcessing.lock = vi.fn().mockResolvedValue(true)
176
+ destinationRelay.beginRelay = vi.fn().mockResolvedValue(true)
177
+
178
+ // Act
179
+ const result = await relay.beginRelay(intent)
180
+
181
+ // Assert
182
+ expect(sourceObservations.getObservationForIntent).toHaveBeenCalledWith(intent)
183
+ expect(intentProcessing.lock).toHaveBeenCalledWith(account.address, intent)
184
+ expect(destinationRelay.beginRelay).toHaveBeenCalledWith(intent)
185
+ expect(result).toBe(true)
186
+ })
187
+
188
+ it('should unlock if an exception is thrown', async () => {
189
+ // Arrange
190
+ sourceObservations.getObservationForIntent = vi.fn().mockResolvedValue(srcObservation)
191
+ intentProcessing.lock = vi.fn().mockResolvedValue(true)
192
+ destinationRelay.beginRelay = vi.fn().mockImplementation(() => {
193
+ throw new Error('boom')
194
+ })
195
+ intentProcessing.unlock = vi.fn()
196
+
197
+ // Act
198
+ const result = await relay.beginRelay(intent)
199
+
200
+ // Assert
201
+ expect(intentProcessing.unlock).toHaveBeenCalledWith(account.address, intent)
202
+ expect(result).toBe(false)
203
+ })
204
+ })
205
+ describe('onBridgeDestinationObservation', () => {
206
+ it('should return false if no intent exists for observation', async () => {
207
+ // Arrange
208
+ destinationObservations.getIntentForObservation = vi.fn().mockResolvedValue(null)
209
+
210
+ // Act
211
+ const result = await relay.onDestinationObservation(destObservation)
212
+
213
+ // Assert
214
+ expect(intentProcessed.isLocked).not.toHaveBeenCalled()
215
+ expect(result).toBe(false)
216
+ })
217
+
218
+ it('should return false if already processed', async () => {
219
+ // Arrange
220
+ const other = await HDWallet.random()
221
+ destinationObservations.getIntentForObservation = vi.fn().mockResolvedValue(intent)
222
+ intentProcessed.isLocked = vi.fn().mockResolvedValue(other.address)
223
+
224
+ // Act
225
+ const result = await relay.onDestinationObservation(destObservation)
226
+
227
+ // Assert
228
+ expect(intentProcessed.isLocked).toHaveBeenCalledWith(intent)
229
+ expect(result).toBe(false)
230
+ })
231
+
232
+ it('should return false if not the processor', async () => {
233
+ // Arrange
234
+ const other = await HDWallet.random()
235
+ destinationObservations.getIntentForObservation = vi.fn().mockResolvedValue(intent)
236
+ intentProcessed.isLocked = vi.fn().mockResolvedValue(null)
237
+ intentProcessing.isLocked = vi.fn().mockResolvedValue(other.address)
238
+
239
+ // Act
240
+ const result = await relay.onDestinationObservation(destObservation)
241
+
242
+ // Assert
243
+ expect(intentProcessing.isLocked).toHaveBeenCalledWith(intent)
244
+ expect(result).toBe(false)
245
+ })
246
+
247
+ it('should process if we are the processor', async () => {
248
+ // Arrange
249
+ destinationObservations.getIntentForObservation = vi.fn().mockResolvedValue(intent)
250
+ intentProcessed.isLocked = vi.fn().mockResolvedValue(null)
251
+ intentProcessing.isLocked = vi.fn().mockResolvedValue(account.address)
252
+ intentProcessed.lock = vi.fn().mockResolvedValue(true)
253
+ intentProcessing.unlock = vi.fn().mockResolvedValue(true)
254
+
255
+ // Act
256
+ const result = await relay.onDestinationObservation(destObservation)
257
+
258
+ // Assert
259
+ expect(intentProcessed.lock).toHaveBeenCalledWith(account.address, intent)
260
+ expect(intentProcessing.unlock).toHaveBeenCalledWith(account.address, intent)
261
+ expect(result).toBe(true)
262
+ })
263
+ })
264
+ })