diff --git a/packages/indexer-database/src/entities/evm/SwapFlowInitialized.ts b/packages/indexer-database/src/entities/evm/SwapFlowInitialized.ts new file mode 100644 index 00000000..e260507f --- /dev/null +++ b/packages/indexer-database/src/entities/evm/SwapFlowInitialized.ts @@ -0,0 +1,78 @@ +import { + Column, + Entity, + PrimaryGeneratedColumn, + Index, + CreateDateColumn, + DeleteDateColumn, + Unique, +} from "typeorm"; + +@Entity({ schema: "evm" }) +@Unique("UK_swapFlowInitialized_chain_block_tx_log", [ + "chainId", + "blockNumber", + "transactionHash", + "logIndex", +]) +@Index("IX_SwapFlowInitialized_chainId", ["chainId"]) +@Index("IX_SwapFlowInitialized_quoteNonce", ["quoteNonce"]) +@Index("IX_SwapFlowInitialized_finalRecipient", ["finalRecipient"]) +@Index("IX_SwapFlowInitialized_blockNumber", ["blockNumber"]) +@Index("IX_SwapFlowInitialized_createdAt", ["createdAt"]) +@Index("IX_SwapFlowInitialized_deletedAt", ["deletedAt"]) +export class SwapFlowInitialized { + @PrimaryGeneratedColumn() + id: number; + + @Column() + chainId: string; + + @Column({ nullable: true }) + quoteNonce: string; + + @Column() + finalRecipient: string; + + @Column() + finalToken: string; + + @Column({ type: "bigint" }) + evmAmountIn: string; + + @Column({ type: "bigint" }) + bridgingFeesIncurred: string; + + @Column({ type: "bigint" }) + coreAmountIn: string; + + @Column() + minAmountToSend: string; + + @Column() + maxAmountToSend: string; + + @Column() + blockNumber: number; + + @Column() + transactionHash: string; + + @Column() + transactionIndex: number; + + @Column() + logIndex: number; + + @Column() + finalised: boolean; + + @Column() + blockTimestamp: Date; + + @CreateDateColumn() + createdAt: Date; + + @DeleteDateColumn({ nullable: true }) + deletedAt?: Date; +} diff --git a/packages/indexer-database/src/entities/index.ts b/packages/indexer-database/src/entities/index.ts index 6f5e747c..7ea0abe4 100644 --- a/packages/indexer-database/src/entities/index.ts +++ b/packages/indexer-database/src/entities/index.ts @@ -44,3 +44,4 @@ export * from "./OftTransfer"; // HyperEVM export * from "./evm/SimpleTransferFlowCompleted"; +export * from "./evm/SwapFlowInitialized"; diff --git a/packages/indexer-database/src/main.ts b/packages/indexer-database/src/main.ts index d50d521e..8e42d488 100644 --- a/packages/indexer-database/src/main.ts +++ b/packages/indexer-database/src/main.ts @@ -75,6 +75,7 @@ export const createDataSource = (config: DatabaseConfig): DataSource => { entities.OftTransfer, // HyperEVM entities.SimpleTransferFlowCompleted, + entities.SwapFlowInitialized, ], migrationsTableName: "_migrations", migrations: ["migrations/*.ts"], diff --git a/packages/indexer/src/data-indexing/adapter/hyper-evm/model.ts b/packages/indexer/src/data-indexing/adapter/hyper-evm/model.ts index fc5096bc..cc52b6f9 100644 --- a/packages/indexer/src/data-indexing/adapter/hyper-evm/model.ts +++ b/packages/indexer/src/data-indexing/adapter/hyper-evm/model.ts @@ -19,6 +19,19 @@ export type SimpleTransferFlowCompleted = { export type SimpleTransferFlowCompletedWithBlock = SimpleTransferFlowCompleted & Block; +export type SwapFlowInitialized = { + quoteNonce: string; + finalRecipient: string; + finalToken: string; + evmAmountIn: string; + bridgingFeesIncurred: string; + coreAmountIn: string; + minAmountToSend: string; + maxAmountToSend: string; +}; + +export type SwapFlowInitializedWithBlock = SwapFlowInitialized & Block; + export const SimpleTransferFlowCompletedABI = new utils.Interface([ `event SimpleTransferFlowCompleted( bytes32 indexed quoteNonce, @@ -29,3 +42,16 @@ export const SimpleTransferFlowCompletedABI = new utils.Interface([ uint256 evmAmountSponsored )`, ]); + +export const SwapFlowInitializedABI = new utils.Interface([ + `event SwapFlowInitialized( + bytes32 indexed quoteNonce, + address indexed finalRecipient, + address indexed finalToken, + uint256 evmAmountIn, + uint256 bridgingFeesIncurred, + uint256 coreAmountIn, + uint64 minAmountToSend, + uint64 maxAmountToSend + )`, +]); diff --git a/packages/indexer/src/data-indexing/adapter/hyper-evm/service.ts b/packages/indexer/src/data-indexing/adapter/hyper-evm/service.ts index 52b7878d..fe7ee1b5 100644 --- a/packages/indexer/src/data-indexing/adapter/hyper-evm/service.ts +++ b/packages/indexer/src/data-indexing/adapter/hyper-evm/service.ts @@ -5,6 +5,9 @@ import { SimpleTransferFlowCompleted, SimpleTransferFlowCompletedABI, SimpleTransferFlowCompletedWithBlock, + SwapFlowInitialized, + SwapFlowInitializedABI, + SwapFlowInitializedWithBlock, } from "./model"; // we need to fetch only recent events, so @@ -66,3 +69,38 @@ export async function getSimpleTransferFlowCompletedEvents( }; }); } + +export async function getSwapFlowInitializedEvents( + provider: ethers.providers.JsonRpcProvider, + address: string, + fromBlock: number, + toBlock: number, +): Promise { + const eventFilter = { + address, + topics: [SwapFlowInitializedABI.getEventTopic("SwapFlowInitialized")], + }; + const logs = await provider.getLogs({ + ...eventFilter, + fromBlock, + toBlock, + }); + + return logs.map((log) => { + const decodedLog = SwapFlowInitializedABI.parseLog(log); + return { + quoteNonce: decodedLog.args.quoteNonce, + finalRecipient: decodedLog.args.finalRecipient, + finalToken: decodedLog.args.finalToken, + evmAmountIn: decodedLog.args.evmAmountIn.toString(), + bridgingFeesIncurred: decodedLog.args.bridgingFeesIncurred.toString(), + coreAmountIn: decodedLog.args.coreAmountIn.toString(), + minAmountToSend: decodedLog.args.minAmountToSend.toString(), + maxAmountToSend: decodedLog.args.maxAmountToSend.toString(), + blockNumber: log.blockNumber, + logIndex: log.logIndex, + transactionIndex: log.transactionIndex, + transactionHash: log.transactionHash, + }; + }); +} diff --git a/packages/indexer/src/data-indexing/service/HyperEVMIndexerDataHandler.ts b/packages/indexer/src/data-indexing/service/HyperEVMIndexerDataHandler.ts index 9175a553..06b35c90 100644 --- a/packages/indexer/src/data-indexing/service/HyperEVMIndexerDataHandler.ts +++ b/packages/indexer/src/data-indexing/service/HyperEVMIndexerDataHandler.ts @@ -7,12 +7,18 @@ import { SimpleTransferFlowCompletedRepository } from "../../database/SimpleTran import { getIndexingStartBlockNumber, getSimpleTransferFlowCompletedEvents, + getSwapFlowInitializedEvents, HYPERCORE_FLOW_EXECUTOR_ADDRESS, } from "../adapter/hyper-evm/service"; -import { SimpleTransferFlowCompletedWithBlock } from "../adapter/hyper-evm/model"; +import { + SimpleTransferFlowCompletedWithBlock, + SwapFlowInitializedWithBlock, +} from "../adapter/hyper-evm/model"; +import { SwapFlowInitializedRepository } from "../../database/SwapFlowInitializedRepository"; export type FetchEventsResult = { simpleTransferFlowCompletedEvents: SimpleTransferFlowCompletedWithBlock[]; + swapFlowInitializedEvents: SwapFlowInitializedWithBlock[]; blocks: Record; }; export type StoreEventsResult = {}; @@ -25,6 +31,7 @@ export class HyperEVMIndexerDataHandler implements IndexerDataHandler { private chainId: number, private provider: across.providers.RetryProvider, private simpleTransferFlowCompletedRepository: SimpleTransferFlowCompletedRepository, + private swapFlowInitializedRepository: SwapFlowInitializedRepository, ) { this.isInitialized = false; } @@ -61,10 +68,16 @@ export class HyperEVMIndexerDataHandler implements IndexerDataHandler { const events = await this.fetchEventsByRange(blockRange); await this.storeEvents(events, lastFinalisedBlock); const timeToStoreEvents = performance.now(); - await this.simpleTransferFlowCompletedRepository.deleteUnfinalisedSimpleTransferFlowCompletedEvents( - this.chainId, - lastFinalisedBlock, - ); + await Promise.all([ + this.simpleTransferFlowCompletedRepository.deleteUnfinalisedSimpleTransferFlowCompletedEvents( + this.chainId, + lastFinalisedBlock, + ), + this.swapFlowInitializedRepository.deleteUnfinalisedSwapFlowInitializedEvents( + this.chainId, + lastFinalisedBlock, + ), + ]); const timeToDeleteEvents = performance.now(); const finalPerfTime = performance.now(); @@ -91,27 +104,40 @@ export class HyperEVMIndexerDataHandler implements IndexerDataHandler { }); return { simpleTransferFlowCompletedEvents: [], + swapFlowInitializedEvents: [], blocks: {}, }; } - const simpleTransferFlowCompletedEvents = - await getSimpleTransferFlowCompletedEvents( - this.provider, - address, - blockRange.from, - blockRange.to, - ); + const [simpleTransferFlowCompletedEvents, swapFlowInitializedEvents] = + await Promise.all([ + getSimpleTransferFlowCompletedEvents( + this.provider, + address, + blockRange.from, + blockRange.to, + ), + getSwapFlowInitializedEvents( + this.provider, + address, + blockRange.from, + blockRange.to, + ), + ]); const blocks = await this.getBlocks([ - ...new Set( - simpleTransferFlowCompletedEvents.map((event) => + ...new Set([ + ...simpleTransferFlowCompletedEvents.map((event) => event.blockNumber.toString(), ), - ), + ...swapFlowInitializedEvents.map((event) => + event.blockNumber.toString(), + ), + ]), ]); return { simpleTransferFlowCompletedEvents, + swapFlowInitializedEvents, blocks, }; } @@ -135,15 +161,27 @@ export class HyperEVMIndexerDataHandler implements IndexerDataHandler { events: FetchEventsResult, lastFinalisedBlock: number, ): Promise { - const { simpleTransferFlowCompletedEvents, blocks } = events; + const { + simpleTransferFlowCompletedEvents, + swapFlowInitializedEvents, + blocks, + } = events; const blocksTimestamps = this.getBlocksTimestamps(blocks); - await this.simpleTransferFlowCompletedRepository.formatAndSaveSimpleTransferFlowCompletedEvents( - simpleTransferFlowCompletedEvents, - lastFinalisedBlock, - this.chainId, - blocksTimestamps, - ); + await Promise.all([ + this.simpleTransferFlowCompletedRepository.formatAndSaveSimpleTransferFlowCompletedEvents( + simpleTransferFlowCompletedEvents, + lastFinalisedBlock, + this.chainId, + blocksTimestamps, + ), + this.swapFlowInitializedRepository.formatAndSaveSwapFlowInitializedEvents( + swapFlowInitializedEvents, + lastFinalisedBlock, + this.chainId, + blocksTimestamps, + ), + ]); return {}; } diff --git a/packages/indexer/src/data-indexing/service/HyperEVMIndexerManager.ts b/packages/indexer/src/data-indexing/service/HyperEVMIndexerManager.ts index f8f729cd..80104d6d 100644 --- a/packages/indexer/src/data-indexing/service/HyperEVMIndexerManager.ts +++ b/packages/indexer/src/data-indexing/service/HyperEVMIndexerManager.ts @@ -11,6 +11,7 @@ import { Indexer, EvmIndexer } from "./Indexer"; import { RetryProvidersFactory } from "../../web3/RetryProvidersFactory"; import { HyperEVMIndexerDataHandler } from "./HyperEVMIndexerDataHandler"; import { SimpleTransferFlowCompletedRepository } from "../../database/SimpleTransferFlowCompletedRepository"; +import { SwapFlowInitializedRepository } from "../../database/SwapFlowInitializedRepository"; const MAX_BLOCK_RANGE_SIZE = 1000; @@ -23,6 +24,7 @@ export class HyperEVMIndexerManager { private postgres: DataSource, private retryProvidersFactory: RetryProvidersFactory, private simpleTransferFlowCompletedRepository: SimpleTransferFlowCompletedRepository, + private swapFlowInitializedRepository: SwapFlowInitializedRepository, private testNet: boolean = false, ) {} @@ -65,6 +67,7 @@ export class HyperEVMIndexerManager { chainId, provider, this.simpleTransferFlowCompletedRepository, + this.swapFlowInitializedRepository, ); const indexer = new EvmIndexer( { diff --git a/packages/indexer/src/data-indexing/tests/HyperEVMIndexerDataHandler.integration.test.ts b/packages/indexer/src/data-indexing/tests/HyperEVMIndexerDataHandler.integration.test.ts index c2431e6c..8bbbb793 100644 --- a/packages/indexer/src/data-indexing/tests/HyperEVMIndexerDataHandler.integration.test.ts +++ b/packages/indexer/src/data-indexing/tests/HyperEVMIndexerDataHandler.integration.test.ts @@ -6,6 +6,7 @@ import * as sinon from "sinon"; import { getTestDataSource } from "../../tests/setup"; import { HyperEVMIndexerDataHandler } from "../service/HyperEVMIndexerDataHandler"; import { SimpleTransferFlowCompletedRepository } from "../../database/SimpleTransferFlowCompletedRepository"; +import { SwapFlowInitializedRepository } from "../../database/SwapFlowInitializedRepository"; import { BlockRange } from "../model"; import { createTestRetryProvider } from "../../tests/testProvider"; import { entities } from "../../../../indexer-database/dist/src"; @@ -14,6 +15,7 @@ import { CHAIN_IDs } from "@across-protocol/constants"; describe("HyperEVMIndexerDataHandler", () => { let dataSource: DataSource; let simpleTransferFlowCompletedRepository: SimpleTransferFlowCompletedRepository; + let swapFlowInitializedRepository: SwapFlowInitializedRepository; let logger: Logger; let provider: across.providers.RetryProvider; let handler: HyperEVMIndexerDataHandler; @@ -30,6 +32,10 @@ describe("HyperEVMIndexerDataHandler", () => { simpleTransferFlowCompletedRepository = new SimpleTransferFlowCompletedRepository(dataSource, logger); + swapFlowInitializedRepository = new SwapFlowInitializedRepository( + dataSource, + logger, + ); provider = createTestRetryProvider(CHAIN_IDs.HYPEREVM_TESTNET, logger); handler = new HyperEVMIndexerDataHandler( @@ -37,6 +43,7 @@ describe("HyperEVMIndexerDataHandler", () => { CHAIN_IDs.HYPEREVM_TESTNET, provider, simpleTransferFlowCompletedRepository, + swapFlowInitializedRepository, ); }); diff --git a/packages/indexer/src/data-indexing/tests/HyperEVMIndexerManager.integration.test.ts b/packages/indexer/src/data-indexing/tests/HyperEVMIndexerManager.integration.test.ts index 04ec4ad4..230fdd0e 100644 --- a/packages/indexer/src/data-indexing/tests/HyperEVMIndexerManager.integration.test.ts +++ b/packages/indexer/src/data-indexing/tests/HyperEVMIndexerManager.integration.test.ts @@ -12,10 +12,12 @@ import { RedisCache } from "../../redis/redisCache"; import { parseProvidersUrls, Config } from "../../parseEnv"; import { getTestDataSource } from "../../tests/setup"; import { SimpleTransferFlowCompletedRepository } from "../../database/SimpleTransferFlowCompletedRepository"; +import { SwapFlowInitializedRepository } from "../../database/SwapFlowInitializedRepository"; describe("HyperEVMIndexerManager", () => { let dataSource: DataSource; let simpleTransferFlowCompletedRepository: SimpleTransferFlowCompletedRepository; + let swapFlowInitializedRepository: SwapFlowInitializedRepository; let logger: Logger; let manager: HyperEVMIndexerManager; let retryProvidersFactory: RetryProvidersFactory; @@ -37,6 +39,10 @@ describe("HyperEVMIndexerManager", () => { simpleTransferFlowCompletedRepository = new SimpleTransferFlowCompletedRepository(dataSource, logger); + swapFlowInitializedRepository = new SwapFlowInitializedRepository( + dataSource, + logger, + ); const providerUrls = parseProvidersUrls(); const config = { @@ -61,6 +67,7 @@ describe("HyperEVMIndexerManager", () => { dataSource, retryProvidersFactory, simpleTransferFlowCompletedRepository, + swapFlowInitializedRepository, true, ); }); diff --git a/packages/indexer/src/database/SwapFlowInitializedRepository.ts b/packages/indexer/src/database/SwapFlowInitializedRepository.ts new file mode 100644 index 00000000..358c237b --- /dev/null +++ b/packages/indexer/src/database/SwapFlowInitializedRepository.ts @@ -0,0 +1,77 @@ +import winston from "winston"; +import * as across from "@across-protocol/sdk"; +import { DataSource, entities, utils as dbUtils } from "@repo/indexer-database"; +import { SwapFlowInitializedWithBlock } from "../data-indexing/adapter/hyper-evm/model"; + +export class SwapFlowInitializedRepository extends dbUtils.BlockchainEventRepository { + constructor( + postgres: DataSource, + logger: winston.Logger, + private chunkSize = 100, + ) { + super(postgres, logger); + } + + public async deleteUnfinalisedSwapFlowInitializedEvents( + chainId: number, + lastFinalisedBlock: number, + ) { + const chainIdColumn = "chainId"; + const [swapFlowInitializedEvents] = await Promise.all([ + this.deleteUnfinalisedEvents( + chainId, + chainIdColumn, + lastFinalisedBlock, + entities.SwapFlowInitialized, + ), + ]); + + return { + swapFlowInitializedEvents, + }; + } + + public async formatAndSaveSwapFlowInitializedEvents( + swapFlowInitializedEvents: SwapFlowInitializedWithBlock[], + lastFinalisedBlock: number, + chainId: number, + blockDates: Record, + ) { + const formattedEvents: Partial[] = + swapFlowInitializedEvents.map((event) => { + return { + blockNumber: event.blockNumber, + logIndex: event.logIndex, + transactionHash: event.transactionHash, + transactionIndex: event.transactionIndex, + + blockTimestamp: blockDates[event.blockNumber]!, + chainId: chainId.toString(), + + quoteNonce: event.quoteNonce, + finalRecipient: event.finalRecipient, + finalToken: event.finalToken, + evmAmountIn: event.evmAmountIn, + bridgingFeesIncurred: event.bridgingFeesIncurred, + coreAmountIn: event.coreAmountIn, + minAmountToSend: event.minAmountToSend, + maxAmountToSend: event.maxAmountToSend, + finalised: event.blockNumber <= lastFinalisedBlock, + }; + }); + + const chunkedEvents = across.utils.chunk(formattedEvents, this.chunkSize); + const savedEvents = await Promise.all( + chunkedEvents.map((eventsChunk) => + this.saveAndHandleFinalisationBatch( + entities.SwapFlowInitialized, + eventsChunk, + ["chainId", "blockNumber", "transactionHash", "logIndex"], + [], + ), + ), + ); + const result = savedEvents.flat(); + return result; + } +} diff --git a/packages/indexer/src/main.ts b/packages/indexer/src/main.ts index f3dc73ca..f8abb65d 100644 --- a/packages/indexer/src/main.ts +++ b/packages/indexer/src/main.ts @@ -34,6 +34,7 @@ import { HyperEVMIndexerManager } from "./data-indexing/service/HyperEVMIndexerM import { CCTPRepository } from "./database/CctpRepository"; import { OftRepository } from "./database/OftRepository"; import { SimpleTransferFlowCompletedRepository } from "./database/SimpleTransferFlowCompletedRepository"; +import { SwapFlowInitializedRepository } from "./database/SwapFlowInitializedRepository"; async function initializeRedis( config: parseEnv.RedisConfig, @@ -134,6 +135,7 @@ export async function Main(config: parseEnv.Config, logger: winston.Logger) { postgres, retryProvidersFactory, new SimpleTransferFlowCompletedRepository(postgres, logger), + new SwapFlowInitializedRepository(postgres, logger), ); const bundleServicesManager = new BundleServicesManager( config,