diff --git a/packages/indexer/src/data-indexing/model/abis.ts b/packages/indexer/src/data-indexing/model/abis.ts index bb2c951b..449bdf22 100644 --- a/packages/indexer/src/data-indexing/model/abis.ts +++ b/packages/indexer/src/data-indexing/model/abis.ts @@ -7,3 +7,11 @@ export const CCTP_DEPOSIT_FOR_BURN_ABI = [ ]; export const MESSAGE_SENT_ABI = ["event MessageSent(bytes message)"]; + +/* ================================================================================== + * OFT DOMAIN LOGIC & CONFIGURATION + * * Specific ABIs for the Omni-chain Fungible Token (OFT) protocol. + * ================================================================================== */ +export const OFT_SENT_ABI = [ + "event OFTSent(bytes32 indexed guid, uint32 dstEid, address indexed fromAddress, uint256 amountSentLD, uint256 amountReceivedLD)", +]; diff --git a/packages/indexer/src/data-indexing/model/eventTypes.ts b/packages/indexer/src/data-indexing/model/eventTypes.ts index 22278057..88841f3f 100644 --- a/packages/indexer/src/data-indexing/model/eventTypes.ts +++ b/packages/indexer/src/data-indexing/model/eventTypes.ts @@ -20,3 +20,14 @@ export interface DepositForBurnArgs { export interface MessageSentArgs { message: `0x${string}`; } +/* ================================================================================== + * OFT DOMAIN LOGIC & CONFIGURATION + * * Specific event types for the Omni-chain Fungible Token (OFT) protocol. + * ================================================================================== */ +export interface OftSentArgs { + guid: `0x${string}`; + fromAddress: `0x${string}`; + dstEid: number; + amountSentLD: number; + amountReceivedLD: number; +} diff --git a/packages/indexer/src/data-indexing/service/constants.ts b/packages/indexer/src/data-indexing/service/constants.ts index dce6a902..1f99fa26 100644 --- a/packages/indexer/src/data-indexing/service/constants.ts +++ b/packages/indexer/src/data-indexing/service/constants.ts @@ -132,3 +132,9 @@ export const WHITELISTED_FINALIZERS: Array<`0x${string}`> = [ ]; export const DEPOSIT_FOR_BURN_EVENT_NAME = "DepositForBurn"; export const MESSAGE_SENT_EVENT_NAME = "MessageSent"; + +/* ================================================================================== + * OFT DOMAIN LOGIC & CONFIGURATION + * * Specific implementations for the Omni-chain Fungible Token (OFT) protocol. + * ================================================================================== */ +export const OFTSENT_EVENT_NAME = "OFTSent"; diff --git a/packages/indexer/src/data-indexing/service/indexing.ts b/packages/indexer/src/data-indexing/service/indexing.ts index c0e12823..d41c3d00 100644 --- a/packages/indexer/src/data-indexing/service/indexing.ts +++ b/packages/indexer/src/data-indexing/service/indexing.ts @@ -1,23 +1,34 @@ import { IndexerConfig, startIndexing } from "./genericIndexing"; -import { CHAIN_IDs, MAINNET_CHAIN_IDs } from "@across-protocol/constants"; +import { CHAIN_IDs } from "@across-protocol/constants"; import { IndexerEventPayload } from "./genericEventListening"; import { Entity } from "typeorm"; import { TOKEN_MESSENGER_ADDRESS_MAINNET, DEPOSIT_FOR_BURN_EVENT_NAME, MESSAGE_SENT_EVENT_NAME, + OFTSENT_EVENT_NAME, MESSAGE_TRANSMITTER_ADDRESS_MAINNET, TOKEN_MESSENGER_ADDRESS_TESTNET, MESSAGE_TRANSMITTER_ADDRESS_TESTNET, } from "./constants"; -import { CCTP_DEPOSIT_FOR_BURN_ABI, MESSAGE_SENT_ABI } from "../model/abis"; +import { + CCTP_DEPOSIT_FOR_BURN_ABI, + MESSAGE_SENT_ABI, + OFT_SENT_ABI, +} from "../model/abis"; +import { + storeDepositForBurnEvent, + storeMessageSentEvent, + storeOftSentEvent, +} from "./storing"; import { transformDepositForBurnEvent, transformMessageSentEvent, + transformOftSentEvent, } from "./tranforming"; -import { storeDepositForBurnEvent, storeMessageSentEvent } from "./storing"; import { utils as dbUtils } from "@repo/indexer-database"; import { Logger } from "winston"; +import { getOftChainConfiguration } from "../adapter/oft/service"; /** * Definition of the request object for starting an indexer. @@ -32,7 +43,7 @@ export interface StartIndexerRequest { } /** - * Sets up and starts the indexer for events on Arbitrum Mainnet. + * Sets up and starts the indexer for events on Arbitrum. * * This function demonstrates how the generic components are assembled into a concrete * indexer. To support a new event, one would need to add another event to the events array with its @@ -43,7 +54,7 @@ export async function startArbitrumIndexing(request: StartIndexerRequest) { // Destructure the request object const { repo, rpcUrl, logger, sigterm } = request; // Concrete Configuration - // Define the specific parameters for the Arbitrum Mainnet indexer. + // Define the specific parameters for the Arbitrum indexer. const indexerConfig: IndexerConfig< Partial, dbUtils.BlockchainEventRepository, @@ -81,7 +92,46 @@ export async function startArbitrumIndexing(request: StartIndexerRequest) { // Start the generic indexer subsystem with our concrete configuration and functions. await startIndexing({ db: repo, - indexerConfig, + indexerConfig: indexerConfig, + logger, + sigterm, + }); +} + +/** + * Sets up and starts the indexer for OFT events on hyperEVM. + * @param request The configuration object containing repo, rpcUrl, logger, and shutdown signal. + */ +export async function startHyperEvmIndexing(request: StartIndexerRequest) { + const { repo, rpcUrl, logger, sigterm, testNet } = request; + const chainId = testNet ? CHAIN_IDs.HYPEREVM_TESTNET : CHAIN_IDs.HYPEREVM; + const oftChainConfig = getOftChainConfiguration(chainId); + if (!oftChainConfig) { + throw new Error(`OFT configuration not found for chainId: ${chainId}`); + } + + const indexerConfig: IndexerConfig< + Partial, + dbUtils.BlockchainEventRepository, + IndexerEventPayload + > = { + chainId, + rpcUrl, + events: oftChainConfig.tokens.map((token) => ({ + config: { + address: token.address as `0x${string}`, + abi: OFT_SENT_ABI, + eventName: OFTSENT_EVENT_NAME, + fromBlock: token.startBlockNumber, + }, + transform: transformOftSentEvent, + store: storeOftSentEvent, + })), + }; + + await startIndexing({ + db: repo, + indexerConfig: indexerConfig, logger, sigterm, }); diff --git a/packages/indexer/src/data-indexing/service/storing.ts b/packages/indexer/src/data-indexing/service/storing.ts index 3ca5788a..48bd1e7c 100644 --- a/packages/indexer/src/data-indexing/service/storing.ts +++ b/packages/indexer/src/data-indexing/service/storing.ts @@ -9,6 +9,8 @@ const PK_CHAIN_BLOCK_TX_LOG = [ "logIndex", ]; +const PK_CHAIN_BLOCK_HASH_LOG = ["chainId", "blockHash", "logIndex"]; + /** * Stores a DepositForBurn event in the database. * @@ -45,3 +47,18 @@ export const storeMessageSentEvent: Storer< [], ); }; + +export const storeOftSentEvent: Storer< + Partial, + dbUtils.BlockchainEventRepository +> = async ( + event: Partial, + repository: dbUtils.BlockchainEventRepository, +) => { + return repository.saveAndHandleFinalisationBatch( + entities.OFTSent, + [event], + PK_CHAIN_BLOCK_HASH_LOG as (keyof entities.OFTSent)[], + [], + ); +}; diff --git a/packages/indexer/src/data-indexing/service/tranforming.ts b/packages/indexer/src/data-indexing/service/tranforming.ts index 8d4e88cb..555d6d9a 100644 --- a/packages/indexer/src/data-indexing/service/tranforming.ts +++ b/packages/indexer/src/data-indexing/service/tranforming.ts @@ -8,9 +8,14 @@ import { import { formatFromAddressToChainFormat } from "../../utils"; import { Transformer } from "../model/genericTypes"; import { getFinalisedBlockBufferDistance } from "./constants"; -import { DepositForBurnArgs, MessageSentArgs } from "../model/eventTypes"; +import { + DepositForBurnArgs, + MessageSentArgs, + OftSentArgs, +} from "../model/eventTypes"; import { Logger } from "winston"; import { arrayify } from "ethers/lib/utils"; // New import +import { getOftChainConfiguration } from "../adapter/oft/service"; /** * A generic transformer for addresses. @@ -157,6 +162,27 @@ export const transformMessageSentEvent: Transformer< }; }; +export const transformOftSentEvent: Transformer< + IndexerEventPayload, + Partial +> = (payload, logger: Logger = console as unknown as Logger) => { + const rawArgs = getRawArgs(payload, logger); + const args = rawArgs as unknown as OftSentArgs; + const base = baseTransformer(payload, logger); + const chainId = parseInt(base.chainId); + const fromAddress = transformAddress(args.fromAddress, chainId); + + return { + ...base, + guid: args.guid, + dstEid: args.dstEid, + fromAddress, + amountSentLD: args.amountSentLD.toString(), + amountReceivedLD: args.amountReceivedLD.toString(), + token: getOftChainConfiguration(payload.chainId).tokens[0]!.address, + }; +}; + const getRawArgs = (payload: IndexerEventPayload, logger: Logger) => { const rawArgs = (payload.log as any).args; diff --git a/packages/indexer/src/data-indexing/tests/Indexers.integration.test.ts b/packages/indexer/src/data-indexing/tests/Indexers.integration.test.ts index fa8f08ab..1494c417 100644 --- a/packages/indexer/src/data-indexing/tests/Indexers.integration.test.ts +++ b/packages/indexer/src/data-indexing/tests/Indexers.integration.test.ts @@ -1,7 +1,10 @@ import { expect } from "chai"; import { DataSource } from "typeorm"; import { getTestDataSource } from "../../tests/setup"; -import { startArbitrumIndexing } from "../service/indexing"; +import { + startArbitrumIndexing, + startHyperEvmIndexing, +} from "../service/indexing"; import { MockWebSocketRPCServer } from "../../tests/testProvider"; import { utils as dbUtils } from "@repo/indexer-database"; import { entities } from "@repo/indexer-database"; @@ -12,6 +15,7 @@ import { import sinon from "sinon"; import { Logger } from "winston"; import { CHAIN_IDs } from "@across-protocol/constants"; +import { getOftChainConfiguration } from "../adapter/oft/service"; describe("Indexer Integration (Real Transaction Data)", () => { let dataSource: DataSource; @@ -302,4 +306,78 @@ describe("Indexer Integration (Real Transaction Data)", () => { // Null checks expect(savedEvent!.deletedAt).to.be.null; }).timeout(20000); + + it("should ingest the OFTSent event from hyperEVM tx 0x94d7...cd9", async () => { + // Real transaction taken from: + // https://hyperevmscan.io/tx/0x94d7feace76e29767cbdb1c1ff83430a846e933040de9caaf167290d22315cd9#eventlog#18 + const txHash = + "0x94d7feace76e29767cbdb1c1ff83430a846e933040de9caaf167290d22315cd9"; + const blockNumber = 20670509; + const blockHash = + "0xa2ae33b41e3f1d1896ab84e625da2416f0dc9ee1fcd9c91b975eb118331364e9"; + const blockTimestamp = "0x6564b1f3"; // An arbitrary timestamp + + server.mockBlockResponse({ + number: "0x" + blockNumber.toString(16), + hash: blockHash, + timestamp: blockTimestamp, + transactions: [], + }); + + startHyperEvmIndexing({ + repo: blockchainRepository, + rpcUrl, + logger, + sigterm: abortController.signal, + }); + await server.waitForSubscription(); + + const oftChainConfig = getOftChainConfiguration(CHAIN_IDs.HYPEREVM); + const token = oftChainConfig.tokens.find( + (t) => t.address === "0x904861a24F30EC96ea7CFC3bE9EA4B476d237e98", + )!; + + server.pushEvent({ + address: token.address, + blockNumber: "0x" + blockNumber.toString(16), + transactionHash: txHash, + logIndex: "0x12", // 18 + blockHash, + transactionIndex: "0x1", + topics: [ + "0x85496b760a4b7f8d66384b9df21b381f5d1b1e79f229a47aaf4c232edc2fe59a", + "0xfd74b08cc4da0b6cea03a2731ce3bf9c5fa6912cc6241557c39ce2f2dd20b002", + "0x00000000000000000000000034f9c0b11e67d72ad65c41ff90a6989846f28c22", + ], + data: + "0x" + + "0000000000000000000000000000000000000000000000000000000000007670" + // dstEid: 30320 + "0000000000000000000000000000000000000000000000000000000005f5e100" + // amountSentLD: 100000000 + "0000000000000000000000000000000000000000000000000000000005f5e100", // amountReceivedLD: 100000000 + }); + + await new Promise((r) => setTimeout(r, 500)); + + const oftSentRepo = dataSource.getRepository(entities.OFTSent); + const savedEvent = await oftSentRepo.findOne({ + where: { transactionHash: txHash, logIndex: 18 }, + }); + + expect(savedEvent).to.exist; + + expect(savedEvent).to.deep.include({ + chainId: CHAIN_IDs.HYPEREVM, + blockNumber: blockNumber, + blockHash, + transactionHash: txHash, + logIndex: 18, + finalised: false, + guid: "0xfd74b08cc4da0b6cea03a2731ce3bf9c5fa6912cc6241557c39ce2f2dd20b002", + dstEid: 30320, + fromAddress: "0x34F9C0B11e67d72AD65C41Ff90A6989846f28c22", + amountSentLD: 100000000, + amountReceivedLD: 100000000, + token: token.address, + }); + }).timeout(20000); });