{"path":"lib/sns.js","size":4911,"locked":false,"binary":false,"truncated":false,"content":"import { Connection, Keypair, Transaction } from '@solana/web3.js';\nimport {\n  createRecordV2Instruction,\n  updateRecordV2Instruction,\n  getRecordV2,\n  Record\n} from '@bonfida/spl-name-service';\nimport { sendAndConfirm } from './solana-confirm.js';\n\nconst DEFAULT_RPC = 'https://api.mainnet-beta.solana.com';\n\nfunction loadWallet() {\n  const secret = process.env.ANCHR_WALLET_SECRET_KEY;\n  if (!secret) {\n    throw new Error(\n      \"Missing ANCHR_WALLET_SECRET_KEY env var — this must be the wallet that owns the .sol domain.\"\n    );\n  }\n  let secretKey;\n    try {\n    secretKey = Uint8Array.from(JSON.parse(secret));\n  } catch (err) {\n    // Attach the original parse error as the cause so ESLint's\n    // `preserve-caught-error` rule is satisfied and callers can inspect\n    // the original error.\n    throw new Error(\n      'ANCHR_WALLET_SECRET_KEY must be a JSON array of numbers (e.g. the contents of a Solana CLI keypair file).',\n      { cause: err }\n    );\n  }\n  return Keypair.fromSecretKey(secretKey);\n}\n\n/**\n * Writes a CID to a domain's IPFS record using the V2 record format.\n *\n * STATUS: V2 is now the primary path (V1 is abandoned — see CHANGELOG /\n * project history: devnet.bindings.createNameRegistry repeatedly failed\n * with an unexplained second-signer requirement across multiple attempts).\n *\n * VERIFIED signature directly from source, not guessed\n * (node_modules/@bonfida/spl-name-service/dist/esm/bindings/createRecordV2Instruction.d.ts,\n * and confirmed exported at the top level via dist/esm/index.d.ts):\n *   createRecordV2Instruction(domain, record, content, owner, payer) => TransactionInstruction\n *   updateRecordV2Instruction(domain, record, content, owner, payer) => TransactionInstruction\n * Both are synchronous (no Promise wrapper), unlike every V1 function.\n *\n * CONFIRMED WORKING on devnet via a real transaction whose program logs\n * showed both \"Instruction: Create\" (allocates the record account) and\n * \"Instruction: Update Data\" (writes the content) succeeding — not just\n * a successful top-level transaction status. createRecordV2Instruction\n * bundles account creation AND the content write into one instruction.\n *\n * That confirmed run only covered a domain with NO existing record yet.\n * If a record already exists, re-running \"create\" would likely fail\n * (same as any Solana account creation on an already-initialized\n * account) — so this function tries create first and falls back to\n * update on failure. The update fallback path itself has NOT been\n * independently tested end-to-end; if you hit it, verify the result\n * with readIpfsRecord before trusting it blindly.\n */\nexport async function writeIpfsRecord(domain, cid, { rpcUrl = DEFAULT_RPC } = {}) {\n  const connection = new Connection(rpcUrl, 'confirmed');\n  const wallet = loadWallet();\n\n  try {\n    const ix = createRecordV2Instruction(domain, Record.IPFS, cid, wallet.publicKey, wallet.publicKey);\n    const tx = new Transaction().add(ix);\n    const signature = await sendAndConfirm(connection, tx, [wallet]);\n    return { signature, explorerUrl: `https://explorer.solana.com/tx/${signature}` };\n  } catch (createErr) {\n    console.log(`(Record likely already exists for ${domain} — falling back to update: ${createErr.message})`);\n    try {\n      const ix = updateRecordV2Instruction(domain, Record.IPFS, cid, wallet.publicKey, wallet.publicKey);\n      const tx = new Transaction().add(ix);\n      const signature = await sendAndConfirm(connection, tx, [wallet]);\n      return { signature, explorerUrl: `https://explorer.solana.com/tx/${signature}` };\n        } catch (updateErr) {\n      // Attach the caught updateErr as the cause so the original stack/message\n      // are preserved and the ESLint rule is satisfied.\n      throw new Error(\n        `Both create and update attempts failed for ${domain}.\\n` +\n        `Create error: ${createErr?.message ?? String(createErr)}\\n` +\n        `Update error: ${updateErr?.message ?? String(updateErr)}`,\n        { cause: updateErr }\n      );\n    }\n  }\n}\n\n/**\n * Reads back the current IPFS record for a domain (V2 format).\n *\n * Uses the top-level getRecordV2 — this is well-documented on the read\n * side (docs.sns.id/dev/sns-sdk/domain-record-methods/v2-records),\n * unlike the write side which has no public docs at all. A devnet-only\n * SDK gap was found during testing (GetRecordV2Options only has a\n * `deserialize` boolean, no cluster/program override — confirmed by\n * reading the actual .d.ts), but that only affects devnet testing; this\n * targets mainnet by default, where getRecordV2 is the documented,\n * intended way to read V2 records.\n */\nexport async function readIpfsRecord(domain, { rpcUrl = DEFAULT_RPC } = {}) {\n  const connection = new Connection(rpcUrl, 'confirmed');\n  const result = await getRecordV2(connection, domain, Record.IPFS, { deserialize: true });\n  return result?.deserializedContent ?? null;\n}\n"}