@xyo-network/chain-bridge 1.15.2 → 1.15.4

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 (141) hide show
  1. package/README.md +1 -1
  2. package/dist/node/driver/indexer/ChainHydratedBlocksObservable.d.ts +4 -4
  3. package/dist/node/driver/indexer/ChainHydratedBlocksObservable.d.ts.map +1 -1
  4. package/dist/node/driver/mongo/MongoMap.d.ts +3 -2
  5. package/dist/node/driver/mongo/MongoMap.d.ts.map +1 -1
  6. package/dist/node/index.mjs +300 -683
  7. package/dist/node/index.mjs.map +1 -1
  8. package/dist/node/interface/interface/IntentIndexerInterface.d.ts +5 -3
  9. package/dist/node/interface/interface/IntentIndexerInterface.d.ts.map +1 -1
  10. package/dist/node/interface/service/Observer/ERC20TransferObserver/ERC20TransferObserver.d.ts +28 -0
  11. package/dist/node/interface/service/Observer/ERC20TransferObserver/ERC20TransferObserver.d.ts.map +1 -0
  12. package/dist/node/interface/service/Observer/ERC20TransferObserver/index.d.ts +2 -0
  13. package/dist/node/interface/service/Observer/ERC20TransferObserver/index.d.ts.map +1 -0
  14. package/dist/node/interface/service/Observer/ERC20TransferObserver/spec/ERC20TransferObserver.spec.d.ts +2 -0
  15. package/dist/node/interface/service/Observer/ERC20TransferObserver/spec/ERC20TransferObserver.spec.d.ts.map +1 -0
  16. package/dist/node/interface/service/Observer/LiquidityPoolBridgeObserver/LiquidityPoolBridgeObserver.d.ts +36 -0
  17. package/dist/node/interface/service/Observer/LiquidityPoolBridgeObserver/LiquidityPoolBridgeObserver.d.ts.map +1 -0
  18. package/dist/node/interface/service/Observer/LiquidityPoolBridgeObserver/index.d.ts +2 -0
  19. package/dist/node/interface/service/Observer/LiquidityPoolBridgeObserver/index.d.ts.map +1 -0
  20. package/dist/node/interface/service/Observer/LiquidityPoolBridgeObserver/spec/LiquidityPoolBridgeObserver.spec.d.ts +2 -0
  21. package/dist/node/interface/service/Observer/LiquidityPoolBridgeObserver/spec/LiquidityPoolBridgeObserver.spec.d.ts.map +1 -0
  22. package/dist/node/interface/service/Observer/Observer.d.ts +1 -1
  23. package/dist/node/interface/service/Observer/Observer.d.ts.map +1 -1
  24. package/dist/node/interface/service/{ChainBridgeRelay → Relay/ChainBridgeRelay}/ChainBridgeRelayInterface.d.ts +1 -1
  25. package/dist/node/interface/service/Relay/ChainBridgeRelay/ChainBridgeRelayInterface.d.ts.map +1 -0
  26. package/dist/node/interface/service/{ChainBridgeRelay → Relay/ChainBridgeRelay}/ChainBridgeRelayService.d.ts +1 -1
  27. package/dist/node/interface/service/Relay/ChainBridgeRelay/ChainBridgeRelayService.d.ts.map +1 -0
  28. package/dist/node/interface/service/Relay/ChainBridgeRelay/index.d.ts.map +1 -0
  29. package/dist/node/interface/service/Relay/ChainBridgeRelay/spec/ChainBridgeRelayService.spec.d.ts.map +1 -0
  30. package/dist/node/interface/service/Relay/LiquidityPoolBridgeRelay/LiquidityPoolBridgeRelay.d.ts +57 -0
  31. package/dist/node/interface/service/Relay/LiquidityPoolBridgeRelay/LiquidityPoolBridgeRelay.d.ts.map +1 -0
  32. package/dist/node/interface/service/Relay/LiquidityPoolBridgeRelay/index.d.ts +2 -0
  33. package/dist/node/interface/service/Relay/LiquidityPoolBridgeRelay/index.d.ts.map +1 -0
  34. package/dist/node/interface/service/Relay/LiquidityPoolBridgeRelay/spec/LiquidityPoolBridgeRelay.spec.d.ts +2 -0
  35. package/dist/node/interface/service/Relay/LiquidityPoolBridgeRelay/spec/LiquidityPoolBridgeRelay.spec.d.ts.map +1 -0
  36. package/dist/node/interface/service/Relay/index.d.ts +2 -0
  37. package/dist/node/interface/service/Relay/index.d.ts.map +1 -0
  38. package/dist/node/interface/service/index.d.ts +1 -1
  39. package/dist/node/interface/service/index.d.ts.map +1 -1
  40. package/dist/node/manifest/getLocator.d.ts.map +1 -1
  41. package/dist/node/manifest/public/index.d.ts +6 -2
  42. package/dist/node/manifest/public/index.d.ts.map +1 -1
  43. package/dist/node/server/app.d.ts +1 -2
  44. package/dist/node/server/app.d.ts.map +1 -1
  45. package/dist/node/server/routes/addRoutes.d.ts.map +1 -1
  46. package/dist/node/server/routes/bridge/addBridgeRoutes.d.ts +3 -0
  47. package/dist/node/server/routes/bridge/addBridgeRoutes.d.ts.map +1 -0
  48. package/dist/node/server/routes/bridge/index.d.ts +2 -0
  49. package/dist/node/server/routes/bridge/index.d.ts.map +1 -0
  50. package/dist/node/server/routes/bridge/middleware/index.d.ts +2 -0
  51. package/dist/node/server/routes/bridge/middleware/index.d.ts.map +1 -0
  52. package/dist/node/server/routes/bridge/middleware/requestHandlerValidator.d.ts +32 -0
  53. package/dist/node/server/routes/bridge/middleware/requestHandlerValidator.d.ts.map +1 -0
  54. package/dist/node/server/routes/bridge/routeDefinitions/getRouteDefinitions.d.ts +3 -0
  55. package/dist/node/server/routes/bridge/routeDefinitions/getRouteDefinitions.d.ts.map +1 -0
  56. package/dist/node/server/routes/bridge/routeDefinitions/index.d.ts +2 -0
  57. package/dist/node/server/routes/bridge/routeDefinitions/index.d.ts.map +1 -0
  58. package/dist/node/server/routes/bridge/routeDefinitions/pathParams/ChainIdPathParam.d.ts +6 -0
  59. package/dist/node/server/routes/bridge/routeDefinitions/pathParams/ChainIdPathParam.d.ts.map +1 -0
  60. package/dist/node/server/routes/bridge/routeDefinitions/pathParams/index.d.ts +2 -0
  61. package/dist/node/server/routes/bridge/routeDefinitions/pathParams/index.d.ts.map +1 -0
  62. package/dist/node/server/routes/bridge/routeDefinitions/routeDefinition.d.ts +8 -0
  63. package/dist/node/server/routes/bridge/routeDefinitions/routeDefinition.d.ts.map +1 -0
  64. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteStatus.d.ts +3 -0
  65. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteStatus.d.ts.map +1 -0
  66. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemote.d.ts +3 -0
  67. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemote.d.ts.map +1 -0
  68. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteEstimate.d.ts +3 -0
  69. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteEstimate.d.ts.map +1 -0
  70. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteStatus.d.ts +3 -0
  71. package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteStatus.d.ts.map +1 -0
  72. package/dist/node/server/routes/bridge/routeDefinitions/routes/index.d.ts +5 -0
  73. package/dist/node/server/routes/bridge/routeDefinitions/routes/index.d.ts.map +1 -0
  74. package/dist/node/server/routes/healthz/get.d.ts +2 -1
  75. package/dist/node/server/routes/healthz/get.d.ts.map +1 -1
  76. package/dist/node/server/routes/index.d.ts +0 -1
  77. package/dist/node/server/routes/index.d.ts.map +1 -1
  78. package/dist/node/server/server.d.ts.map +1 -1
  79. package/package.json +62 -55
  80. package/src/driver/indexer/ChainHydratedBlocksObservable.ts +5 -5
  81. package/src/driver/indexer/spec/ChainBlocksObservable.spec.ts +6 -3
  82. package/src/driver/indexer/spec/ChainHydratedBlocksObservable.spec.ts +10 -4
  83. package/src/driver/mongo/MongoMap.ts +13 -3
  84. package/src/interface/interface/IntentIndexerInterface.ts +5 -4
  85. package/src/interface/service/Observer/ERC20TransferObserver/ERC20TransferObserver.ts +181 -0
  86. package/src/interface/service/Observer/ERC20TransferObserver/index.ts +1 -0
  87. package/src/interface/service/Observer/ERC20TransferObserver/spec/ERC20TransferObserver.spec.ts +271 -0
  88. package/src/interface/service/Observer/LiquidityPoolBridgeObserver/LiquidityPoolBridgeObserver.ts +212 -0
  89. package/src/interface/service/Observer/LiquidityPoolBridgeObserver/index.ts +1 -0
  90. package/src/interface/service/Observer/LiquidityPoolBridgeObserver/spec/LiquidityPoolBridgeObserver.spec.ts +313 -0
  91. package/src/interface/service/Observer/Observer.ts +1 -1
  92. package/src/interface/service/{ChainBridgeRelay → Relay/ChainBridgeRelay}/ChainBridgeRelayInterface.ts +1 -1
  93. package/src/interface/service/{ChainBridgeRelay → Relay/ChainBridgeRelay}/ChainBridgeRelayService.ts +1 -1
  94. package/src/interface/service/{ChainBridgeRelay → Relay/ChainBridgeRelay}/spec/ChainBridgeRelayService.spec.ts +7 -5
  95. package/src/interface/service/Relay/LiquidityPoolBridgeRelay/LiquidityPoolBridgeRelay.ts +227 -0
  96. package/src/interface/service/Relay/LiquidityPoolBridgeRelay/index.ts +1 -0
  97. package/src/interface/service/Relay/LiquidityPoolBridgeRelay/spec/LiquidityPoolBridgeRelay.spec.ts +237 -0
  98. package/src/interface/service/Relay/index.ts +1 -0
  99. package/src/interface/service/index.ts +1 -1
  100. package/src/manifest/getLocator.ts +7 -6
  101. package/src/manifest/node.json +1 -1
  102. package/src/manifest/public/Chain.json +3 -109
  103. package/src/manifest/public/Ethereum.json +88 -0
  104. package/src/manifest/public/XL1.json +88 -0
  105. package/src/manifest/public/index.ts +15 -6
  106. package/src/server/app.ts +5 -12
  107. package/src/server/routes/addRoutes.ts +2 -6
  108. package/src/server/routes/bridge/addBridgeRoutes.ts +10 -0
  109. package/src/server/routes/bridge/index.ts +1 -0
  110. package/src/server/routes/bridge/middleware/index.ts +1 -0
  111. package/src/server/routes/bridge/middleware/requestHandlerValidator.ts +120 -0
  112. package/src/server/routes/bridge/routeDefinitions/getRouteDefinitions.ts +13 -0
  113. package/src/server/routes/bridge/routeDefinitions/index.ts +1 -0
  114. package/src/server/routes/bridge/routeDefinitions/pathParams/ChainIdPathParam.ts +18 -0
  115. package/src/server/routes/bridge/routeDefinitions/pathParams/index.ts +1 -0
  116. package/src/server/routes/bridge/routeDefinitions/routeDefinition.ts +18 -0
  117. package/src/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteStatus.ts +55 -0
  118. package/src/server/routes/bridge/routeDefinitions/routes/bridgeToRemote.ts +58 -0
  119. package/src/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteEstimate.ts +83 -0
  120. package/src/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteStatus.ts +55 -0
  121. package/src/server/routes/bridge/routeDefinitions/routes/index.ts +4 -0
  122. package/src/server/routes/healthz/get.ts +1 -1
  123. package/src/server/routes/index.ts +0 -2
  124. package/src/server/server.ts +11 -14
  125. package/dist/node/interface/service/ChainBridgeRelay/ChainBridgeRelayInterface.d.ts.map +0 -1
  126. package/dist/node/interface/service/ChainBridgeRelay/ChainBridgeRelayService.d.ts.map +0 -1
  127. package/dist/node/interface/service/ChainBridgeRelay/index.d.ts.map +0 -1
  128. package/dist/node/interface/service/ChainBridgeRelay/spec/ChainBridgeRelayService.spec.d.ts.map +0 -1
  129. package/dist/node/server/routes/rpc/index.d.ts +0 -2
  130. package/dist/node/server/routes/rpc/index.d.ts.map +0 -1
  131. package/dist/node/server/routes/rpc/routes/addRpcRoutes.d.ts +0 -3
  132. package/dist/node/server/routes/rpc/routes/addRpcRoutes.d.ts.map +0 -1
  133. package/dist/node/server/routes/rpc/routes/index.d.ts +0 -2
  134. package/dist/node/server/routes/rpc/routes/index.d.ts.map +0 -1
  135. package/src/manifest/public/Pending.json +0 -35
  136. package/src/server/routes/rpc/index.ts +0 -1
  137. package/src/server/routes/rpc/routes/addRpcRoutes.ts +0 -22
  138. package/src/server/routes/rpc/routes/index.ts +0 -1
  139. /package/dist/node/interface/service/{ChainBridgeRelay → Relay/ChainBridgeRelay}/index.d.ts +0 -0
  140. /package/dist/node/interface/service/{ChainBridgeRelay → Relay/ChainBridgeRelay}/spec/ChainBridgeRelayService.spec.d.ts +0 -0
  141. /package/src/interface/service/{ChainBridgeRelay → Relay/ChainBridgeRelay}/index.ts +0 -0
