-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test: initial swap monitor testing ft_swap: parse redeem params to get fill vaa add id for redeem event ft_watcher: parse swap event and input ft_watcher: plug swap layer into ft watcher Signed-off-by: bingyuyap <[email protected]>
- Loading branch information
Showing
10 changed files
with
420 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { ethers } from "ethers"; | ||
|
||
function decodeLog(log: any) { | ||
console.log("Decoding log:"); | ||
console.log("Contract address:", log.address); | ||
|
||
console.log("\nTopics:"); | ||
console.log("Event signature:", log.topics[0]); | ||
console.log("Indexed parameter:", log.topics[1]); | ||
|
||
console.log("\nDecoding indexed parameter:"); | ||
const indexedAddress = ethers.utils.getAddress('0x' + log.topics[1].slice(-40)); | ||
console.log("Address:", indexedAddress); | ||
|
||
console.log("\nDecoding data:"); | ||
const decodedData = ethers.utils.defaultAbiCoder.decode( | ||
['address', 'uint256', 'uint256'], | ||
log.data | ||
); | ||
|
||
console.log("Token address:", decodedData[0]); | ||
console.log("Amount:", decodedData[1].toString()); | ||
console.log("Fee:", decodedData[2].toString()); | ||
|
||
// Try to identify the event | ||
const eventSignature = "Redeemed(address,address,uint256,uint256)"; | ||
const calculatedEventHash = ethers.utils.id(eventSignature); | ||
|
||
console.log("\nEvent identification:"); | ||
console.log("Calculated hash for 'Redeemed(address,address,uint256,uint256)':", calculatedEventHash); | ||
console.log("Matches log topic 0:", calculatedEventHash === log.topics[0]); | ||
} | ||
|
||
// Log data | ||
const logData = { | ||
transactionIndex: 0, | ||
blockNumber: 20034945, | ||
transactionHash: '0xcbb19feeadaa8949d5d2a3f253ecfd94d329a5fc6165c87acb297c6f44e19755', | ||
address: '0xdA11B3bc8705D84BEae4a796035bDcCc9b59d1ee', | ||
topics: [ | ||
'0x5cdf07ad0fc222442720b108e3ed4c4640f0fadc2ab2253e66f259a0fea83480', | ||
'0x00000000000000000000000095ced938f7991cd0dfcb48f0a06a40fa1af46ebc' | ||
], | ||
data: '0x000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000004a817c8000000000000000000000000000000000000000000000000000000000000000000', | ||
logIndex: 6, | ||
blockHash: '0x8e9a4f48e546768f8d88a148702d83ba52b15da1c7377ef1d0b1514989b62cbd' | ||
}; | ||
|
||
decodeLog(logData); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { parseVaa } from '@wormhole-foundation/wormhole-monitor-common'; | ||
import { ethers } from 'ethers'; | ||
const provider = new ethers.providers.JsonRpcProvider('http://localhost:8548'); | ||
|
||
const redeemEvent = "Redeemed(address,address,uint256,uint256)"; | ||
const redeemEventHash = ethers.utils.id(redeemEvent); | ||
const redeemInterface = new ethers.utils.Interface([ | ||
"event Redeemed(address indexed recipient, address outputToken, uint256 outputAmount, uint256 relayingFee)" | ||
]); | ||
|
||
async function logBlkTxs(blockNumber: number) { | ||
// Get a block by number | ||
const block = await provider.getBlock(blockNumber); | ||
|
||
// Get all transactions in a block | ||
await Promise.all( | ||
block.transactions.map((txHash) => logTx(txHash))); | ||
} | ||
|
||
async function logTx(txHash: string) { | ||
const tx = await provider.getTransaction(txHash); | ||
console.log('tx',tx) | ||
const receipt = await provider.getTransactionReceipt(txHash); | ||
const REDEEM_SELECTOR = '0x604009a9'; | ||
if (tx.data.startsWith(REDEEM_SELECTOR)) { | ||
console.log('This transaction calls the redeem function'); | ||
console.log(receipt); | ||
// Remove the function selector (first 4 bytes) | ||
const inputData = '0x' + tx.data.slice(10); | ||
|
||
|
||
// Use AbiCoder to decode the raw input data | ||
const abiCoder = new ethers.utils.AbiCoder(); | ||
|
||
try { | ||
const decodedInput = abiCoder.decode(['bytes', 'tuple(bytes, bytes, bytes)'], inputData); | ||
|
||
// If you want to further parse the encodedWormholeMessage | ||
const encodedWormholeMessage = decodedInput[1][0]; | ||
if (encodedWormholeMessage && encodedWormholeMessage.length >= 8) { | ||
const vaaBytes = Buffer.from(encodedWormholeMessage.slice(2), 'hex'); // Remove '0x' if present | ||
const parsedVaa = parseVaa(vaaBytes); | ||
|
||
console.log(parsedVaa); | ||
} | ||
} catch (error) { | ||
console.error('Error decoding input data:', error); | ||
} | ||
|
||
const redeemLogs = receipt.logs.filter((log) => log.topics[0] === redeemEventHash); | ||
|
||
console.log(redeemLogs[0]) | ||
// There should only be one redeem invocation | ||
if (redeemLogs.length != 1) throw new Error(`redeem has ${redeemLogs.length} logs`) | ||
const decodedLog = redeemInterface.parseLog(redeemLogs[0]); | ||
} else { | ||
console.log('This transaction does not call the redeem function'); | ||
} | ||
} | ||
async function logTxs() { | ||
// Get the latest block number | ||
const blockNumber = await provider.getBlockNumber(); | ||
|
||
for (let i = 20034947; i <= blockNumber; ++i) { | ||
await logBlkTxs(i); | ||
console.log('checked block', i); | ||
} | ||
} | ||
|
||
logTxs(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import { ethers } from 'ethers'; | ||
import { RedeemSwap } from '../types'; | ||
import { parseVaa } from '@wormhole-foundation/wormhole-monitor-common'; | ||
|
||
class SwapLayerParser { | ||
private provider: ethers.providers.JsonRpcProvider; | ||
private swapLayerAddress: string; | ||
private swapLayerInterface: ethers.utils.Interface; | ||
|
||
constructor(provider: ethers.providers.JsonRpcProvider, swapLayerAddress: string) { | ||
this.provider = provider; | ||
this.swapLayerAddress = swapLayerAddress; | ||
this.swapLayerInterface = new ethers.utils.Interface([ | ||
"event Redeemed(address indexed recipient, address outputToken, uint256 outputAmount, uint256 relayingFee)" | ||
]); | ||
} | ||
|
||
async parseSwapLayerTransaction(txHash: string, blockTime: number): Promise<RedeemSwap | null> { | ||
const receipt = await this.provider.getTransactionReceipt(txHash); | ||
|
||
const tx = await this.provider.getTransaction(txHash); | ||
if (!receipt || !tx) return null; | ||
|
||
// Remove the function selector (first 4 bytes) | ||
const inputData = '0x' + tx.data.slice(10); | ||
|
||
|
||
// Use AbiCoder to decode the raw input data | ||
let fillVaaId: string = '' | ||
const abiCoder = new ethers.utils.AbiCoder(); | ||
try { | ||
const decodedInput = abiCoder.decode(['bytes', 'tuple(bytes, bytes, bytes)'], inputData); | ||
|
||
// If you want to further parse the encodedWormholeMessage | ||
const encodedWormholeMessage = decodedInput[1][0]; | ||
if (encodedWormholeMessage && encodedWormholeMessage.length >= 8) { | ||
const vaaBytes = Buffer.from(encodedWormholeMessage.slice(2), 'hex'); // Remove '0x' if present | ||
const parsedVaa = parseVaa(vaaBytes); | ||
|
||
fillVaaId = `${parsedVaa.emitterChain}/${parsedVaa.emitterAddress.toString('hex')}/${parsedVaa.sequence}`; | ||
} | ||
} catch (error) { | ||
console.error('Error decoding input data:', error); | ||
} | ||
|
||
const swapEvent = receipt.logs | ||
.filter(log => log.address.toLowerCase() === this.swapLayerAddress.toLowerCase()) | ||
.map(log => { | ||
try { | ||
return this.swapLayerInterface.parseLog(log); | ||
} catch (e) { | ||
return null; | ||
} | ||
}) | ||
.find(event => event && event.name === 'Redeemed'); | ||
|
||
if (!swapEvent) return null; | ||
|
||
return { | ||
tx_hash: txHash, | ||
recipient: swapEvent.args.recipient, | ||
output_amount: swapEvent.args.outputAmount.toString(), | ||
output_token: swapEvent.args.outputToken, | ||
timestamp: new Date(blockTime * 1000), | ||
relaying_fee: swapEvent.args.relayingFee.toString(), | ||
fill_vaa_id: fillVaaId | ||
}; | ||
} | ||
|
||
async getFTSwapInRange(fromBlock: number, toBlock: number): Promise<{ | ||
results: RedeemSwap[]; | ||
lastBlockTime: number; | ||
}> { | ||
const filter = { | ||
address: this.swapLayerAddress, | ||
fromBlock, | ||
toBlock, | ||
topics: [this.swapLayerInterface.getEventTopic('Redeemed')] | ||
}; | ||
|
||
const logs = await this.provider.getLogs(filter); | ||
|
||
const blocks: Map<number, ethers.providers.Block> = new Map(); | ||
|
||
const results = await Promise.all( | ||
logs.map(async log => { | ||
const blockTime = await this.fetchBlockTime(blocks, log.blockNumber); | ||
const txHash = log.transactionHash; | ||
return this.parseSwapLayerTransaction(txHash, blockTime); | ||
}) | ||
); | ||
|
||
const lastBlock = await this.provider.getBlock(toBlock); | ||
|
||
return { | ||
results: results.filter((result): result is RedeemSwap => result !== null), | ||
lastBlockTime: lastBlock.timestamp | ||
}; | ||
} | ||
|
||
private async fetchBlockTime( | ||
blocks: Map<number, ethers.providers.Block>, | ||
blockNumber: number | ||
): Promise<number> { | ||
let block = blocks.get(blockNumber); | ||
if (!block) { | ||
block = await this.provider.getBlock(blockNumber); | ||
blocks.set(blockNumber, block); | ||
} | ||
return block.timestamp; | ||
} | ||
} | ||
|
||
export default SwapLayerParser; | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.