From ed94a2946f9659864ddf10307dfd82c15916aef4 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 22 May 2026 16:14:10 +0200 Subject: [PATCH 1/3] feat: verify OutputFilled via routemesh RPC before Polymer proof request (V2-199) Gates `/polymer` proof requests with a pre-flight `eth_getLogs` lookup against routemesh, ensuring the targeted (block, logIndex) actually emitted an OutputFilled event. Mismatches return 400 without calling Polymer; RPC failures log a warning and fall through so infra blips never block the happy path. Co-Authored-By: Claude Opus 4.7 (1M context) --- .env.example | 1 + src/lib/libraries/outputFilledVerify.ts | 35 +++++++++++++++++++++++++ src/routes/polymer/+server.ts | 9 +++++++ 3 files changed, 45 insertions(+) create mode 100644 src/lib/libraries/outputFilledVerify.ts 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/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, { From 2de90bf0a99d7ebac0db2877b8a17140ea4ec5cb Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 22 May 2026 16:26:37 +0200 Subject: [PATCH 2/3] chore: trigger CI Co-Authored-By: Claude Opus 4.7 (1M context) From 8b17525c314bd28cb4332725822b26e208461bd7 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 22 May 2026 16:32:55 +0200 Subject: [PATCH 3/3] ci: forward PRIVATE_ROUTEMESH_API_KEY to build + deploy steps SvelteKit inlines `$env/static/private` at build time, so the new var must be set in the build step env to land in the bundle. Mirrored to the deploy step to match the existing pattern. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/deploy.yml | 4 ++++ 1 file changed, 4 insertions(+) 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 }}