@@ -0,0 +1,313 @@
1
+ import { assertEx } from '@xylabs/assert'
2
+ import { delay } from '@xylabs/delay'
3
+ import type { Address } from '@xylabs/hex'
4
+ import { toAddress, toHex } from '@xylabs/hex'
5
+ import { Account } from '@xyo-network/account'
6
+ import type { BridgeableToken, LiquidityPoolBridge } from '@xyo-network/typechain'
7
+ import { BridgeableToken__factory, LiquidityPoolBridge__factory } from '@xyo-network/typechain'
8
+ import type {
9
+ BridgeDetailsFields, BridgeIntent, ChainId,
10
+ } from '@xyo-network/xl1-protocol'
11
+ import {
12
+ AttoXL1ConvertFactor, BridgeIntentSchema, BridgeSourceObservationSchema,
13
+ } from '@xyo-network/xl1-protocol'
14
+ import {
15
+ getAddress,
16
+ parseEther, Wallet, WebSocketProvider,
17
+ } from 'ethers'
18
+ import {
19
+ beforeAll, beforeEach, describe, expect, it, vi,
20
+ } from 'vitest'
21
+
22
+ import type {
23
+ BridgeDestinationObservationIndexerInterface, BridgeIntentIndexerInterface, BridgeSourceObservationIndexerInterface,
24
+ } from '../../../../interface/index.ts'
25
+ import { LiquidityPoolBridgeObserver } from '../LiquidityPoolBridgeObserver.ts'
26
+
27
+ describe.skip('LiquidityPoolBridgeObserver', () => {
28
+ // Test ERC-20 deployed to Hardhat local chain
29
+ const TOKEN_ADDRESS = '0x5FbDB2315678afecb367f032d93F642f64180aa3'
30
+ const bridgeAddress = '0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f'
31
+ const WS_URL = 'ws://127.0.0.1:8545'
32
+ const provider = new WebSocketProvider(WS_URL)
33
+ let ethBridgeSender: Wallet
34
+ let ethBridgeReceiver: Wallet
35
+ let token: BridgeableToken
36
+ let bridge: LiquidityPoolBridge
37
+ let xl1Address: Address
38
+ const xl1ChainId: ChainId = toAddress('0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9')
39
+ let sourceObservations: BridgeSourceObservationIndexerInterface
40
+ let destinationObservations: BridgeDestinationObservationIndexerInterface
41
+ let intents: BridgeIntentIndexerInterface
42
+
43
+ const srcAmountBigint = 100n * AttoXL1ConvertFactor.xl1
44
+ const srcAmount = toHex(srcAmountBigint)
45
+ const destAmount = srcAmount // 1:1 for test
46
+ const ethChainId = toHex('0x7A69')
47
+ const bridgeableTokenContract = toHex(TOKEN_ADDRESS)
48
+
49
+ const createRandomWallet = async (): Promise<Wallet> => {
50
+ // Create random account
51
+ const account = await Account.random()
52
+ expect(account.private?.hex).toBeDefined()
53
+ const key = assertEx(account.private?.hex)
54
+
55
+ // Create a wallet from the private key
56
+ const wallet = new Wallet(key, provider)
57
+ const deployer = await provider.getSigner(0)
58
+
59
+ // Fund the wallet with some ETH for gas
60
+ const fundTx = await deployer.sendTransaction({ to: wallet.address, value: parseEther('1') })
61
+ await fundTx.wait()
62
+
63
+ // Ensure wallet has ETH
64
+ const balance = await provider.getBalance(wallet.address)
65
+ expect(balance).toBeGreaterThan(0n)
66
+
67
+ // Return the created wallet
68
+ return wallet
69
+ }
70
+
71
+ const bridgeFromRemote = async (xl1Address: Address, ethBridgeReceiver: Wallet, srcAmountBigint: bigint) => {
72
+ const balance = await token.balanceOf(await bridge.getAddress())
73
+ expect(balance).toBeGreaterThanOrEqual(srcAmountBigint)
74
+ await bridge.bridgeFromRemote(getAddress(xl1Address), ethBridgeReceiver.address, srcAmountBigint)
75
+ }
76
+
77
+ const bridgeToRemote = async (ethBridgeSender: Wallet, xl1Address: Address, srcAmountBigint: bigint) => {
78
+ const balance = await token.balanceOf(ethBridgeSender.address)
79
+ expect(balance).toBeGreaterThanOrEqual(srcAmountBigint)
80
+ const destAddress = getAddress(xl1Address)
81
+ await token.connect(ethBridgeSender).approve(bridge.getAddress(), srcAmountBigint)
82
+ const nonce = await ethBridgeSender.getNonce()
83
+ await bridge.connect(ethBridgeSender).bridgeToRemote(destAddress, srcAmountBigint, { nonce })
84
+ }
85
+
86
+ beforeAll(async () => {
87
+ const deployer = await provider.getSigner(0)
88
+ ethBridgeSender = await createRandomWallet()
89
+
90
+ token = BridgeableToken__factory.connect(TOKEN_ADDRESS, deployer)
91
+ bridge = LiquidityPoolBridge__factory.connect(bridgeAddress, deployer)
92
+ const owner = await bridge.owner()
93
+ expect(owner).toBeDefined()
94
+ expect(owner).to.eq(deployer.address)
95
+ const amount = srcAmountBigint * 2n
96
+ await token.mint(await bridge.getAddress(), parseEther(amount.toString()))
97
+ expect(await token.balanceOf(await bridge.getAddress())).toBeGreaterThanOrEqual(amount)
98
+ await token.mint(ethBridgeSender.address, parseEther(amount.toString()))
99
+ expect(await token.balanceOf(ethBridgeSender.address)).toBeGreaterThanOrEqual(amount)
100
+ })
101
+
102
+ describe('when bridging from Ethereum', () => {
103
+ beforeEach(async () => {
104
+ xl1Address = (await Account.random()).address
105
+ ethBridgeReceiver = await createRandomWallet()
106
+ sourceObservations = {
107
+ addObservation: vi.fn().mockResolvedValue(true),
108
+ getObservationForIntent: vi.fn().mockResolvedValue(null),
109
+ getIntentForObservation: vi.fn().mockResolvedValue(null),
110
+ }
111
+ destinationObservations = {
112
+ addObservation: vi.fn().mockResolvedValue(true),
113
+ getObservationForIntent: vi.fn().mockResolvedValue(null),
114
+ getIntentForObservation: vi.fn().mockResolvedValue(null),
115
+ }
116
+ intents = {
117
+ addIntent: vi.fn().mockResolvedValue(true),
118
+ getIntentByNonce: vi.fn(),
119
+ getIntentsForDestination: vi.fn().mockResolvedValue([]),
120
+ getIntentsForSource: vi.fn().mockResolvedValue([]),
121
+ }
122
+ })
123
+ describe('for new transfers to XL1', () => {
124
+ it('should observe transfer', async () => {
125
+ // Arrange
126
+ // Create observer before transfer
127
+ const observer = await LiquidityPoolBridgeObserver.create({
128
+ destinationObservations,
129
+ intents,
130
+ provider,
131
+ sourceObservations,
132
+ bridgeAddress,
133
+ })
134
+ expect(observer).toBeDefined()
135
+
136
+ // Act
137
+ await bridgeToRemote(ethBridgeSender, xl1Address, srcAmountBigint)
138
+
139
+ // Wait for event
140
+ await delay(2000)
141
+
142
+ // Assert
143
+ // Ensure transfer was observed
144
+ const fields: BridgeDetailsFields = {
145
+ // Source
146
+ src: ethChainId,
147
+ srcAddress: toAddress(ethBridgeSender.address),
148
+ srcAmount,
149
+ srcToken: bridgeableTokenContract,
150
+
151
+ // Destination
152
+ dest: xl1ChainId,
153
+ destAddress: xl1Address,
154
+ destAmount,
155
+ destToken: xl1ChainId,
156
+ }
157
+ expect(intents.addIntent).toHaveBeenCalledWith({
158
+ ...fields,
159
+ nonce: expect.any(String),
160
+ schema: BridgeIntentSchema,
161
+ })
162
+ expect(sourceObservations.addObservation).toHaveBeenCalledWith({
163
+ ...fields,
164
+ nonce: expect.any(String),
165
+ schema: BridgeSourceObservationSchema,
166
+ }, {
167
+ ...fields,
168
+ nonce: expect.any(String),
169
+ schema: BridgeIntentSchema,
170
+ })
171
+ })
172
+ })
173
+ describe('for previous transfers to XL1', () => {
174
+ it('should observe transfer', async () => {
175
+ // Arrange
176
+ await bridgeToRemote(ethBridgeSender, xl1Address, srcAmountBigint)
177
+ // Wait for transaction to be mined
178
+ await delay(2000)
179
+
180
+ // Act
181
+ // Create observer after transfer
182
+ const observer = await LiquidityPoolBridgeObserver.create({
183
+ destinationObservations,
184
+ intents,
185
+ provider,
186
+ sourceObservations,
187
+ bridgeAddress,
188
+ })
189
+ expect(observer).toBeDefined()
190
+
191
+ // Assert
192
+ // Ensure transfer was observed
193
+ // Ensure transfer was observed
194
+ const fields: BridgeDetailsFields = {
195
+ // Source
196
+ src: ethChainId,
197
+ srcAddress: toAddress(ethBridgeSender.address),
198
+ srcAmount,
199
+ srcToken: bridgeableTokenContract,
200
+
201
+ // Destination
202
+ dest: xl1ChainId,
203
+ destAddress: xl1Address,
204
+ destAmount,
205
+ destToken: xl1ChainId,
206
+ }
207
+ expect(intents.addIntent).toHaveBeenCalledWith({
208
+ ...fields,
209
+ nonce: expect.any(String),
210
+ schema: BridgeIntentSchema,
211
+ })
212
+ expect(sourceObservations.addObservation).toHaveBeenCalledWith({
213
+ ...fields,
214
+ nonce: expect.any(String),
215
+ schema: BridgeSourceObservationSchema,
216
+ }, {
217
+ ...fields,
218
+ nonce: expect.any(String),
219
+ schema: BridgeIntentSchema,
220
+ })
221
+ })
222
+ })
223
+ })
224
+
225
+ describe('when bridging to Ethereum', () => {
226
+ beforeEach(async () => {
227
+ xl1Address = (await Account.random()).address
228
+ ethBridgeReceiver = await createRandomWallet()
229
+ sourceObservations = {
230
+ addObservation: vi.fn().mockResolvedValue(true),
231
+ getObservationForIntent: vi.fn().mockResolvedValue(null),
232
+ getIntentForObservation: vi.fn().mockResolvedValue(null),
233
+ }
234
+ destinationObservations = {
235
+ addObservation: vi.fn().mockResolvedValue(true),
236
+ getObservationForIntent: vi.fn().mockResolvedValue(null),
237
+ getIntentForObservation: vi.fn().mockResolvedValue(null),
238
+ }
239
+ const nonce = Date.now().toString()
240
+ const intent: BridgeIntent = {
241
+ // Source
242
+ src: xl1ChainId, // From XL1
243
+ srcAddress: xl1Address, // From XL1 sender
244
+ srcAmount,
245
+ srcToken: xl1ChainId, // In XL1
246
+
247
+ // Destination
248
+ dest: ethChainId, // To Ethereum
249
+ destAddress: toAddress(ethBridgeReceiver.address),
250
+ destAmount,
251
+ destToken: bridgeableTokenContract,
252
+
253
+ // Details
254
+ nonce,
255
+
256
+ schema: BridgeIntentSchema,
257
+ }
258
+ intents = {
259
+ addIntent: vi.fn().mockResolvedValue(true),
260
+ getIntentByNonce: vi.fn().mockResolvedValue(intent),
261
+ getIntentsForDestination: vi.fn().mockResolvedValue([intent]),
262
+ getIntentsForSource: vi.fn().mockResolvedValue([]),
263
+ }
264
+ })
265
+ describe('for new transfers from xl1 address', () => {
266
+ it('should observe transfer', async () => {
267
+ // Arrange
268
+ // Create observer before transfer
269
+ const observer = await LiquidityPoolBridgeObserver.create({
270
+ destinationObservations,
271
+ intents,
272
+ provider,
273
+ sourceObservations,
274
+ bridgeAddress,
275
+ })
276
+ expect(observer).toBeDefined()
277
+
278
+ // Act
279
+ await bridgeFromRemote(xl1Address, ethBridgeReceiver, srcAmountBigint)
280
+
281
+ // Wait for event
282
+ await delay(2000)
283
+
284
+ // Assert
285
+ // Ensure transfer was observed
286
+ expect(destinationObservations.addObservation).toHaveBeenCalled()
287
+ })
288
+ })
289
+ describe('for previous transfers from xl1 address', () => {
290
+ it('should observe transfer', async () => {
291
+ // Arrange
292
+ await bridgeFromRemote(xl1Address, ethBridgeReceiver, srcAmountBigint)
293
+ // Wait for transaction to be mined
294
+ await delay(2000)
295
+
296
+ // Act
297
+ // Create observer after transfer
298
+ const observer = await LiquidityPoolBridgeObserver.create({
299
+ destinationObservations,
300
+ intents,
301
+ provider,
302
+ sourceObservations,
303
+ bridgeAddress,
304
+ })
305
+ expect(observer).toBeDefined()
306
+
307
+ // Assert
308
+ // Ensure transfer was observed
309
+ expect(destinationObservations.addObservation).toHaveBeenCalled()
310
+ })
311
+ })
312
+ })
313
+ })
@@ -34,7 +34,7 @@ export class BridgeObserverService<TParams extends BridgeServiceParams = BridgeS
34
34
  return assertEx(this.params.intentProcessing, () => 'intentProcessing is required')
