{"version":3,"sources":["../../src/mpp/verify-spl.ts"],"sourcesContent":["/**\n * Shared SPL Token Transfer verification utility for Solana.\n *\n * Used by bridge-server.ts when the target chain is Solana.\n * Verifies that a transaction contains an SPL token transfer\n * to the expected recipient for at least the expected amount.\n */\n\nexport interface VerifySplTransferOptions {\n  /** Transaction signature (base58) */\n  txSignature: string\n  /** Solana RPC URL */\n  rpcUrl: string\n  /** SPL token mint address */\n  tokenMint: string\n  /** Expected recipient wallet address */\n  recipient: string\n  /** Minimum expected amount in base units */\n  expectedAmount: bigint\n}\n\n/**\n * Verify that a Solana transaction contains a valid SPL token transfer\n * to the expected recipient for at least the expected amount.\n *\n * Uses raw JSON-RPC to avoid hard dependency on @solana/web3.js at runtime.\n *\n * @throws if the transaction is missing, failed, or doesn't match.\n */\nexport async function verifySplTransfer(opts: VerifySplTransferOptions): Promise<void> {\n  const { txSignature, rpcUrl, tokenMint, recipient, expectedAmount } = opts\n\n  // Fetch parsed transaction (includes token transfer details)\n  const res = await fetch(rpcUrl, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify({\n      jsonrpc: '2.0',\n      id: 1,\n      method: 'getTransaction',\n      params: [\n        txSignature,\n        { encoding: 'jsonParsed', commitment: 'confirmed', maxSupportedTransactionVersion: 0 },\n      ],\n    }),\n  })\n\n  const data = (await res.json()) as { result?: any; error?: { message: string } }\n\n  if (data.error) {\n    throw new Error(`Solana RPC error: ${data.error.message}`)\n  }\n\n  const tx = data.result\n  if (!tx) {\n    throw new Error('Solana transaction not found or not yet confirmed')\n  }\n\n  if (tx.meta?.err) {\n    throw new Error(`Solana transaction failed: ${JSON.stringify(tx.meta.err)}`)\n  }\n\n  // Look for matching SPL token transfer in inner + outer instructions\n  const allInstructions = [\n    ...(tx.transaction?.message?.instructions || []),\n    ...(tx.meta?.innerInstructions?.flatMap((inner: any) => inner.instructions) || []),\n  ]\n\n  const mintLower = tokenMint.toLowerCase()\n  const recipientLower = recipient.toLowerCase()\n\n  const matched = allInstructions.some((ix: any) => {\n    const parsed = ix.parsed\n    if (!parsed) return false\n\n    // SPL Token transfer / transferChecked\n    const type = parsed.type\n    if (type !== 'transfer' && type !== 'transferChecked') return false\n\n    const info = parsed.info\n    if (!info) return false\n\n    // Check mint (transferChecked has .mint, transfer we check via token balances)\n    if (type === 'transferChecked') {\n      if (info.mint?.toLowerCase() !== mintLower) return false\n    }\n\n    // Check amount\n    const amount = type === 'transferChecked'\n      ? BigInt(info.tokenAmount?.amount || '0')\n      : BigInt(info.amount || '0')\n\n    if (amount < expectedAmount) return false\n\n    // Check destination — for SPL, 'destination' is an ATA, not the wallet directly.\n    // We need to check postTokenBalances to see if recipient's wallet received tokens.\n    return true\n  })\n\n  // Also verify via postTokenBalances that recipient received tokens\n  const postBalances: any[] = tx.meta?.postTokenBalances || []\n  const preBalances: any[] = tx.meta?.preTokenBalances || []\n\n  const recipientPostBalance = postBalances.find(\n    (b: any) =>\n      b.mint?.toLowerCase() === mintLower &&\n      b.owner?.toLowerCase() === recipientLower,\n  )\n\n  if (!recipientPostBalance) {\n    throw new Error('Recipient not found in post-token balances for the expected mint')\n  }\n\n  const recipientPreBalance = preBalances.find(\n    (b: any) =>\n      b.mint?.toLowerCase() === mintLower &&\n      b.owner?.toLowerCase() === recipientLower,\n  )\n\n  const postAmount = BigInt(recipientPostBalance.uiTokenAmount?.amount || '0')\n  const preAmount = BigInt(recipientPreBalance?.uiTokenAmount?.amount || '0')\n  const received = postAmount - preAmount\n\n  if (received < expectedAmount) {\n    throw new Error(\n      `Insufficient SPL transfer: expected ${expectedAmount}, recipient received ${received}`,\n    )\n  }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA6BA,eAAsB,kBAAkB,MAA+C;AACrF,QAAM,EAAE,aAAa,QAAQ,WAAW,WAAW,eAAe,IAAI;AAGtE,QAAM,MAAM,MAAM,MAAM,QAAQ;AAAA,IAC9B,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM,KAAK,UAAU;AAAA,MACnB,SAAS;AAAA,MACT,IAAI;AAAA,MACJ,QAAQ;AAAA,MACR,QAAQ;AAAA,QACN;AAAA,QACA,EAAE,UAAU,cAAc,YAAY,aAAa,gCAAgC,EAAE;AAAA,MACvF;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AAED,QAAM,OAAQ,MAAM,IAAI,KAAK;AAE7B,MAAI,KAAK,OAAO;AACd,UAAM,IAAI,MAAM,qBAAqB,KAAK,MAAM,OAAO,EAAE;AAAA,EAC3D;AAEA,QAAM,KAAK,KAAK;AAChB,MAAI,CAAC,IAAI;AACP,UAAM,IAAI,MAAM,mDAAmD;AAAA,EACrE;AAEA,MAAI,GAAG,MAAM,KAAK;AAChB,UAAM,IAAI,MAAM,8BAA8B,KAAK,UAAU,GAAG,KAAK,GAAG,CAAC,EAAE;AAAA,EAC7E;AAGA,QAAM,kBAAkB;AAAA,IACtB,GAAI,GAAG,aAAa,SAAS,gBAAgB,CAAC;AAAA,IAC9C,GAAI,GAAG,MAAM,mBAAmB,QAAQ,CAAC,UAAe,MAAM,YAAY,KAAK,CAAC;AAAA,EAClF;AAEA,QAAM,YAAY,UAAU,YAAY;AACxC,QAAM,iBAAiB,UAAU,YAAY;AAE7C,QAAM,UAAU,gBAAgB,KAAK,CAAC,OAAY;AAChD,UAAM,SAAS,GAAG;AAClB,QAAI,CAAC,OAAQ,QAAO;AAGpB,UAAM,OAAO,OAAO;AACpB,QAAI,SAAS,cAAc,SAAS,kBAAmB,QAAO;AAE9D,UAAM,OAAO,OAAO;AACpB,QAAI,CAAC,KAAM,QAAO;AAGlB,QAAI,SAAS,mBAAmB;AAC9B,UAAI,KAAK,MAAM,YAAY,MAAM,UAAW,QAAO;AAAA,IACrD;AAGA,UAAM,SAAS,SAAS,oBACpB,OAAO,KAAK,aAAa,UAAU,GAAG,IACtC,OAAO,KAAK,UAAU,GAAG;AAE7B,QAAI,SAAS,eAAgB,QAAO;AAIpC,WAAO;AAAA,EACT,CAAC;AAGD,QAAM,eAAsB,GAAG,MAAM,qBAAqB,CAAC;AAC3D,QAAM,cAAqB,GAAG,MAAM,oBAAoB,CAAC;AAEzD,QAAM,uBAAuB,aAAa;AAAA,IACxC,CAAC,MACC,EAAE,MAAM,YAAY,MAAM,aAC1B,EAAE,OAAO,YAAY,MAAM;AAAA,EAC/B;AAEA,MAAI,CAAC,sBAAsB;AACzB,UAAM,IAAI,MAAM,kEAAkE;AAAA,EACpF;AAEA,QAAM,sBAAsB,YAAY;AAAA,IACtC,CAAC,MACC,EAAE,MAAM,YAAY,MAAM,aAC1B,EAAE,OAAO,YAAY,MAAM;AAAA,EAC/B;AAEA,QAAM,aAAa,OAAO,qBAAqB,eAAe,UAAU,GAAG;AAC3E,QAAM,YAAY,OAAO,qBAAqB,eAAe,UAAU,GAAG;AAC1E,QAAM,WAAW,aAAa;AAE9B,MAAI,WAAW,gBAAgB;AAC7B,UAAM,IAAI;AAAA,MACR,uCAAuC,cAAc,wBAAwB,QAAQ;AAAA,IACvF;AAAA,EACF;AACF;","names":[]}