diff --git a/.env.example b/.env.example index e2a3a71..03e92d0 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,4 @@ PUBLIC_WALLET_CONNECT_PROJECT_ID= PRIVATE_POLYMER_MAINNET_ZONE_API_KEY= PRIVATE_POLYMER_TESTNET_ZONE_API_KEY= +PRIVATE_ROUTEMESH_API_KEY= diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5c9a897..8a4ce63 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -34,6 +34,7 @@ jobs: env: PRIVATE_POLYMER_MAINNET_ZONE_API_KEY: ${{ secrets.PRIVATE_POLYMER_MAINNET_ZONE_API_KEY }} PRIVATE_POLYMER_TESTNET_ZONE_API_KEY: ${{ secrets.PRIVATE_POLYMER_TESTNET_ZONE_API_KEY }} + PRIVATE_ROUTEMESH_API_KEY: ${{ secrets.PRIVATE_ROUTEMESH_API_KEY }} PUBLIC_WALLET_CONNECT_PROJECT_ID: ${{ secrets.PUBLIC_WALLET_CONNECT_PROJECT_ID }} run: bun run build @@ -42,6 +43,7 @@ jobs: env: PRIVATE_POLYMER_MAINNET_ZONE_API_KEY: ${{ secrets.PRIVATE_POLYMER_MAINNET_ZONE_API_KEY }} PRIVATE_POLYMER_TESTNET_ZONE_API_KEY: ${{ secrets.PRIVATE_POLYMER_TESTNET_ZONE_API_KEY }} + PRIVATE_ROUTEMESH_API_KEY: ${{ secrets.PRIVATE_ROUTEMESH_API_KEY }} PUBLIC_WALLET_CONNECT_PROJECT_ID: ${{ secrets.PUBLIC_WALLET_CONNECT_PROJECT_ID }} with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} @@ -77,6 +79,7 @@ jobs: env: PRIVATE_POLYMER_MAINNET_ZONE_API_KEY: ${{ secrets.PRIVATE_POLYMER_MAINNET_ZONE_API_KEY }} PRIVATE_POLYMER_TESTNET_ZONE_API_KEY: ${{ secrets.PRIVATE_POLYMER_TESTNET_ZONE_API_KEY }} + PRIVATE_ROUTEMESH_API_KEY: ${{ secrets.PRIVATE_ROUTEMESH_API_KEY }} PUBLIC_WALLET_CONNECT_PROJECT_ID: ${{ secrets.PUBLIC_WALLET_CONNECT_PROJECT_ID }} run: bun run build @@ -86,6 +89,7 @@ jobs: env: PRIVATE_POLYMER_MAINNET_ZONE_API_KEY: ${{ secrets.PRIVATE_POLYMER_MAINNET_ZONE_API_KEY }} PRIVATE_POLYMER_TESTNET_ZONE_API_KEY: ${{ secrets.PRIVATE_POLYMER_TESTNET_ZONE_API_KEY }} + PRIVATE_ROUTEMESH_API_KEY: ${{ secrets.PRIVATE_ROUTEMESH_API_KEY }} PUBLIC_WALLET_CONNECT_PROJECT_ID: ${{ secrets.PUBLIC_WALLET_CONNECT_PROJECT_ID }} with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} diff --git a/src/lib/libraries/outputFilledVerify.ts b/src/lib/libraries/outputFilledVerify.ts new file mode 100644 index 0000000..97a701d --- /dev/null +++ b/src/lib/libraries/outputFilledVerify.ts @@ -0,0 +1,35 @@ +import { createPublicClient, http } from "viem"; +import { PRIVATE_ROUTEMESH_API_KEY } from "$env/static/private"; +import { COIN_FILLER_ABI } from "$lib/abi/outputsettler"; + +const OUTPUT_FILLED_EVENT = COIN_FILLER_ABI.find( + (item) => item.type === "event" && item.name === "OutputFilled" +) as Extract<(typeof COIN_FILLER_ABI)[number], { type: "event"; name: "OutputFilled" }>; + +export type OutputFilledVerification = "match" | "mismatch" | "unknown"; + +export async function verifyOutputFilledLog( + chainId: number, + blockNumber: bigint, + globalLogIndex: number +): Promise { + try { + const client = createPublicClient({ + transport: http(`https://lb.routeme.sh/rpc/${chainId}/${PRIVATE_ROUTEMESH_API_KEY}`) + }); + const logs = await client.getLogs({ + fromBlock: blockNumber, + toBlock: blockNumber, + event: OUTPUT_FILLED_EVENT + }); + return logs.some((l) => Number(l.logIndex) === globalLogIndex) ? "match" : "mismatch"; + } catch (e) { + console.warn("routemesh OutputFilled verification failed", { + chainId, + blockNumber: blockNumber.toString(), + globalLogIndex, + error: e instanceof Error ? e.message : String(e) + }); + return "unknown"; + } +} diff --git a/src/routes/polymer/+server.ts b/src/routes/polymer/+server.ts index c19da15..dbde7b6 100644 --- a/src/routes/polymer/+server.ts +++ b/src/routes/polymer/+server.ts @@ -6,6 +6,7 @@ import { PRIVATE_POLYMER_TESTNET_ZONE_API_KEY } from "$env/static/private"; import { toByteArray } from "base64-js"; +import { verifyOutputFilledLog } from "$lib/libraries/outputFilledVerify"; function getPolymerUrl(mainnet: boolean) { return mainnet @@ -27,6 +28,14 @@ export const POST: RequestHandler = async ({ request }) => { let polymerRequestIndex = polymerIndex; if (!polymerRequestIndex) { + const verification = await verifyOutputFilledLog( + Number(srcChainId), + BigInt(srcBlockNumber), + Number(globalLogIndex) + ); + if (verification === "mismatch") { + return json({ error: "log is not an OutputFilled event" }, { status: 400 }); + } const requestProof = await axios.post( POLYMER_URL, {