35
35
  }
36
36
 
37
- protected get intentResourceAccess(): BridgeIntentIndexerInterface {
37
+ protected get intents(): BridgeIntentIndexerInterface {
38
38
  return assertEx(this.params.intents, () => 'intents is required')
39
39
  }
40
40
 
@@ -1,6 +1,6 @@
1
1
  import type { BridgeDestinationObservation, BridgeIntent } from '@xyo-network/xl1-protocol'
2
2
 
3
- import type { AsynchronousRelayInterface, SynchronousRelayInterface } from '../../interface/index.ts'
3
+ import type { AsynchronousRelayInterface, SynchronousRelayInterface } from '../../../interface/index.ts'
4
4
 
5
5
  export interface BlockingChainBridgeRelay extends SynchronousRelayInterface<BridgeIntent, BridgeDestinationObservation> {}
6
6
 
@@ -8,7 +8,7 @@ import type {
8
8
  BridgeServiceParams,
9
9
  BridgeSourceObservationIndexerInterface, ChainBridgeRelayInterface, LockingBridgeIntentProcessorInterface,
10
10
  UnlockingBridgeIntentProcessorInterface,
11
- } from '../../interface/index.ts'
11
+ } from '../../../interface/index.ts'
12
12
 
13
13
  export class ChainBridgeRelayService<TParams extends BridgeServiceParams = BridgeServiceParams>
