From 405463d868f7b0eb70213ed0c7fffa9492ad8cb1 Mon Sep 17 00:00:00 2001 From: Kevin Peters Date: Fri, 13 Sep 2024 10:39:43 -0500 Subject: [PATCH 1/3] cloud_functions: add destChain param to getSolanaEvents --- cloud_functions/src/getSolanaEvents.ts | 66 +++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/cloud_functions/src/getSolanaEvents.ts b/cloud_functions/src/getSolanaEvents.ts index e2cc128f..746c27a3 100644 --- a/cloud_functions/src/getSolanaEvents.ts +++ b/cloud_functions/src/getSolanaEvents.ts @@ -12,6 +12,19 @@ import * as ethers from 'ethers'; import * as bs58 from 'bs58'; import { deserialize } from 'borsh'; import { assertEnvironmentVariable, EventData } from '@wormhole-foundation/wormhole-monitor-common'; +import { Chain, ChainId, chainToChainId, isChain } from '@wormhole-foundation/sdk-base'; + +function convertDefiLlamaChainToChainId(chain: string): ChainId { + if (chain === 'avax') { + return chainToChainId('Avalanche'); + } + // DefiLlama uses lowercase chain names + chain = chain.charAt(0).toUpperCase() + chain.slice(1); + if (!isChain(chain as Chain)) { + throw new Error('invalid chain'); + } + return chainToChainId(chain as Chain); +} export async function getSolanaEvents(req: any, res: any) { res.set('Access-Control-Allow-Origin', '*'); @@ -31,12 +44,24 @@ export async function getSolanaEvents(req: any, res: any) { res.status(400).send('toSlot is required'); return; } + let destChain: ChainId | undefined = undefined; + if (req.query.destChain) { + try { + destChain = convertDefiLlamaChainToChainId(req.query.destChain); + } catch (e) { + console.error(e); + res.status(400).send('invalid destChain'); + return; + } + } try { const fromSlot = Number(req.query.fromSlot); const toSlot = Number(req.query.toSlot); - console.log(`fetching events from ${fromSlot} to ${toSlot}`); + console.log( + `fetching events from ${fromSlot} to ${toSlot}${destChain ? ` to chain ${destChain}` : ''}` + ); // the RPC doesn't store blocks that are too old - const events = fromSlot < 232090284 ? [] : await _getSolanaEvents(fromSlot, toSlot); + const events = fromSlot < 232090284 ? [] : await _getSolanaEvents(fromSlot, toSlot, destChain); console.log(`fetched ${events.length} events`); res.json(events); } catch (e) { @@ -82,7 +107,7 @@ const PostMessageDataSchema = { * @param toSlot The ending slot to retrieve events from. * @returns An array of EventData objects representing the events that occurred within the given slot range. */ -const _getSolanaEvents = async (fromSlot: number, toSlot: number) => { +const _getSolanaEvents = async (fromSlot: number, toSlot: number, destChain?: ChainId) => { const txs = await getParsedTransactions(fromSlot, toSlot, tokenBridge); const events = txs.reduce((acc, tx) => { if (!tx || !tx.blockTime || tx.meta?.err) { @@ -97,7 +122,7 @@ const _getSolanaEvents = async (fromSlot: number, toSlot: number) => { if (!innerIx || innerIx.instructions.length === 0) { return; } - const event = getEventData(tx, ix, innerIx.instructions); + const event = getEventData(tx, ix, innerIx.instructions, destChain); if (event) { acc.push(event); } @@ -106,7 +131,7 @@ const _getSolanaEvents = async (fromSlot: number, toSlot: number) => { tx.meta?.innerInstructions?.forEach((innerIx: any) => { innerIx.instructions.forEach((ix: any, index: any) => { if (isTokenBridgeIx(ix)) { - const event = getEventData(tx, ix, innerIx.instructions.slice(index + 1)); + const event = getEventData(tx, ix, innerIx.instructions.slice(index + 1), destChain); if (event) { acc.push(event); } @@ -121,7 +146,8 @@ const _getSolanaEvents = async (fromSlot: number, toSlot: number) => { const getEventData = ( tx: ParsedTransactionWithMeta, tokenBridgeIx: PartiallyDecodedInstruction, - innerIxs: (ParsedInstruction | PartiallyDecodedInstruction)[] + innerIxs: (ParsedInstruction | PartiallyDecodedInstruction)[], + destChain?: ChainId ): EventData | undefined => { const data = bs58.decode(tokenBridgeIx.data); if (data.length === 0) { @@ -139,6 +165,31 @@ const getEventData = ( isTransferIx(ix) && ix.parsed.info?.authority === transferAuthority ); if (transferIx) { + if (destChain) { + const coreBridgeIx = innerIxs.find( + (ix): ix is PartiallyDecodedInstruction => + ix.programId.equals(coreBridge) && + (ix as PartiallyDecodedInstruction).data !== undefined + ); + const coreBridgeIxData = coreBridgeIx?.data + ? Buffer.from(bs58.decode(coreBridgeIx.data)) + : undefined; + if ( + coreBridgeIxData && + coreBridgeIxData.length > 0 && + coreBridgeIxData[0] === CoreBridgeIxId.PostMessage + ) { + const postMessageData: any = deserialize( + PostMessageDataSchema, + coreBridgeIxData.subarray(1) + ); + const payload = Buffer.from(postMessageData.payload); + const toChain = payload.readUInt16BE(99); + if (toChain !== destChain) { + return undefined; + } + } + } return { blockNumber, txHash, @@ -177,6 +228,9 @@ const getEventData = ( const payload = Buffer.from(postMessageData.payload); const originChain = payload.readUint16BE(65); const toChain = payload.readUInt16BE(99); + if (destChain && toChain !== destChain) { + return undefined; + } // if this is a wrapped token being burned and not being sent to its origin chain, // then it should be included in the volume by fixing the `to` address // https://docs.wormhole.com/wormhole/explore-wormhole/vaa#token-transfer From 899ac2430b8c13dd7b70fcbfbcab5128eb762e35 Mon Sep 17 00:00:00 2001 From: Kevin Peters Date: Fri, 27 Sep 2024 13:43:09 -0500 Subject: [PATCH 2/3] superfluous casts remove --- cloud_functions/src/getSolanaEvents.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cloud_functions/src/getSolanaEvents.ts b/cloud_functions/src/getSolanaEvents.ts index 746c27a3..6989c941 100644 --- a/cloud_functions/src/getSolanaEvents.ts +++ b/cloud_functions/src/getSolanaEvents.ts @@ -12,7 +12,7 @@ import * as ethers from 'ethers'; import * as bs58 from 'bs58'; import { deserialize } from 'borsh'; import { assertEnvironmentVariable, EventData } from '@wormhole-foundation/wormhole-monitor-common'; -import { Chain, ChainId, chainToChainId, isChain } from '@wormhole-foundation/sdk-base'; +import { ChainId, chainToChainId, isChain } from '@wormhole-foundation/sdk-base'; function convertDefiLlamaChainToChainId(chain: string): ChainId { if (chain === 'avax') { @@ -20,10 +20,10 @@ function convertDefiLlamaChainToChainId(chain: string): ChainId { } // DefiLlama uses lowercase chain names chain = chain.charAt(0).toUpperCase() + chain.slice(1); - if (!isChain(chain as Chain)) { + if (!isChain(chain)) { throw new Error('invalid chain'); } - return chainToChainId(chain as Chain); + return chainToChainId(chain); } export async function getSolanaEvents(req: any, res: any) { From 3f14b053069340b555a0d3d550b3e21a9cbcfdf2 Mon Sep 17 00:00:00 2001 From: Kevin Peters Date: Fri, 27 Sep 2024 13:47:45 -0500 Subject: [PATCH 3/3] add timestamp to event data --- cloud_functions/src/getSolanaEvents.ts | 4 ++++ common/src/types.ts | 1 + 2 files changed, 5 insertions(+) diff --git a/cloud_functions/src/getSolanaEvents.ts b/cloud_functions/src/getSolanaEvents.ts index 6989c941..c59331f6 100644 --- a/cloud_functions/src/getSolanaEvents.ts +++ b/cloud_functions/src/getSolanaEvents.ts @@ -198,6 +198,7 @@ const getEventData = ( token: tokenBridgeIx.accounts[3]?.toString() || '', // mint account amount: transferIx.parsed.info?.amount, isDeposit: true, + timestamp: tx.blockTime || undefined, }; } break; @@ -243,6 +244,7 @@ const getEventData = ( token: tokenBridgeIx.accounts[4]?.toString() || '', // mint account amount: burnIx.parsed.info?.amount, isDeposit: false, + timestamp: tx.blockTime || undefined, }; } break; @@ -268,6 +270,7 @@ const getEventData = ( token: tokenBridgeIx.accounts[mintAccountIndex]?.toString() || '', // mint account amount: transferIx.parsed.info?.amount, isDeposit: false, + timestamp: tx.blockTime || undefined, }; } break; @@ -294,6 +297,7 @@ const getEventData = ( token: mintToIx.parsed.info?.mint, amount: mintToIx.parsed.info?.amount, isDeposit: false, + timestamp: tx.blockTime || undefined, }; } break; diff --git a/common/src/types.ts b/common/src/types.ts index 3b7f6606..40646c99 100644 --- a/common/src/types.ts +++ b/common/src/types.ts @@ -154,6 +154,7 @@ export interface EventData { token: string; amount: string; isDeposit: boolean; + timestamp?: number; } export type TokenAmount = {