diff --git a/tee-apps/intent-pauser/README.md b/tee-apps/intent-pauser/README.md index 631493c4..2c3c490b 100644 --- a/tee-apps/intent-pauser/README.md +++ b/tee-apps/intent-pauser/README.md @@ -11,8 +11,9 @@ bun install To configure: ```bash -cp .env.example .env +cp .env.template .env ``` + ... and fill in the values in `.env`. To run: diff --git a/tee-apps/sealed-bid-auction/.env.template b/tee-apps/sealed-bid-auction/.env.template index 959232f9..50103748 100644 --- a/tee-apps/sealed-bid-auction/.env.template +++ b/tee-apps/sealed-bid-auction/.env.template @@ -7,6 +7,11 @@ SOLVER_PRICE_TTL_SECONDS=600 NEXT_NONCE_URL= IS_MAINNET=false +# optional from blocks for historical event logging +# these are useful if we miss events and want to rewind +INTENT_OBSERVER_FROM_BLOCK_BASE= +INTENT_OBSERVER_FROM_BLOCK_ARBITRUM= + #Arbitrum ARBITRUM_WS= ARBITRUM_T1_ERC7683_CONTRACT_ADDRESS= diff --git a/tee-apps/sealed-bid-auction/README.md b/tee-apps/sealed-bid-auction/README.md index 68b89535..406d31ce 100644 --- a/tee-apps/sealed-bid-auction/README.md +++ b/tee-apps/sealed-bid-auction/README.md @@ -8,6 +8,13 @@ bun install ``` Save TLS key in `key.pem` and certificate in `cert.pem` . Consider using CloudFlare Origin Server Certificate (of course if you use CloudFlare) +To configure: + +```bash +cp .env.template .env +``` +... and fill in the values in `.env`. + ### To run: Start server: diff --git a/tee-apps/sealed-bid-auction/script/server.ts b/tee-apps/sealed-bid-auction/script/server.ts index 33c63d66..c6a45335 100644 --- a/tee-apps/sealed-bid-auction/script/server.ts +++ b/tee-apps/sealed-bid-auction/script/server.ts @@ -33,13 +33,22 @@ const baseClient = new BlockchainClient( IS_MAINNET ? base : baseSepolia, process.env.BASE_SIGNER_PRIVATE_KEY as `0x${string}` ); + +const fromBlockEnvArbitrum = process.env.INTENT_OBSERVER_FROM_BLOCK_ARBITRUM; +const fromBlockArbitrum = fromBlockEnvArbitrum ? BigInt(fromBlockEnvArbitrum) : null; +const fromBlockEnvBase = process.env.INTENT_OBSERVER_FROM_BLOCK_BASE; +const fromBlockBase = fromBlockEnvBase ? BigInt(fromBlockEnvBase) : null; +const auctionPollingInterval = 500; + const arbitrumSepoliaIntentObserver = new ViemIntentObserver( arbitrumClient, ARBITRUM_T1_ERC_7683_CONTRACT_ADDRESS, auctionService, httpServer, baseClient.publicClient.chain.id, - BASE_T1_ERC_7683_CONTRACT_ADDRESS + BASE_T1_ERC_7683_CONTRACT_ADDRESS, + auctionPollingInterval, + fromBlockArbitrum ); const baseSepoliaIntentObserver = new ViemIntentObserver( baseClient, @@ -47,7 +56,9 @@ const baseSepoliaIntentObserver = new ViemIntentObserver( auctionService, httpServer, arbitrumClient.publicClient.chain.id, - ARBITRUM_T1_ERC_7683_CONTRACT_ADDRESS + ARBITRUM_T1_ERC_7683_CONTRACT_ADDRESS, + auctionPollingInterval, + fromBlockBase ); async function main() { diff --git a/tee-apps/sealed-bid-auction/src/api/types.ts b/tee-apps/sealed-bid-auction/src/api/types.ts index b352444a..9b3c06d2 100644 --- a/tee-apps/sealed-bid-auction/src/api/types.ts +++ b/tee-apps/sealed-bid-auction/src/api/types.ts @@ -31,12 +31,25 @@ export interface AuctionQuote { timestamp: number; } +export interface OpenEventLog { + address: string; + topics: string[]; + data: string; + blockNumber: string; + transactionHash: string; + transactionIndex: string; + blockHash: string; + logIndex: string; + removed: boolean; +} + export interface AuctionResult { type: string; orderId: string; settlementReceiverAddress: string; amountOut: bigint; signature: string; + openEvent: OpenEventLog; } export interface AuthAttempt { diff --git a/tee-apps/sealed-bid-auction/src/blockchain/ViemIntentObserver.ts b/tee-apps/sealed-bid-auction/src/blockchain/ViemIntentObserver.ts index 658208b2..cd535d00 100644 --- a/tee-apps/sealed-bid-auction/src/blockchain/ViemIntentObserver.ts +++ b/tee-apps/sealed-bid-auction/src/blockchain/ViemIntentObserver.ts @@ -2,6 +2,7 @@ import { decodeAbiParameters, parseEventLogs, trim, + type Log, type WatchEventOnLogsParameter, } from "viem"; @@ -14,11 +15,13 @@ import { import type { AuctionApiServer } from "../api/AuctionApiServer.ts"; import { serialize, WinstonLogger } from "../utils/WinstonLogger.ts"; import type { BlockchainClient } from "./BlockchainClient.ts"; -import type { AuctionResult } from "../api/types.ts"; +import type { AuctionResult, OpenEventLog } from "../api/types.ts"; + +const HISTORICAL_BLOCK_CHUNK_SIZE = 100n; export class ViemIntentObserver { private logger: WinstonLogger; - + constructor( private readonly sourceChainClient: BlockchainClient, private readonly sourceChainT1Erc7683ContractAddress: `0x${string}`, @@ -26,14 +29,23 @@ export class ViemIntentObserver { private readonly apiServer: AuctionApiServer, private readonly destinationChainId: number, private readonly destinationChainT1Erc7683ContractAddress: `0x${string}`, - private readonly auctionPollingInterval: number = 500 + private readonly auctionPollingInterval: number = 500, + private readonly fromBlock: bigint | null ) { this.logger = new WinstonLogger( `${ViemIntentObserver.name}[${this.sourceChainClient.publicClient.chain.name}]` ); } - public start() { + public async start() { + if (this.fromBlock !== null) { + await this.fetchHistoricalLogs(); + } + + this.startWatching(); + } + + private startWatching() { this.sourceChainClient.publicClient.watchEvent({ address: this.sourceChainT1Erc7683ContractAddress, event: OPEN_INTENT_ABI_EVENT, @@ -46,6 +58,41 @@ export class ViemIntentObserver { ); } + private async fetchHistoricalLogs() { + if (this.fromBlock === null) return; + + const currentBlock = await this.sourceChainClient.publicClient.getBlockNumber(); + this.logger.info( + `Fetching historical logs from block ${this.fromBlock} to ${currentBlock}` + ); + + let fromBlock = this.fromBlock; + + while (fromBlock <= currentBlock) { + const toBlock = fromBlock + HISTORICAL_BLOCK_CHUNK_SIZE - 1n > currentBlock + ? currentBlock + : fromBlock + HISTORICAL_BLOCK_CHUNK_SIZE - 1n; + + this.logger.info(`Fetching logs from block ${fromBlock} to ${toBlock}`); + + const logs = await this.sourceChainClient.publicClient.getLogs({ + address: this.sourceChainT1Erc7683ContractAddress, + event: OPEN_INTENT_ABI_EVENT, + fromBlock, + toBlock, + }); + + if (logs.length > 0) { + this.logger.info(`Found ${logs.length} historical logs in blocks ${fromBlock}-${toBlock}`); + await this.processIntentLogs(logs as unknown as WatchEventOnLogsParameter); + } + + fromBlock = toBlock + 1n; + } + + this.logger.info(`Finished fetching historical logs, caught up to block ${currentBlock}`); + } + private async processIntentLogs(logs: WatchEventOnLogsParameter) { this.logger.info("Intent was Open-ed!"); @@ -54,7 +101,23 @@ export class ViemIntentObserver { logs, }); - for (const order of parsedLogs) { + for (let i = 0; i < parsedLogs.length; i++) { + const order = parsedLogs[i]; + const rawLog = logs[i]; + + // Convert raw log to eth_getLogs hex format for tokka-filler + const openEvent: OpenEventLog = { + address: rawLog.address, + topics: rawLog.topics as string[], + data: rawLog.data, + blockNumber: `0x${rawLog.blockNumber.toString(16)}`, + transactionHash: rawLog.transactionHash, + transactionIndex: `0x${rawLog.transactionIndex.toString(16)}`, + blockHash: rawLog.blockHash, + logIndex: `0x${rawLog.logIndex.toString(16)}`, + removed: rawLog.removed, + }; + for (const fillInstruction of order.args.resolvedOrder.fillInstructions) { const [decodedOrder] = decodeAbiParameters( ORDER_DATA_ABI_PARAMETERS_WRAPPED_IN_TUPLE, @@ -79,7 +142,8 @@ export class ViemIntentObserver { closedAuction: orderData.closedAuction, data: orderData.data, }, - order.args.orderId + order.args.orderId, + openEvent ); } } @@ -87,18 +151,25 @@ export class ViemIntentObserver { private async runAuctionAndNotifySolver( orderData: OrderData, - orderId: `0x${string}` + orderId: `0x${string}`, + openEvent: OpenEventLog ) { this.logger.info(`Running auction for order ${orderId}`); this.logger.debug(`Running auction for orderData ${serialize(orderData)}`); while (Date.now() / 1000 < orderData.fillDeadline) { - const winningPrice = this.auctionService.auction( - orderData.inputToken, - orderData.outputToken, - orderData.amountIn - ); + let winningPrice; + try { + winningPrice = this.auctionService.auction( + orderData.inputToken, + orderData.outputToken, + orderData.amountIn + ); + } catch (e) { + this.logger.error(`Auction error: ${e}`); + break; + } this.logger.debug(`Auction winner: ${serialize(winningPrice)}`); @@ -116,6 +187,7 @@ export class ViemIntentObserver { winningPrice.settlementReceiverAddress as `0x${string}`, winningPrice.amountOut ), + openEvent, }; await this.apiServer.notifySolvers(