14
14
  extends BaseAccountableService<TParams> implements ChainBridgeRelayInterface {
@@ -2,7 +2,7 @@ import { toAddress, toHex } from '@xylabs/hex'
2
2
  import { HDWallet } from '@xyo-network/wallet'
3
3
  import type { WalletInstance } from '@xyo-network/wallet-model'
4
4
  import type {
5
- BridgeDestinationObservation, BridgeIntent, BridgeSourceObservation, Chain,
5
+ BridgeDestinationObservation, BridgeIntent, BridgeSourceObservation, ChainId,
6
6
  } from '@xyo-network/xl1-protocol'
7
7
  import {
8
8
  AttoXL1ConvertFactor, BridgeDestinationObservationSchema, BridgeIntentSchema,
@@ -19,7 +19,7 @@ import type {
19
19
  BridgeSourceObservationIndexerInterface,
20
20
  ChainBridgeRelayInterface,
21
21
  LockingBridgeIntentProcessorInterface, UnlockingBridgeIntentProcessorInterface,
22
- } from '../../../interface/index.ts'
22
+ } from '../../../../interface/index.ts'
23
23
  import { ChainBridgeRelayService } from '../ChainBridgeRelayService.ts'
24
24
 
25
25
  describe('ChainBridgeRelayService', () => {
@@ -36,7 +36,7 @@ describe('ChainBridgeRelayService', () => {
36
36
  const srcAmount = toHex(100n * AttoXL1ConvertFactor.xl1) // 100 XL1 in AttoXL1
37
37
  const destAmount = srcAmount // 1:1 for test
38
38
 
39
- const xl1ChainId: Chain = toHex('dd381fbb392c85160d8b0453e446757b12384046')
39
+ const xl1ChainId: ChainId = toHex('dd381fbb392c85160d8b0453e446757b12384046')
40
40
  const ethChainId = toHex('0x1')
41
41
 
42
42
  const xl1Address = toAddress('1111111111111111111111111111111111111111')
@@ -111,8 +111,10 @@ describe('ChainBridgeRelayService', () => {
111
111
  config = getDefaultConfig()
112
112
 
113
113
  intents = {
114
- getIntent: vi.fn().mockResolvedValue(null),
115
- getIntents: vi.fn().mockResolvedValue([]),
114
+ addIntent: vi.fn().mockResolvedValue(true),
115
+ getIntentByNonce: vi.fn(),
116
+ getIntentsForDestination: vi.fn().mockResolvedValue([]),
117
+ getIntentsForSource: vi.fn().mockResolvedValue([]),
116
118
  }
117
119
 
118
120
  sourceObservations = {
@@ -0,0 +1,227 @@
1
+ import { assertEx } from '@xylabs/assert'
2
+ import type { Address, Hex } from '@xylabs/hex'
3
+ import {
4
+ asHex, hexFromBigInt, hexToBigInt, toAddress,
5
+ } from '@xylabs/hex'
6
+ import { isNull } from '@xylabs/typeof'
7
+ import { BaseAccountableService } from '@xyo-network/chain-services'
8
+ import { PayloadBuilder } from '@xyo-network/payload-builder'
9
+ import type { LiquidityPoolBridge } from '@xyo-network/typechain'
10
+ import { LiquidityPoolBridge__factory } from '@xyo-network/typechain'
11
+ import {
12
+ type BridgeDestinationObservation, BridgeDestinationObservationSchema, type BridgeIntent,
13
+ } from '@xyo-network/xl1-protocol'
14
+ import type { WebSocketProvider } from 'ethers'
15
+ import { getAddress, Wallet } from 'ethers'
16
+
17
+ import type {
18
+ BridgeDestinationObservationIndexerInterface, BridgeIntentIndexerInterface, BridgeServiceParams, BridgeSourceObservationIndexerInterface,
19
+ ChainBridgeRelayInterface, LockingBridgeIntentProcessorInterface, UnlockingBridgeIntentProcessorInterface,
20
+ } from '../../../interface/index.ts'
21
+
22
+ export type LiquidityPoolBridgeRelayParams = BridgeServiceParams & {
23
+ /**
24
+ * The address to watch for incoming or outgoing ERC-20 token transfers.
25
+ */
26
+ bridgeAddress: string
27
+ /**
28
+ * An ethers.js WebSocketProvider connected to the Ethereum network to monitor for ERC-20 transfers.
29
+ */
30
+ provider: WebSocketProvider
31
+ }
32
+
33
+ export class LiquidityPoolBridgeRelay<TParams extends LiquidityPoolBridgeRelayParams = LiquidityPoolBridgeRelayParams>
34
+ extends BaseAccountableService<TParams> implements ChainBridgeRelayInterface {
35
+ protected _bridge: LiquidityPoolBridge | undefined
36
+ protected _bridgeChainId: Hex | undefined
37
+ protected _bridgeRemoteChainId: Hex | undefined
38
+ protected _bridgeTokenAddress: Address | undefined
39
+ protected _wallet: Wallet | undefined
40
+
41
+ protected get account() {
42
+ return assertEx(this.params.account, () => 'account is required')
43
+ }
44
+
45
+ protected get bridge(): LiquidityPoolBridge {
46
+ return assertEx(this._bridge, () => new Error('Bridge contract not initialized'))
47
+ }
48
+
49
+ protected get bridgeChainId(): Hex {
50
+ return assertEx(this._bridgeChainId, () => new Error('Bridge chain ID not initialized'))
51
+ }
52
+
53
+ protected get bridgeRemoteChainId(): Hex {
54
+ return assertEx(this._bridgeRemoteChainId, () => new Error('Bridge remote chain ID not initialized'))
55
+ }
56
+
57
+ protected get bridgeTokenAddress(): Address {
58
+ return assertEx(this._bridgeTokenAddress, () => new Error('Bridge token address not initialized'))
59
+ }
60
+
61
+ protected get destinationObservations(): BridgeDestinationObservationIndexerInterface {
62
+ return assertEx(this.params.destinationObservations, () => 'destinationObservations is required')
63
+ }
64
+
65
+ protected get destinationRelay(): ChainBridgeRelayInterface {
66
+ return assertEx(this.params.destinationRelay, () => 'destinationRelay is required')
67
+ }
68
+
69
+ protected get intentProcessed(): LockingBridgeIntentProcessorInterface {
70
+ return assertEx(this.params.intentProcessed, () => 'intentProcessed is required')
71
+ }
72
+
73
+ protected get intentProcessing(): UnlockingBridgeIntentProcessorInterface {
74
+ return assertEx(this.params.intentProcessing, () => 'intentProcessing is required')
75
+ }
76
+
77
+ protected get intentResourceAccess(): BridgeIntentIndexerInterface {
78
+ return assertEx(this.params.intents, () => 'intents is required')
79
+ }
80
+
81
+ protected get provider(): WebSocketProvider {
82
+ return assertEx(this.params.provider, () => new Error('Provider not initialized'))
83
+ }
84
+
85
+ protected get sourceObservations(): BridgeSourceObservationIndexerInterface {
86
+ return assertEx(this.params.sourceObservations, () => 'sourceObservations is required')
87
+ }
88
+
89
+ protected get wallet(): Wallet {
90
+ return assertEx(this._wallet, () => 'wallet is required')
91
+ }
92
+
93
+ /**
94
+ * Begins the relay process for a given BridgeIntent.
95
+ * @param bridgeIntent The bridgeIntent to begin relaying
96
+ * @returns True if the relay was started, false otherwise
97
+ */
98
+ async beginRelay(bridgeIntent: BridgeIntent): Promise<boolean> {
99
+ try {
100
+ // Ensure source observation exists
101
+ const bridgeSourceObservation = await this.sourceObservations.getObservationForIntent(bridgeIntent)
102
+ if (!isNull(bridgeSourceObservation)) {
103
+ const canProcess = await this.intentProcessing.lock(this.account.address, bridgeIntent)
104
+ if (canProcess) {
105
+ const amount = hexToBigInt(bridgeIntent.destAmount)
106
+ const nonce = await this.wallet.getNonce()
107
+ await this.bridge.bridgeFromRemote(
108
+ getAddress(bridgeIntent.srcAddress),
109
+ getAddress(bridgeIntent.destAddress),
110
+ amount,
111
+ { nonce },
112
+ )
113
+ return true
114
+ }
115
+ }
116
+ } catch (error) {
117
+ console.error('Error occurred while beginning relay:', error)
118
+ await this.intentProcessing.unlock(this.account.address, bridgeIntent)
119
+ }
120
+ return false
121
+ }
122
+
123
+ override async createHandler(): Promise<void> {
124
+ const { provider, bridgeAddress } = this.params
125
+
126
+ // Connect to the bridge contract
127
+ // Create a wallet bound to the provider (signer + provider)
128
+ const key = assertEx(this.account?.private?.hex, () => new Error('Account private key is required'))
129
+ this._wallet = new Wallet(key, provider)
130
+ this._bridge = LiquidityPoolBridge__factory.connect(getAddress(bridgeAddress), this._wallet)
131
+
132
+ // Parse bridge network chain ID
133
+ const network = await provider.getNetwork()
134
+ this._bridgeChainId = assertEx(hexFromBigInt(network.chainId), () => new Error('Failed to parse bridgeChainId'))
135
+
136
+ // Parse bridge token address
137
+ const tokenAddress = await this.bridge.token()
138
+ this._bridgeTokenAddress = toAddress(tokenAddress)
139
+
140
+ // Parse bridge remote chain ID
141
+ const bridgeRemoteChain = await this.bridge.remoteChain()
142
+ this._bridgeRemoteChainId = asHex(bridgeRemoteChain)
143
+
144
+ await this.bridge.on(this.bridge.getEvent('BridgedFromRemote'), (id, from, to, amount, remoteChain, args) => {
145
+ const txHash = args.transactionHash
146
+ const destConfirmation = asHex(txHash)
147
+ const observation: BridgeDestinationObservation = new PayloadBuilder<BridgeDestinationObservation>({ schema: BridgeDestinationObservationSchema })
148
+ .fields({
149
+ src: this.bridgeRemoteChainId,
150
+ dest: this.bridgeChainId,
151
+ srcAddress: toAddress(from),
152
+ destAddress: toAddress(to),
153
+ srcToken: this.bridgeTokenAddress,
154
+ destToken: this.bridgeTokenAddress,
155
+ srcAmount: amount.toString(),
156
+ destAmount: amount.toString(),
157
+ destConfirmation,
158
+ }).build()
159
+ this.onDestinationObservation(observation).catch(console.error)
160
+ })
161
+ }
162
+
163
+ /**
164
+ * Handles a BridgeDestinationObservation.
165
+ * @param bridgeDestinationObservation The BridgeDestinationObservation to process
166
+ * @returns True if the observation was processed as a completion for an outstanding relay, false otherwise
167
+ */
168
+ async onDestinationObservation(bridgeDestinationObservation: BridgeDestinationObservation): Promise<boolean> {
169
+ // Ensure intent exists
170
+ const bridgeIntent = await this.destinationObservations.getIntentForObservation(bridgeDestinationObservation)
171
+ if (!isNull(bridgeIntent)) {
172
+ // Ensure not already processed
173
+ const processed = await this.intentProcessed.isLocked(bridgeIntent)
174
+ if (isNull(processed)) {
175
+ // Ensure we are the processor
176
+ const processor = await this.intentProcessing.isLocked(bridgeIntent)
177
+ if (!isNull(processor) && processor === this.account.address) {
178
+ // Mark as completed
179
+ await this.intentProcessed.lock(this.account.address, bridgeIntent)
180
+ await this.intentProcessing.unlock(this.account.address, bridgeIntent)
181
+ return true
182
+ }
183
+ }
184
+ }
185
+ return false
186
+ }
187
+
188
+ /**
189
+ * Relays a given BridgeIntent.
190
+ * @param bridgeIntent The BridgeIntent to relay
191
+ * @returns Relays the intent in a blocking manner, returning the resulting BridgeDestinationObservation if successful
192
+ */
193
+ async relaySync(bridgeIntent: BridgeIntent): Promise<BridgeDestinationObservation | null> {
194
+ // Ensure source observation exists
195
+ const bridgeSourceObservation = await this.sourceObservations.getObservationForIntent(bridgeIntent)
196
+ if (!isNull(bridgeSourceObservation)) {
197
+ let canProcess = isNull(await this.intentProcessing.isLocked(bridgeIntent))
198
+ try {
199
+ canProcess = await this.intentProcessing.lock(this.account.address, bridgeIntent)
200
+ if (canProcess) {
201
+ const amount = hexToBigInt(bridgeIntent.destAmount)
202
+ const nonce = await this.wallet.getNonce()
203
+ const tx = await this.bridge.bridgeFromRemote(
204
+ getAddress(bridgeIntent.srcAddress),
205
+ getAddress(bridgeIntent.destAddress),
206
+ amount,
207
+ { nonce },
208
+ )
209
+ const confirmation = await tx.wait()
210
+ const transactionResponse = await confirmation?.getTransaction()
211
+ const destConfirmation = asHex(transactionResponse?.hash ?? '')
212
+ const { schema, ...rest } = bridgeIntent
213
+ const result: BridgeDestinationObservation = new PayloadBuilder<BridgeDestinationObservation>({ schema: BridgeDestinationObservationSchema })
214
+ .fields({ ...rest, destConfirmation }).build()
215
+ await this.destinationObservations.addObservation(result, bridgeIntent)
216
+ await this.intentProcessed.lock(this.account.address, bridgeIntent)
217
+ return result
218
+ }
219
+ } finally {
220
+ if (!isNull(bridgeIntent)) {
221
+ await this.intentProcessing.unlock(this.account.address, bridgeIntent)
222
+ }
223
+ }
224
+ }
225
+ return null
226
+ }
227
+ }
@@ -0,0 +1 @@
1
+ export * from './LiquidityPoolBridgeRelay.ts'