@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.
- package/README.md +1 -1
- package/dist/node/driver/indexer/ChainHydratedBlocksObservable.d.ts +4 -4
- package/dist/node/driver/indexer/ChainHydratedBlocksObservable.d.ts.map +1 -1
- package/dist/node/driver/mongo/MongoMap.d.ts +3 -2
- package/dist/node/driver/mongo/MongoMap.d.ts.map +1 -1
- package/dist/node/index.mjs +300 -683
- package/dist/node/index.mjs.map +1 -1
- package/dist/node/interface/interface/IntentIndexerInterface.d.ts +5 -3
- package/dist/node/interface/interface/IntentIndexerInterface.d.ts.map +1 -1
- package/dist/node/interface/service/Observer/ERC20TransferObserver/ERC20TransferObserver.d.ts +28 -0
- package/dist/node/interface/service/Observer/ERC20TransferObserver/ERC20TransferObserver.d.ts.map +1 -0
- package/dist/node/interface/service/Observer/ERC20TransferObserver/index.d.ts +2 -0
- package/dist/node/interface/service/Observer/ERC20TransferObserver/index.d.ts.map +1 -0
- package/dist/node/interface/service/Observer/ERC20TransferObserver/spec/ERC20TransferObserver.spec.d.ts +2 -0
- package/dist/node/interface/service/Observer/ERC20TransferObserver/spec/ERC20TransferObserver.spec.d.ts.map +1 -0
- package/dist/node/interface/service/Observer/LiquidityPoolBridgeObserver/LiquidityPoolBridgeObserver.d.ts +36 -0
- package/dist/node/interface/service/Observer/LiquidityPoolBridgeObserver/LiquidityPoolBridgeObserver.d.ts.map +1 -0
- package/dist/node/interface/service/Observer/LiquidityPoolBridgeObserver/index.d.ts +2 -0
- package/dist/node/interface/service/Observer/LiquidityPoolBridgeObserver/index.d.ts.map +1 -0
- package/dist/node/interface/service/Observer/LiquidityPoolBridgeObserver/spec/LiquidityPoolBridgeObserver.spec.d.ts +2 -0
- package/dist/node/interface/service/Observer/LiquidityPoolBridgeObserver/spec/LiquidityPoolBridgeObserver.spec.d.ts.map +1 -0
- package/dist/node/interface/service/Observer/Observer.d.ts +1 -1
- package/dist/node/interface/service/Observer/Observer.d.ts.map +1 -1
- package/dist/node/interface/service/{ChainBridgeRelay → Relay/ChainBridgeRelay}/ChainBridgeRelayInterface.d.ts +1 -1
- package/dist/node/interface/service/Relay/ChainBridgeRelay/ChainBridgeRelayInterface.d.ts.map +1 -0
- package/dist/node/interface/service/{ChainBridgeRelay → Relay/ChainBridgeRelay}/ChainBridgeRelayService.d.ts +1 -1
- package/dist/node/interface/service/Relay/ChainBridgeRelay/ChainBridgeRelayService.d.ts.map +1 -0
- package/dist/node/interface/service/Relay/ChainBridgeRelay/index.d.ts.map +1 -0
- package/dist/node/interface/service/Relay/ChainBridgeRelay/spec/ChainBridgeRelayService.spec.d.ts.map +1 -0
- package/dist/node/interface/service/Relay/LiquidityPoolBridgeRelay/LiquidityPoolBridgeRelay.d.ts +57 -0
- package/dist/node/interface/service/Relay/LiquidityPoolBridgeRelay/LiquidityPoolBridgeRelay.d.ts.map +1 -0
- package/dist/node/interface/service/Relay/LiquidityPoolBridgeRelay/index.d.ts +2 -0
- package/dist/node/interface/service/Relay/LiquidityPoolBridgeRelay/index.d.ts.map +1 -0
- package/dist/node/interface/service/Relay/LiquidityPoolBridgeRelay/spec/LiquidityPoolBridgeRelay.spec.d.ts +2 -0
- package/dist/node/interface/service/Relay/LiquidityPoolBridgeRelay/spec/LiquidityPoolBridgeRelay.spec.d.ts.map +1 -0
- package/dist/node/interface/service/Relay/index.d.ts +2 -0
- package/dist/node/interface/service/Relay/index.d.ts.map +1 -0
- package/dist/node/interface/service/index.d.ts +1 -1
- package/dist/node/interface/service/index.d.ts.map +1 -1
- package/dist/node/manifest/getLocator.d.ts.map +1 -1
- package/dist/node/manifest/public/index.d.ts +6 -2
- package/dist/node/manifest/public/index.d.ts.map +1 -1
- package/dist/node/server/app.d.ts +1 -2
- package/dist/node/server/app.d.ts.map +1 -1
- package/dist/node/server/routes/addRoutes.d.ts.map +1 -1
- package/dist/node/server/routes/bridge/addBridgeRoutes.d.ts +3 -0
- package/dist/node/server/routes/bridge/addBridgeRoutes.d.ts.map +1 -0
- package/dist/node/server/routes/bridge/index.d.ts +2 -0
- package/dist/node/server/routes/bridge/index.d.ts.map +1 -0
- package/dist/node/server/routes/bridge/middleware/index.d.ts +2 -0
- package/dist/node/server/routes/bridge/middleware/index.d.ts.map +1 -0
- package/dist/node/server/routes/bridge/middleware/requestHandlerValidator.d.ts +32 -0
- package/dist/node/server/routes/bridge/middleware/requestHandlerValidator.d.ts.map +1 -0
- package/dist/node/server/routes/bridge/routeDefinitions/getRouteDefinitions.d.ts +3 -0
- package/dist/node/server/routes/bridge/routeDefinitions/getRouteDefinitions.d.ts.map +1 -0
- package/dist/node/server/routes/bridge/routeDefinitions/index.d.ts +2 -0
- package/dist/node/server/routes/bridge/routeDefinitions/index.d.ts.map +1 -0
- package/dist/node/server/routes/bridge/routeDefinitions/pathParams/ChainIdPathParam.d.ts +6 -0
- package/dist/node/server/routes/bridge/routeDefinitions/pathParams/ChainIdPathParam.d.ts.map +1 -0
- package/dist/node/server/routes/bridge/routeDefinitions/pathParams/index.d.ts +2 -0
- package/dist/node/server/routes/bridge/routeDefinitions/pathParams/index.d.ts.map +1 -0
- package/dist/node/server/routes/bridge/routeDefinitions/routeDefinition.d.ts +8 -0
- package/dist/node/server/routes/bridge/routeDefinitions/routeDefinition.d.ts.map +1 -0
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteStatus.d.ts +3 -0
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteStatus.d.ts.map +1 -0
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemote.d.ts +3 -0
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemote.d.ts.map +1 -0
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteEstimate.d.ts +3 -0
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteEstimate.d.ts.map +1 -0
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteStatus.d.ts +3 -0
- package/dist/node/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteStatus.d.ts.map +1 -0
- package/dist/node/server/routes/bridge/routeDefinitions/routes/index.d.ts +5 -0
- package/dist/node/server/routes/bridge/routeDefinitions/routes/index.d.ts.map +1 -0
- package/dist/node/server/routes/healthz/get.d.ts +2 -1
- package/dist/node/server/routes/healthz/get.d.ts.map +1 -1
- package/dist/node/server/routes/index.d.ts +0 -1
- package/dist/node/server/routes/index.d.ts.map +1 -1
- package/dist/node/server/server.d.ts.map +1 -1
- package/package.json +62 -55
- package/src/driver/indexer/ChainHydratedBlocksObservable.ts +5 -5
- package/src/driver/indexer/spec/ChainBlocksObservable.spec.ts +6 -3
- package/src/driver/indexer/spec/ChainHydratedBlocksObservable.spec.ts +10 -4
- package/src/driver/mongo/MongoMap.ts +13 -3
- package/src/interface/interface/IntentIndexerInterface.ts +5 -4
- package/src/interface/service/Observer/ERC20TransferObserver/ERC20TransferObserver.ts +181 -0
- package/src/interface/service/Observer/ERC20TransferObserver/index.ts +1 -0
- package/src/interface/service/Observer/ERC20TransferObserver/spec/ERC20TransferObserver.spec.ts +271 -0
- package/src/interface/service/Observer/LiquidityPoolBridgeObserver/LiquidityPoolBridgeObserver.ts +212 -0
- package/src/interface/service/Observer/LiquidityPoolBridgeObserver/index.ts +1 -0
- package/src/interface/service/Observer/LiquidityPoolBridgeObserver/spec/LiquidityPoolBridgeObserver.spec.ts +313 -0
- package/src/interface/service/Observer/Observer.ts +1 -1
- package/src/interface/service/{ChainBridgeRelay → Relay/ChainBridgeRelay}/ChainBridgeRelayInterface.ts +1 -1
- package/src/interface/service/{ChainBridgeRelay → Relay/ChainBridgeRelay}/ChainBridgeRelayService.ts +1 -1
- package/src/interface/service/{ChainBridgeRelay → Relay/ChainBridgeRelay}/spec/ChainBridgeRelayService.spec.ts +7 -5
- package/src/interface/service/Relay/LiquidityPoolBridgeRelay/LiquidityPoolBridgeRelay.ts +227 -0
- package/src/interface/service/Relay/LiquidityPoolBridgeRelay/index.ts +1 -0
- package/src/interface/service/Relay/LiquidityPoolBridgeRelay/spec/LiquidityPoolBridgeRelay.spec.ts +237 -0
- package/src/interface/service/Relay/index.ts +1 -0
- package/src/interface/service/index.ts +1 -1
- package/src/manifest/getLocator.ts +7 -6
- package/src/manifest/node.json +1 -1
- package/src/manifest/public/Chain.json +3 -109
- package/src/manifest/public/Ethereum.json +88 -0
- package/src/manifest/public/XL1.json +88 -0
- package/src/manifest/public/index.ts +15 -6
- package/src/server/app.ts +5 -12
- package/src/server/routes/addRoutes.ts +2 -6
- package/src/server/routes/bridge/addBridgeRoutes.ts +10 -0
- package/src/server/routes/bridge/index.ts +1 -0
- package/src/server/routes/bridge/middleware/index.ts +1 -0
- package/src/server/routes/bridge/middleware/requestHandlerValidator.ts +120 -0
- package/src/server/routes/bridge/routeDefinitions/getRouteDefinitions.ts +13 -0
- package/src/server/routes/bridge/routeDefinitions/index.ts +1 -0
- package/src/server/routes/bridge/routeDefinitions/pathParams/ChainIdPathParam.ts +18 -0
- package/src/server/routes/bridge/routeDefinitions/pathParams/index.ts +1 -0
- package/src/server/routes/bridge/routeDefinitions/routeDefinition.ts +18 -0
- package/src/server/routes/bridge/routeDefinitions/routes/bridgeFromRemoteStatus.ts +55 -0
- package/src/server/routes/bridge/routeDefinitions/routes/bridgeToRemote.ts +58 -0
- package/src/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteEstimate.ts +83 -0
- package/src/server/routes/bridge/routeDefinitions/routes/bridgeToRemoteStatus.ts +55 -0
- package/src/server/routes/bridge/routeDefinitions/routes/index.ts +4 -0
- package/src/server/routes/healthz/get.ts +1 -1
- package/src/server/routes/index.ts +0 -2
- package/src/server/server.ts +11 -14
- package/dist/node/interface/service/ChainBridgeRelay/ChainBridgeRelayInterface.d.ts.map +0 -1
- package/dist/node/interface/service/ChainBridgeRelay/ChainBridgeRelayService.d.ts.map +0 -1
- package/dist/node/interface/service/ChainBridgeRelay/index.d.ts.map +0 -1
- package/dist/node/interface/service/ChainBridgeRelay/spec/ChainBridgeRelayService.spec.d.ts.map +0 -1
- package/dist/node/server/routes/rpc/index.d.ts +0 -2
- package/dist/node/server/routes/rpc/index.d.ts.map +0 -1
- package/dist/node/server/routes/rpc/routes/addRpcRoutes.d.ts +0 -3
- package/dist/node/server/routes/rpc/routes/addRpcRoutes.d.ts.map +0 -1
- package/dist/node/server/routes/rpc/routes/index.d.ts +0 -2
- package/dist/node/server/routes/rpc/routes/index.d.ts.map +0 -1
- package/src/manifest/public/Pending.json +0 -35
- package/src/server/routes/rpc/index.ts +0 -1
- package/src/server/routes/rpc/routes/addRpcRoutes.ts +0 -22
- package/src/server/routes/rpc/routes/index.ts +0 -1
- /package/dist/node/interface/service/{ChainBridgeRelay → Relay/ChainBridgeRelay}/index.d.ts +0 -0
- /package/dist/node/interface/service/{ChainBridgeRelay → Relay/ChainBridgeRelay}/spec/ChainBridgeRelayService.spec.d.ts +0 -0
- /package/src/interface/service/{ChainBridgeRelay → Relay/ChainBridgeRelay}/index.ts +0 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import type { Address } from '@xylabs/hex'
|
|
2
|
+
import {
|
|
3
|
+
asAddress, asHex, hexToBigInt, toAddress,
|
|
4
|
+
} from '@xylabs/hex'
|
|
5
|
+
import {
|
|
6
|
+
isBigInt, isNull, isUndefined,
|
|
7
|
+
} from '@xylabs/typeof'
|
|
8
|
+
import type { BridgeDestinationObservation, BridgeSourceObservation } from '@xyo-network/xl1-protocol'
|
|
9
|
+
import {
|
|
10
|
+
BridgeDestinationObservationSchema, BridgeSourceObservationSchema, XYO_ZERO_ADDRESS,
|
|
11
|
+
} from '@xyo-network/xl1-protocol'
|
|
12
|
+
import type { EventLog, WebSocketProvider } from 'ethers'
|
|
13
|
+
import { Contract } from 'ethers'
|
|
14
|
+
import { getAddress } from 'ethers/address'
|
|
15
|
+
|
|
16
|
+
import type { BridgeServiceParams } from '../../../interface/index.ts'
|
|
17
|
+
import { BridgeObserverService } from '../Observer.ts'
|
|
18
|
+
|
|
19
|
+
export type ERC20TransferObserverParams = BridgeServiceParams & {
|
|
20
|
+
/**
|
|
21
|
+
* An ethers.js WebSocketProvider connected to the Ethereum network to monitor for ERC-20 transfers.
|
|
22
|
+
*/
|
|
23
|
+
provider: WebSocketProvider
|
|
24
|
+
/**
|
|
25
|
+
* The ERC-20 token contract address to monitor (e.g., XYO, USDC).
|
|
26
|
+
*/
|
|
27
|
+
tokenAddress: string
|
|
28
|
+
/**
|
|
29
|
+
* The address to watch for incoming or outgoing ERC-20 token transfers.
|
|
30
|
+
*/
|
|
31
|
+
watchAddress: string
|
|
32
|
+
/**
|
|
33
|
+
* Specify whether to watch for 'incoming' or 'outgoing' types of transfers.
|
|
34
|
+
*/
|
|
35
|
+
watchDirection: 'incoming' | 'outgoing'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Minimal ERC-20 ABI only with Transfer event
|
|
39
|
+
const ERC20_ABI = [
|
|
40
|
+
'event Transfer(address indexed from, address indexed to, uint256 value)',
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
export class ERC20TransferObserver extends BridgeObserverService<ERC20TransferObserverParams> {
|
|
44
|
+
protected get tokenAddress(): Address {
|
|
45
|
+
return toAddress(this.params.tokenAddress)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
override async createHandler(): Promise<void> {
|
|
49
|
+
const {
|
|
50
|
+
provider, tokenAddress, watchAddress, watchDirection,
|
|
51
|
+
} = this.params
|
|
52
|
+
// The custodial wallet to watch for transfers
|
|
53
|
+
const normalizedWatchAddress = getAddress(watchAddress)
|
|
54
|
+
|
|
55
|
+
// Create a Contract for the ERC-20 token
|
|
56
|
+
const token = new Contract(getAddress(tokenAddress), ERC20_ABI, provider)
|
|
57
|
+
|
|
58
|
+
// Listen for transfers involving WATCH_ADDRESS
|
|
59
|
+
const filterIncoming = token.filters.Transfer(null, normalizedWatchAddress)
|
|
60
|
+
const filterOutgoing = token.filters.Transfer(normalizedWatchAddress, null)
|
|
61
|
+
|
|
62
|
+
// Pick correct filter + direction
|
|
63
|
+
const filter = watchDirection === 'incoming' ? filterIncoming : filterOutgoing
|
|
64
|
+
|
|
65
|
+
// Replay old logs
|
|
66
|
+
const currentBlock = await provider.getBlockNumber()
|
|
67
|
+
const pastEvents = await token.queryFilter(filter, 0, currentBlock)
|
|
68
|
+
for (const ev of pastEvents) {
|
|
69
|
+
const {
|
|
70
|
+
from, to, value,
|
|
71
|
+
} = (ev as EventLog)?.args
|
|
72
|
+
await (watchDirection === 'incoming' ? this.handleTransfer(from, value, 'incoming') : this.handleTransfer(to, value, 'outgoing'))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Watch for new events
|
|
76
|
+
if (watchDirection === 'incoming') {
|
|
77
|
+
await token.on(filterIncoming, (ev: EventLog) => {
|
|
78
|
+
// Sanitize event log
|
|
79
|
+
const { from, value } = ev.args
|
|
80
|
+
if (isUndefined(from) || isUndefined(value)) return
|
|
81
|
+
const sender = asAddress(from)
|
|
82
|
+
// Ignore mints
|
|
83
|
+
if (isUndefined(sender) || sender === XYO_ZERO_ADDRESS) return
|
|
84
|
+
// Ensure value is bigint and non-zero
|
|
85
|
+
if (!isBigInt(value) || value === 0n) return
|
|
86
|
+
// Handle transfer
|
|
87
|
+
this.handleTransfer(sender, value, 'incoming').catch(console.error)
|
|
88
|
+
})
|
|
89
|
+
} else if (watchDirection === 'outgoing') {
|
|
90
|
+
await token.on(filterOutgoing, (ev: EventLog) => {
|
|
91
|
+
// Sanitize event log
|
|
92
|
+
const { to, value } = ev.args
|
|
93
|
+
if (isUndefined(to) || isUndefined(value)) return
|
|
94
|
+
const receiver = asAddress(to)
|
|
95
|
+
// Ignore burns
|
|
96
|
+
if (isUndefined(receiver) || receiver === XYO_ZERO_ADDRESS) return
|
|
97
|
+
// Ensure value is bigint and non-zero
|
|
98
|
+
if (!isBigInt(value) || value === 0n) return
|
|
99
|
+
this.handleTransfer(receiver, value, 'outgoing').catch(console.error)
|
|
100
|
+
})
|
|
101
|
+
} else {
|
|
102
|
+
throw new Error(`Invalid watchDirection: ${watchDirection}`)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private async handleTransfer(account: Address, value: bigint, direction: 'incoming' | 'outgoing'): Promise<void> {
|
|
107
|
+
if (direction === 'incoming') {
|
|
108
|
+
const sender = asAddress(account)
|
|
109
|
+
if (isUndefined (sender) || sender === XYO_ZERO_ADDRESS) return
|
|
110
|
+
const intents = await this.intents.getIntentsForSource(sender)
|
|
111
|
+
if (intents.length === 0) return
|
|
112
|
+
// Find the intent that matches the transfer amount (chain and token are implicit to provider and intents were already indexed by address)
|
|
113
|
+
const intent = intents.findLast((i) => {
|
|
114
|
+
try {
|
|
115
|
+
// Source address matches intent
|
|
116
|
+
const address = asAddress(i.srcAddress)
|
|
117
|
+
if (isUndefined(address) || address !== sender) return false
|
|
118
|
+
// Source token must match
|
|
119
|
+
const token = asHex(i.srcToken)
|
|
120
|
+
if (isUndefined(token) || token !== this.tokenAddress) return false
|
|
121
|
+
// Source amount must match
|
|
122
|
+
const hexAmount = asHex(i.srcAmount)
|
|
123
|
+
if (isUndefined(hexAmount)) return false
|
|
124
|
+
const amount = hexToBigInt(hexAmount)
|
|
125
|
+
if (amount !== value) return false
|
|
126
|
+
// Otherwise matches
|
|
127
|
+
return true
|
|
128
|
+
} catch {
|
|
129
|
+
return false
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
if (isUndefined(intent)) return
|
|
133
|
+
// Found matching intent, check if observation already exists
|
|
134
|
+
const observations = this.sourceObservations
|
|
135
|
+
const existing = await observations.getObservationForIntent(intent)
|
|
136
|
+
// Only add if observation not already existing
|
|
137
|
+
if (isUndefined(existing) || isNull(existing)) {
|
|
138
|
+
const { schema, ...rest } = intent
|
|
139
|
+
const observation: BridgeSourceObservation = { schema: BridgeSourceObservationSchema, ...rest }
|
|
140
|
+
await observations.addObservation(observation, intent)
|
|
141
|
+
}
|
|
142
|
+
} else if (direction === 'outgoing') {
|
|
143
|
+
const receiver = asAddress(account)
|
|
144
|
+
if (isUndefined (receiver) || receiver === XYO_ZERO_ADDRESS) return
|
|
145
|
+
const intents = await this.intents.getIntentsForDestination(receiver)
|
|
146
|
+
if (intents.length === 0) return
|
|
147
|
+
// Find the intent that matches the transfer amount (chain and token are implicit to provider and intents were already indexed by address)
|
|
148
|
+
const intent = intents.findLast((i) => {
|
|
149
|
+
try {
|
|
150
|
+
// Source address matches intent
|
|
151
|
+
const address = asAddress(i.destAddress)
|
|
152
|
+
if (isUndefined(address) || address !== receiver) return false
|
|
153
|
+
// Source token must match
|
|
154
|
+
const token = asHex(i.destToken)
|
|
155
|
+
if (isUndefined(token) || token !== this.tokenAddress) return false
|
|
156
|
+
// Source amount must match
|
|
157
|
+
const hexAmount = asHex(i.destAmount)
|
|
158
|
+
if (isUndefined(hexAmount)) return false
|
|
159
|
+
const amount = hexToBigInt(hexAmount)
|
|
160
|
+
if (amount !== value) return false
|
|
161
|
+
// Otherwise matches
|
|
162
|
+
return true
|
|
163
|
+
} catch {
|
|
164
|
+
return false
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
if (isUndefined(intent)) return
|
|
168
|
+
// Found matching intent, check if observation already exists
|
|
169
|
+
const observations = this.destinationObservations
|
|
170
|
+
const existing = await observations.getObservationForIntent(intent)
|
|
171
|
+
// Only add if observation not already existing
|
|
172
|
+
if (isUndefined(existing) || isNull(existing)) {
|
|
173
|
+
const { schema, ...rest } = intent
|
|
174
|
+
const observation: BridgeDestinationObservation = { schema: BridgeDestinationObservationSchema, ...rest }
|
|
175
|
+
await observations.addObservation(observation, intent)
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
throw new Error(`Invalid direction: ${direction}`)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ERC20TransferObserver.ts'
|
package/src/interface/service/Observer/ERC20TransferObserver/spec/ERC20TransferObserver.spec.ts
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
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 } from '@xyo-network/typechain'
|
|
7
|
+
import { BridgeableToken__factory } from '@xyo-network/typechain'
|
|
8
|
+
import {
|
|
9
|
+
AttoXL1ConvertFactor, type BridgeIntent, BridgeIntentSchema, type ChainId,
|
|
10
|
+
} from '@xyo-network/xl1-protocol'
|
|
11
|
+
import {
|
|
12
|
+
parseEther, Wallet, WebSocketProvider,
|
|
13
|
+
} from 'ethers'
|
|
14
|
+
import {
|
|
15
|
+
beforeAll, beforeEach, describe, expect, it, vi,
|
|
16
|
+
} from 'vitest'
|
|
17
|
+
|
|
18
|
+
import type {
|
|
19
|
+
BridgeDestinationObservationIndexerInterface, BridgeIntentIndexerInterface, BridgeSourceObservationIndexerInterface,
|
|
20
|
+
} from '../../../../interface/index.ts'
|
|
21
|
+
import { ERC20TransferObserver } from '../ERC20TransferObserver.ts'
|
|
22
|
+
|
|
23
|
+
describe.skip('ERC20TransferObserver', () => {
|
|
24
|
+
// Test ERC-20 deployed to Hardhat local chain
|
|
25
|
+
const TOKEN_ADDRESS = '0x5FbDB2315678afecb367f032d93F642f64180aa3'
|
|
26
|
+
const WS_URL = 'ws://127.0.0.1:8545'
|
|
27
|
+
const provider = new WebSocketProvider(WS_URL)
|
|
28
|
+
let ethBridgeSender: Wallet
|
|
29
|
+
let ethBridgeReceiver: Wallet
|
|
30
|
+
let custodialWallet: Wallet
|
|
31
|
+
let token: BridgeableToken
|
|
32
|
+
let xl1Address: Address
|
|
33
|
+
let xl1ChainId: ChainId
|
|
34
|
+
let sourceObservations: BridgeSourceObservationIndexerInterface
|
|
35
|
+
let destinationObservations: BridgeDestinationObservationIndexerInterface
|
|
36
|
+
let intents: BridgeIntentIndexerInterface
|
|
37
|
+
|
|
38
|
+
const srcAmountBigint = 100n * AttoXL1ConvertFactor.xl1
|
|
39
|
+
const srcAmount = toHex(srcAmountBigint)
|
|
40
|
+
const destAmount = srcAmount // 1:1 for test
|
|
41
|
+
const ethChainId = toHex('0x7A69')
|
|
42
|
+
const bridgeableTokenContract = toHex(TOKEN_ADDRESS)
|
|
43
|
+
|
|
44
|
+
const createRandomWallet = async (): Promise<Wallet> => {
|
|
45
|
+
// Create random account
|
|
46
|
+
const account = await Account.random()
|
|
47
|
+
expect(account.private?.hex).toBeDefined()
|
|
48
|
+
const key = assertEx(account.private?.hex)
|
|
49
|
+
|
|
50
|
+
// Create a wallet from the private key
|
|
51
|
+
const wallet = new Wallet(key, provider)
|
|
52
|
+
const deployer = await provider.getSigner(0)
|
|
53
|
+
|
|
54
|
+
// Fund the wallet with some ETH for gas
|
|
55
|
+
const fundTx = await deployer.sendTransaction({ to: wallet.address, value: parseEther('1') })
|
|
56
|
+
await fundTx.wait()
|
|
57
|
+
|
|
58
|
+
// Ensure wallet has ETH
|
|
59
|
+
const balance = await provider.getBalance(wallet.address)
|
|
60
|
+
expect(balance).toBeGreaterThan(0n)
|
|
61
|
+
|
|
62
|
+
// Return the created wallet
|
|
63
|
+
return wallet
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
beforeAll(async () => {
|
|
67
|
+
const tokenOwner = await provider.getSigner(0)
|
|
68
|
+
ethBridgeSender = await createRandomWallet()
|
|
69
|
+
ethBridgeReceiver = await createRandomWallet()
|
|
70
|
+
custodialWallet = await createRandomWallet()
|
|
71
|
+
|
|
72
|
+
token = BridgeableToken__factory.connect(TOKEN_ADDRESS, tokenOwner)
|
|
73
|
+
await token.mint(custodialWallet.address, parseEther((srcAmountBigint * 2n).toString()))
|
|
74
|
+
await token.mint(ethBridgeSender.address, parseEther((srcAmountBigint * 2n).toString()))
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
beforeEach(async () => {
|
|
78
|
+
xl1Address = (await Account.random()).address
|
|
79
|
+
xl1ChainId = (await Account.random()).address
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
describe('when bridging from Ethereum', () => {
|
|
83
|
+
beforeEach(() => {
|
|
84
|
+
sourceObservations = {
|
|
85
|
+
addObservation: vi.fn().mockResolvedValue(true),
|
|
86
|
+
getObservationForIntent: vi.fn().mockResolvedValue(null),
|
|
87
|
+
getIntentForObservation: vi.fn().mockResolvedValue(null),
|
|
88
|
+
}
|
|
89
|
+
destinationObservations = {
|
|
90
|
+
addObservation: vi.fn().mockResolvedValue(true),
|
|
91
|
+
getObservationForIntent: vi.fn().mockResolvedValue(null),
|
|
92
|
+
getIntentForObservation: vi.fn().mockResolvedValue(null),
|
|
93
|
+
}
|
|
94
|
+
const nonce = Date.now().toString()
|
|
95
|
+
const intent: BridgeIntent = {
|
|
96
|
+
// Source
|
|
97
|
+
src: ethChainId,
|
|
98
|
+
srcAddress: toAddress(ethBridgeSender.address),
|
|
99
|
+
srcAmount,
|
|
100
|
+
srcToken: bridgeableTokenContract,
|
|
101
|
+
|
|
102
|
+
// Destination
|
|
103
|
+
dest: xl1ChainId,
|
|
104
|
+
destAddress: xl1Address,
|
|
105
|
+
destAmount,
|
|
106
|
+
destToken: xl1ChainId,
|
|
107
|
+
|
|
108
|
+
// Details
|
|
109
|
+
nonce,
|
|
110
|
+
|
|
111
|
+
schema: BridgeIntentSchema,
|
|
112
|
+
}
|
|
113
|
+
intents = {
|
|
114
|
+
addIntent: vi.fn().mockResolvedValue(true),
|
|
115
|
+
getIntentByNonce: vi.fn().mockResolvedValue(intent),
|
|
116
|
+
getIntentsForDestination: vi.fn().mockResolvedValue([]),
|
|
117
|
+
getIntentsForSource: vi.fn().mockResolvedValue([intent]),
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
describe('for new transfers to custodial wallet', () => {
|
|
121
|
+
it('should observe transfer', async () => {
|
|
122
|
+
// Arrange
|
|
123
|
+
// Create observer before transfer
|
|
124
|
+
const observer = await ERC20TransferObserver.create({
|
|
125
|
+
destinationObservations,
|
|
126
|
+
intents,
|
|
127
|
+
provider,
|
|
128
|
+
sourceObservations,
|
|
129
|
+
tokenAddress: TOKEN_ADDRESS,
|
|
130
|
+
watchAddress: toAddress(custodialWallet.address),
|
|
131
|
+
watchDirection: 'incoming',
|
|
132
|
+
})
|
|
133
|
+
expect(observer).toBeDefined()
|
|
134
|
+
// Ensure sender has tokens
|
|
135
|
+
const balance = await token.balanceOf(ethBridgeSender.address)
|
|
136
|
+
expect(balance).toBeGreaterThan(0n)
|
|
137
|
+
const sender = token.connect(ethBridgeSender)
|
|
138
|
+
|
|
139
|
+
// Act
|
|
140
|
+
// Transfer from sender to custodial wallet
|
|
141
|
+
await sender.transfer(custodialWallet.address, srcAmountBigint)
|
|
142
|
+
|
|
143
|
+
// Wait for event
|
|
144
|
+
await delay(2000)
|
|
145
|
+
|
|
146
|
+
// Assert
|
|
147
|
+
// Ensure transfer was observed
|
|
148
|
+
expect(sourceObservations.addObservation).toHaveBeenCalled()
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
describe('for previous transfers to custodial wallet', () => {
|
|
152
|
+
it('should observe transfer', async () => {
|
|
153
|
+
// Arrange
|
|
154
|
+
// Ensure sender has tokens
|
|
155
|
+
const balance = await token.balanceOf(ethBridgeSender.address)
|
|
156
|
+
expect(balance).toBeGreaterThan(0n)
|
|
157
|
+
const sender = token.connect(ethBridgeSender)
|
|
158
|
+
// Transfer from sender to custodial wallet
|
|
159
|
+
await sender.transfer(custodialWallet.address, srcAmountBigint)
|
|
160
|
+
|
|
161
|
+
// Act
|
|
162
|
+
// Create observer after transfer
|
|
163
|
+
const observer = await ERC20TransferObserver.create({
|
|
164
|
+
destinationObservations,
|
|
165
|
+
intents,
|
|
166
|
+
provider,
|
|
167
|
+
sourceObservations,
|
|
168
|
+
tokenAddress: TOKEN_ADDRESS,
|
|
169
|
+
watchAddress: toAddress(custodialWallet.address),
|
|
170
|
+
watchDirection: 'incoming',
|
|
171
|
+
})
|
|
172
|
+
expect(observer).toBeDefined()
|
|
173
|
+
|
|
174
|
+
// Assert
|
|
175
|
+
// Ensure transfer was observed
|
|
176
|
+
expect(sourceObservations.addObservation).toHaveBeenCalled()
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
describe('when bridging to Ethereum', () => {
|
|
182
|
+
beforeEach(() => {
|
|
183
|
+
sourceObservations = {
|
|
184
|
+
addObservation: vi.fn().mockResolvedValue(true),
|
|
185
|
+
getObservationForIntent: vi.fn().mockResolvedValue(null),
|
|
186
|
+
getIntentForObservation: vi.fn().mockResolvedValue(null),
|
|
187
|
+
}
|
|
188
|
+
destinationObservations = {
|
|
189
|
+
addObservation: vi.fn().mockResolvedValue(true),
|
|
190
|
+
getObservationForIntent: vi.fn().mockResolvedValue(null),
|
|
191
|
+
getIntentForObservation: vi.fn().mockResolvedValue(null),
|
|
192
|
+
}
|
|
193
|
+
const nonce = Date.now().toString()
|
|
194
|
+
const intent: BridgeIntent = {
|
|
195
|
+
// Source
|
|
196
|
+
src: xl1ChainId, // From XL1
|
|
197
|
+
srcAddress: xl1Address, // From XL1 sender
|
|
198
|
+
srcAmount,
|
|
199
|
+
srcToken: xl1ChainId, // In XL1
|
|
200
|
+
|
|
201
|
+
// Destination
|
|
202
|
+
dest: ethChainId, // To Ethereum
|
|
203
|
+
destAddress: toAddress(ethBridgeReceiver.address),
|
|
204
|
+
destAmount,
|
|
205
|
+
destToken: bridgeableTokenContract,
|
|
206
|
+
|
|
207
|
+
// Details
|
|
208
|
+
nonce,
|
|
209
|
+
|
|
210
|
+
schema: BridgeIntentSchema,
|
|
211
|
+
}
|
|
212
|
+
intents = {
|
|
213
|
+
addIntent: vi.fn().mockResolvedValue(true),
|
|
214
|
+
getIntentByNonce: vi.fn().mockResolvedValue(intent),
|
|
215
|
+
getIntentsForDestination: vi.fn().mockResolvedValue([intent]),
|
|
216
|
+
getIntentsForSource: vi.fn().mockResolvedValue([]),
|
|
217
|
+
}
|
|
218
|
+
})
|
|
219
|
+
describe('for new transfers from custodial wallet', () => {
|
|
220
|
+
it('should observe transfer', async () => {
|
|
221
|
+
// Arrange
|
|
222
|
+
// Create observer before transfer
|
|
223
|
+
const observer = await ERC20TransferObserver.create({
|
|
224
|
+
destinationObservations,
|
|
225
|
+
intents,
|
|
226
|
+
provider,
|
|
227
|
+
sourceObservations,
|
|
228
|
+
tokenAddress: TOKEN_ADDRESS,
|
|
229
|
+
watchAddress: toAddress(custodialWallet.address),
|
|
230
|
+
watchDirection: 'outgoing',
|
|
231
|
+
})
|
|
232
|
+
expect(observer).toBeDefined()
|
|
233
|
+
|
|
234
|
+
// Act
|
|
235
|
+
const sender = token.connect(custodialWallet)
|
|
236
|
+
await sender.transfer(ethBridgeReceiver.address, srcAmountBigint.toString())
|
|
237
|
+
|
|
238
|
+
// Wait for event
|
|
239
|
+
await delay(2000)
|
|
240
|
+
|
|
241
|
+
// Assert
|
|
242
|
+
// Ensure transfer was observed
|
|
243
|
+
expect(destinationObservations.addObservation).toHaveBeenCalled()
|
|
244
|
+
})
|
|
245
|
+
})
|
|
246
|
+
describe('for previous transfers from custodial wallet', () => {
|
|
247
|
+
it('should observe transfer', async () => {
|
|
248
|
+
// Arrange
|
|
249
|
+
const sender = token.connect(custodialWallet)
|
|
250
|
+
await sender.transfer(ethBridgeReceiver.address, srcAmountBigint.toString())
|
|
251
|
+
|
|
252
|
+
// Act
|
|
253
|
+
// Create observer after transfer
|
|
254
|
+
const observer = await ERC20TransferObserver.create({
|
|
255
|
+
destinationObservations,
|
|
256
|
+
intents,
|
|
257
|
+
provider,
|
|
258
|
+
sourceObservations,
|
|
259
|
+
tokenAddress: TOKEN_ADDRESS,
|
|
260
|
+
watchAddress: toAddress(custodialWallet.address),
|
|
261
|
+
watchDirection: 'outgoing',
|
|
262
|
+
})
|
|
263
|
+
expect(observer).toBeDefined()
|
|
264
|
+
|
|
265
|
+
// Assert
|
|
266
|
+
// Ensure transfer was observed
|
|
267
|
+
expect(destinationObservations.addObservation).toHaveBeenCalled()
|
|
268
|
+
})
|
|
269
|
+
})
|
|
270
|
+
})
|
|
271
|
+
})
|
package/src/interface/service/Observer/LiquidityPoolBridgeObserver/LiquidityPoolBridgeObserver.ts
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { assertEx } from '@xylabs/assert'
|
|
2
|
+
import type { Address, Hex } from '@xylabs/hex'
|
|
3
|
+
import {
|
|
4
|
+
asAddress, asHex, hexFromBigInt, hexToBigInt, toAddress,
|
|
5
|
+
} from '@xylabs/hex'
|
|
6
|
+
import { isNull, isUndefined } from '@xylabs/typeof'
|
|
7
|
+
import { PayloadBuilder } from '@xyo-network/payload-builder'
|
|
8
|
+
import type { LiquidityPoolBridge } from '@xyo-network/typechain'
|
|
9
|
+
import { LiquidityPoolBridge__factory } from '@xyo-network/typechain'
|
|
10
|
+
import type {
|
|
11
|
+
BridgeDestinationObservation, BridgeIntent, BridgeSourceObservation,
|
|
12
|
+
} from '@xyo-network/xl1-protocol'
|
|
13
|
+
import {
|
|
14
|
+
BridgeDestinationObservationSchema, BridgeIntentSchema, BridgeSourceObservationSchema,
|
|
15
|
+
} from '@xyo-network/xl1-protocol'
|
|
16
|
+
import type { ContractEventPayload, WebSocketProvider } from 'ethers'
|
|
17
|
+
import { getAddress } from 'ethers'
|
|
18
|
+
|
|
19
|
+
import type { BridgeServiceParams } from '../../../interface/index.ts'
|
|
20
|
+
import { BridgeObserverService } from '../Observer.ts'
|
|
21
|
+
|
|
22
|
+
export type LiquidityPoolBridgeObserverParams = 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
|
+
* The block number to start monitoring from.
|
|
33
|
+
*/
|
|
34
|
+
startBlock?: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class LiquidityPoolBridgeObserver extends BridgeObserverService<LiquidityPoolBridgeObserverParams> {
|
|
38
|
+
protected _bridge: LiquidityPoolBridge | undefined
|
|
39
|
+
protected _bridgeChainId: Hex | undefined
|
|
40
|
+
protected _bridgeRemoteChainId: Hex | undefined
|
|
41
|
+
protected _bridgeTokenAddress: Address | undefined
|
|
42
|
+
|
|
43
|
+
protected get bridge(): LiquidityPoolBridge {
|
|
44
|
+
return assertEx(this._bridge, () => new Error('Bridge contract not initialized'))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
protected get bridgeChainId(): Hex {
|
|
48
|
+
return assertEx(this._bridgeChainId, () => new Error('Bridge chain ID not initialized'))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
protected get bridgeRemoteChainId(): Hex {
|
|
52
|
+
return assertEx(this._bridgeRemoteChainId, () => new Error('Bridge remote chain ID not initialized'))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
protected get bridgeTokenAddress(): Address {
|
|
56
|
+
return assertEx(this._bridgeTokenAddress, () => new Error('Bridge token address not initialized'))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
protected get provider(): WebSocketProvider {
|
|
60
|
+
return assertEx(this.params.provider, () => new Error('Provider not initialized'))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
protected get startBlock(): number {
|
|
64
|
+
return isUndefined(this.params.startBlock) ? 0 : this.params.startBlock
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
override async createHandler(): Promise<void> {
|
|
68
|
+
const { provider, bridgeAddress } = this.params
|
|
69
|
+
|
|
70
|
+
// Connect to the bridge contract
|
|
71
|
+
this._bridge = LiquidityPoolBridge__factory.connect(getAddress(bridgeAddress), provider)
|
|
72
|
+
|
|
73
|
+
// Parse bridge network chain ID
|
|
74
|
+
const network = await provider.getNetwork()
|
|
75
|
+
this._bridgeChainId = assertEx(hexFromBigInt(network.chainId), () => new Error('Failed to parse bridgeChainId'))
|
|
76
|
+
|
|
77
|
+
// Parse bridge token address
|
|
78
|
+
const tokenAddress = await this.bridge.token()
|
|
79
|
+
this._bridgeTokenAddress = toAddress(tokenAddress)
|
|
80
|
+
|
|
81
|
+
// Parse bridge remote chain ID
|
|
82
|
+
const bridgeRemoteChain = await this.bridge.remoteChain()
|
|
83
|
+
this._bridgeRemoteChainId = asHex(bridgeRemoteChain)
|
|
84
|
+
|
|
85
|
+
// Grab the current block number to avoid processing old events
|
|
86
|
+
const currentBlock = await provider.getBlockNumber()
|
|
87
|
+
const manualIndexThroughBlockNumber = Math.max(currentBlock, this.startBlock)
|
|
88
|
+
|
|
89
|
+
// Watch for & process new events
|
|
90
|
+
await this.bridge.on(this.bridge.getEvent('BridgedToRemote'), (id, from, to, amount, remoteChain, args) => {
|
|
91
|
+
const contractEvent = args as unknown as ContractEventPayload
|
|
92
|
+
if (contractEvent?.log?.blockNumber <= manualIndexThroughBlockNumber) return
|
|
93
|
+
this.handleBridgeToRemote(id, from, to, amount, remoteChain).catch(console.error)
|
|
94
|
+
})
|
|
95
|
+
await this.bridge.on(this.bridge.getEvent('BridgedFromRemote'), (id, from, to, amount, remoteChain, args) => {
|
|
96
|
+
const contractEvent = args as unknown as ContractEventPayload
|
|
97
|
+
if (contractEvent?.log?.blockNumber <= manualIndexThroughBlockNumber) return
|
|
98
|
+
this.handleBridgeFromRemote(id, from, to, amount, remoteChain).catch(console.error)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// Process old events
|
|
102
|
+
await this.processOldEvents(this.startBlock, manualIndexThroughBlockNumber)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private async handleBridgeFromRemote(id: bigint, from: string, to: string, value: bigint, remoteChain: string): Promise<void> {
|
|
106
|
+
const srcAddress = asAddress(from)
|
|
107
|
+
const destAddress = asAddress(to)
|
|
108
|
+
const remoteChainId = asHex(remoteChain)
|
|
109
|
+
if (isUndefined (srcAddress) || isUndefined (destAddress) || isUndefined (remoteChainId)) return
|
|
110
|
+
if (remoteChainId != this.bridgeRemoteChainId) return
|
|
111
|
+
|
|
112
|
+
const intents = await this.intents.getIntentsForDestination(destAddress)
|
|
113
|
+
if (intents.length === 0) return
|
|
114
|
+
// Find the intent that matches the transfer amount (chain and token are implicit to provider and intents were already indexed by address)
|
|
115
|
+
const intent = intents.findLast((i) => {
|
|
116
|
+
try {
|
|
117
|
+
// TODO: Add source to event signature to capture intent
|
|
118
|
+
// Source address matches intent
|
|
119
|
+
// const intentSrcAddress = asAddress(i.srcAddress)
|
|
120
|
+
// if (isUndefined(intentSrcAddress) || intentSrcAddress !== srcAddress) return false
|
|
121
|
+
// Destination address matches intent
|
|
122
|
+
const intentDestAddress = asAddress(i.destAddress)
|
|
123
|
+
if (isUndefined(intentDestAddress) || intentDestAddress !== destAddress) return false
|
|
124
|
+
|
|
125
|
+
// Source token matches intent
|
|
126
|
+
const intentSrcToken = i.srcToken
|
|
127
|
+
if (isUndefined(intentSrcToken) || intentSrcToken !== this.bridgeRemoteChainId) return false
|
|
128
|
+
// Destination token matches intent
|
|
129
|
+
const intentDestToken = asHex(i.destToken)
|
|
130
|
+
if (isUndefined(intentDestToken) || intentDestToken !== this.bridgeTokenAddress) return false
|
|
131
|
+
|
|
132
|
+
// Destination amount must match
|
|
133
|
+
const intentDestAmountHex = asHex(i.destAmount)
|
|
134
|
+
if (isUndefined(intentDestAmountHex)) return false
|
|
135
|
+
const intentDestAmountBigInt = hexToBigInt(intentDestAmountHex)
|
|
136
|
+
if (intentDestAmountBigInt !== value) return false
|
|
137
|
+
|
|
138
|
+
// Otherwise matches
|
|
139
|
+
return true
|
|
140
|
+
} catch {
|
|
141
|
+
return false
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
if (isUndefined(intent)) return
|
|
146
|
+
// Found matching intent, check if observation already exists
|
|
147
|
+
const observations = this.destinationObservations
|
|
148
|
+
const existing = await observations.getObservationForIntent(intent)
|
|
149
|
+
// Only add if observation not already existing
|
|
150
|
+
if (isUndefined(existing) || isNull(existing)) {
|
|
151
|
+
const { schema, ...rest } = intent
|
|
152
|
+
const observation: BridgeDestinationObservation = { schema: BridgeDestinationObservationSchema, ...rest }
|
|
153
|
+
await observations.addObservation(observation, intent)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private async handleBridgeToRemote(id: bigint, from: string, to: string, value: bigint, remoteChain: string): Promise<void> {
|
|
158
|
+
const srcAddress = asAddress(from)
|
|
159
|
+
const destAddress = asAddress(to)
|
|
160
|
+
const remoteChainId = asHex(remoteChain)
|
|
161
|
+
if (isUndefined (srcAddress) || isUndefined (destAddress) || isUndefined (remoteChainId)) return
|
|
162
|
+
if (remoteChainId != this.bridgeRemoteChainId) return
|
|
163
|
+
|
|
164
|
+
// If we don't have an intent for this nonce already
|
|
165
|
+
const nonce = hexFromBigInt(id)
|
|
166
|
+
let intent = await this.intents.getIntentByNonce(nonce)
|
|
167
|
+
if (isUndefined(intent)) {
|
|
168
|
+
// Create observation for intent if none exists
|
|
169
|
+
intent = new PayloadBuilder<BridgeIntent>({ schema: BridgeIntentSchema }).fields({
|
|
170
|
+
nonce,
|
|
171
|
+
dest: this.bridgeRemoteChainId,
|
|
172
|
+
destAddress,
|
|
173
|
+
destAmount: hexFromBigInt(value),
|
|
174
|
+
destToken: this.bridgeRemoteChainId,
|
|
175
|
+
src: this.bridgeChainId,
|
|
176
|
+
srcAddress,
|
|
177
|
+
srcAmount: hexFromBigInt(value),
|
|
178
|
+
srcToken: this.bridgeTokenAddress,
|
|
179
|
+
}).build()
|
|
180
|
+
await this.intents.addIntent(intent)
|
|
181
|
+
}
|
|
182
|
+
// Ensure we have an intent to match against
|
|
183
|
+
if (isUndefined(intent)) return
|
|
184
|
+
// Found matching intent, check if observation already exists
|
|
185
|
+
const observations = this.sourceObservations
|
|
186
|
+
const existing = await observations.getObservationForIntent(intent)
|
|
187
|
+
// Only add if observation not already existing
|
|
188
|
+
if (isUndefined(existing) || isNull(existing)) {
|
|
189
|
+
const { schema, ...rest } = intent
|
|
190
|
+
const observation: BridgeSourceObservation = { schema: BridgeSourceObservationSchema, ...rest }
|
|
191
|
+
await observations.addObservation(observation, intent)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private async processOldEvents(startBlock: number, endBlock: number): Promise<void> {
|
|
196
|
+
const bridgedToRemote = await this.bridge.queryFilter(this.bridge.filters.BridgedToRemote(), startBlock, endBlock)
|
|
197
|
+
for (const log of bridgedToRemote) {
|
|
198
|
+
const {
|
|
199
|
+
id, srcAddress, destAddress, amount, destToken,
|
|
200
|
+
} = log.args
|
|
201
|
+
await this.handleBridgeToRemote(id, srcAddress, destAddress, amount, destToken)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const bridgedFromRemote = await this.bridge.queryFilter(this.bridge.filters.BridgedFromRemote(), startBlock, endBlock)
|
|
205
|
+
for (const log of bridgedFromRemote) {
|
|
206
|
+
const {
|
|
207
|
+
id, srcAddress, destAddress, amount, srcToken,
|
|
208
|
+
} = log.args
|
|
209
|
+
await this.handleBridgeFromRemote(id, srcAddress, destAddress, amount, srcToken)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './LiquidityPoolBridgeObserver.ts'
|