{"version":3,"sources":["../../src/mpp/verify-erc20.ts"],"sourcesContent":["/**\n * Shared ERC-20 Transfer verification utility.\n *\n * Used by both evm-server.ts (direct payments) and bridge-server.ts\n * (bridged payments on the target chain).\n */\n\nconst TRANSFER_EVENT_TOPIC = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'\n\nexport interface VerifyErc20TransferOptions {\n  /** Transaction hash (0x-prefixed) */\n  txHash: string\n  /** RPC URL for the chain */\n  rpcUrl: string\n  /** ERC-20 token contract address */\n  tokenAddress: string\n  /** Expected recipient address */\n  recipient: string\n  /** Minimum expected amount in base units */\n  expectedAmount: bigint\n}\n\n/**\n * Verify that an on-chain transaction contains a valid ERC-20 Transfer event\n * to the expected recipient for at least the expected amount.\n *\n * @throws if the transaction is missing, failed, or doesn't match.\n */\nexport async function verifyErc20Transfer(opts: VerifyErc20TransferOptions): Promise<void> {\n  const { txHash, rpcUrl, tokenAddress, recipient, expectedAmount } = opts\n\n  // Poll for receipt — the client sends the txHash right after broadcast,\n  // so the receipt may not be available yet.\n  // First check is immediate, then poll at 200ms intervals (aligned with Flashblocks cadence).\n  let receipt: any = null\n  for (let attempt = 0; attempt < 50; attempt++) {\n    if (attempt > 0) await new Promise((r) => setTimeout(r, 200))\n    const receiptRes = await fetch(rpcUrl, {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify({\n        jsonrpc: '2.0', id: 1,\n        method: 'eth_getTransactionReceipt',\n        params: [txHash],\n      }),\n    })\n    const receiptData = await receiptRes.json() as { result?: any; error?: { message: string } }\n\n    if (receiptData.error) {\n      throw new Error(`RPC error: ${receiptData.error.message}`)\n    }\n\n    receipt = receiptData.result\n    if (receipt) break\n  }\n\n  if (!receipt) {\n    throw new Error('Transaction not found or not yet confirmed')\n  }\n  if (receipt.status !== '0x1') {\n    throw new Error('Transaction failed on-chain')\n  }\n\n  // Verify ERC-20 Transfer event\n  const recipientPadded = '0x' + recipient.slice(2).toLowerCase().padStart(64, '0')\n  const tokenLower = tokenAddress.toLowerCase()\n\n  const matchingLog = (receipt.logs as any[]).find((log: any) => {\n    if (log.address.toLowerCase() !== tokenLower) return false\n    if (log.topics[0] !== TRANSFER_EVENT_TOPIC) return false\n    if (log.topics[2]?.toLowerCase() !== recipientPadded) return false\n    return BigInt(log.data) >= expectedAmount\n  })\n\n  if (!matchingLog) {\n    throw new Error('No matching ERC-20 Transfer found for recipient and amount')\n  }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,IAAM,uBAAuB;AAqB7B,eAAsB,oBAAoB,MAAiD;AACzF,QAAM,EAAE,QAAQ,QAAQ,cAAc,WAAW,eAAe,IAAI;AAKpE,MAAI,UAAe;AACnB,WAAS,UAAU,GAAG,UAAU,IAAI,WAAW;AAC7C,QAAI,UAAU,EAAG,OAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAC5D,UAAM,aAAa,MAAM,MAAM,QAAQ;AAAA,MACrC,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU;AAAA,QACnB,SAAS;AAAA,QAAO,IAAI;AAAA,QACpB,QAAQ;AAAA,QACR,QAAQ,CAAC,MAAM;AAAA,MACjB,CAAC;AAAA,IACH,CAAC;AACD,UAAM,cAAc,MAAM,WAAW,KAAK;AAE1C,QAAI,YAAY,OAAO;AACrB,YAAM,IAAI,MAAM,cAAc,YAAY,MAAM,OAAO,EAAE;AAAA,IAC3D;AAEA,cAAU,YAAY;AACtB,QAAI,QAAS;AAAA,EACf;AAEA,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,4CAA4C;AAAA,EAC9D;AACA,MAAI,QAAQ,WAAW,OAAO;AAC5B,UAAM,IAAI,MAAM,6BAA6B;AAAA,EAC/C;AAGA,QAAM,kBAAkB,OAAO,UAAU,MAAM,CAAC,EAAE,YAAY,EAAE,SAAS,IAAI,GAAG;AAChF,QAAM,aAAa,aAAa,YAAY;AAE5C,QAAM,cAAe,QAAQ,KAAe,KAAK,CAAC,QAAa;AAC7D,QAAI,IAAI,QAAQ,YAAY,MAAM,WAAY,QAAO;AACrD,QAAI,IAAI,OAAO,CAAC,MAAM,qBAAsB,QAAO;AACnD,QAAI,IAAI,OAAO,CAAC,GAAG,YAAY,MAAM,gBAAiB,QAAO;AAC7D,WAAO,OAAO,IAAI,IAAI,KAAK;AAAA,EAC7B,CAAC;AAED,MAAI,CAAC,aAAa;AAChB,UAAM,IAAI,MAAM,4DAA4D;AAAA,EAC9E;AACF;","names":[]}