{"version":3,"file":"governance-service.mjs","names":[],"sources":["../../../src/services/governance-service.ts"],"sourcesContent":["/**\n * Governance Service — Snapshot (off-chain) + on-chain Governor integration.\n *\n * Snapshot: GraphQL API at hub.snapshot.org/graphql (off-chain votes, gasless).\n * Tally: REST API at api.tally.xyz (on-chain Governor proposals, voting power).\n * Direct Governor contract calls for on-chain voting via viem.\n *\n * No new dependencies — uses guardedFetch for HTTP, viem for contract calls.\n */\n\nimport { type Address, formatUnits } from 'viem';\nimport { guardedFetch } from './endpoint-allowlist.js';\nimport { AERODROME, UNISWAP, AAVE, ENS } from '../lib/contract-registry.js';\n\n// ── Types ────────────────────────────────────────────────────────────────\n\nexport interface SnapshotProposal {\n  id: string;\n  title: string;\n  body: string;\n  state: 'active' | 'closed' | 'pending';\n  choices: string[];\n  scores: number[];\n  scoresTotal: number;\n  start: number;\n  end: number;\n  author: string;\n  space: { id: string; name: string };\n  votes: number;\n  quorum: number;\n  link: string;\n}\n\nexport interface SnapshotVotingPower {\n  space: string;\n  vp: number;\n  vpByStrategy: number[];\n  vpState: string;\n}\n\nexport interface TallyProposal {\n  id: string;\n  title: string;\n  description: string;\n  status: string;\n  governor: { name: string; chainId: number };\n  voteStats: Array<{ support: string; weight: string; percent: number }>;\n  startBlock: number;\n  endBlock: number;\n  proposer: { address: string; name?: string };\n}\n\nexport interface GovernorInfo {\n  address: Address;\n  chainId: number;\n  name: string;\n  tokenAddress?: Address;\n}\n\n// ── Known Governor Contracts ─────────────────────────────────────────────\n\nexport const KNOWN_GOVERNORS: Record<string, GovernorInfo> = {\n  // Base ecosystem governors\n  'aerodrome': {\n    address: AERODROME.governor,\n    chainId: 8453,\n    name: 'Aerodrome Finance',\n    tokenAddress: AERODROME.token,\n  },\n  // Ethereum mainnet governors\n  'uniswap': {\n    address: UNISWAP.governance.governor,\n    chainId: 1,\n    name: 'Uniswap Governor Bravo',\n    tokenAddress: UNISWAP.governance.token,\n  },\n  'aave': {\n    address: AAVE.governance.governor,\n    chainId: 1,\n    name: 'Aave Governance V2',\n    tokenAddress: AAVE.governance.token,\n  },\n  'ens': {\n    address: ENS.governor,\n    chainId: 1,\n    name: 'ENS Governor',\n    tokenAddress: ENS.token,\n  },\n};\n\n// ── Minimal Governor ABI (OpenZeppelin Governor) ─────────────────────────\n\nexport const GOVERNOR_ABI = [\n  {\n    name: 'castVote',\n    type: 'function',\n    stateMutability: 'nonpayable',\n    inputs: [\n      { name: 'proposalId', type: 'uint256' },\n      { name: 'support', type: 'uint8' },\n    ],\n    outputs: [{ name: '', type: 'uint256' }],\n  },\n  {\n    name: 'castVoteWithReason',\n    type: 'function',\n    stateMutability: 'nonpayable',\n    inputs: [\n      { name: 'proposalId', type: 'uint256' },\n      { name: 'support', type: 'uint8' },\n      { name: 'reason', type: 'string' },\n    ],\n    outputs: [{ name: '', type: 'uint256' }],\n  },\n  {\n    name: 'getVotes',\n    type: 'function',\n    stateMutability: 'view',\n    inputs: [\n      { name: 'account', type: 'address' },\n      { name: 'timepoint', type: 'uint256' },\n    ],\n    outputs: [{ name: '', type: 'uint256' }],\n  },\n  {\n    name: 'state',\n    type: 'function',\n    stateMutability: 'view',\n    inputs: [{ name: 'proposalId', type: 'uint256' }],\n    outputs: [{ name: '', type: 'uint8' }],\n  },\n  {\n    name: 'proposalDeadline',\n    type: 'function',\n    stateMutability: 'view',\n    inputs: [{ name: 'proposalId', type: 'uint256' }],\n    outputs: [{ name: '', type: 'uint256' }],\n  },\n] as const;\n\nconst ERC20_VOTES_ABI = [\n  {\n    name: 'delegates',\n    type: 'function',\n    stateMutability: 'view',\n    inputs: [{ name: 'account', type: 'address' }],\n    outputs: [{ name: '', type: 'address' }],\n  },\n  {\n    name: 'delegate',\n    type: 'function',\n    stateMutability: 'nonpayable',\n    inputs: [{ name: 'delegatee', type: 'address' }],\n    outputs: [],\n  },\n  {\n    name: 'getVotes',\n    type: 'function',\n    stateMutability: 'view',\n    inputs: [{ name: 'account', type: 'address' }],\n    outputs: [{ name: '', type: 'uint256' }],\n  },\n  {\n    name: 'balanceOf',\n    type: 'function',\n    stateMutability: 'view',\n    inputs: [{ name: 'account', type: 'address' }],\n    outputs: [{ name: '', type: 'uint256' }],\n  },\n] as const;\n\n// ── Proposal State Enum (OpenZeppelin) ───────────────────────────────────\n\nconst PROPOSAL_STATES = [\n  'Pending', 'Active', 'Canceled', 'Defeated',\n  'Succeeded', 'Queued', 'Expired', 'Executed',\n] as const;\n\n// ── Service ──────────────────────────────────────────────────────────────\n\nexport class GovernanceService {\n  // ── Snapshot (Off-chain) ───────────────────────────────────────────\n\n  /**\n   * Fetch proposals from a Snapshot space.\n   */\n  async getSnapshotProposals(\n    space: string,\n    state?: 'active' | 'closed' | 'all',\n    limit = 10,\n  ): Promise<SnapshotProposal[]> {\n    const stateFilter = state === 'all' ? '' : `state: \"${state ?? 'active'}\"`;\n    const query = `{\n      proposals(\n        first: ${limit},\n        skip: 0,\n        where: { space: \"${space}\", ${stateFilter} },\n        orderBy: \"created\",\n        orderDirection: desc\n      ) {\n        id title body state choices scores scores_total\n        start end author votes quorum\n        space { id name }\n      }\n    }`;\n\n    const response = await guardedFetch('https://hub.snapshot.org/graphql', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify({ query }),\n      signal: AbortSignal.timeout(15_000),\n    });\n\n    if (!response.ok) {\n      throw new Error(`Snapshot API error: ${response.status}`);\n    }\n\n    const data: any = await response.json();\n    const proposals: any[] = data?.data?.proposals ?? [];\n\n    return proposals.map((p: any) => ({\n      id: p.id,\n      title: p.title,\n      body: (p.body ?? '').slice(0, 500),\n      state: p.state,\n      choices: p.choices ?? [],\n      scores: p.scores ?? [],\n      scoresTotal: p.scores_total ?? 0,\n      start: p.start,\n      end: p.end,\n      author: p.author,\n      space: p.space ?? { id: space, name: space },\n      votes: p.votes ?? 0,\n      quorum: p.quorum ?? 0,\n      link: `https://snapshot.org/#/${space}/proposal/${p.id}`,\n    }));\n  }\n\n  /**\n   * Get voting power on a Snapshot space.\n   */\n  async getSnapshotVotingPower(\n    space: string,\n    voter: string,\n  ): Promise<SnapshotVotingPower> {\n    const query = `{\n      vp(voter: \"${voter}\", space: \"${space}\") {\n        vp vp_by_strategy vp_state\n      }\n    }`;\n\n    const response = await guardedFetch('https://hub.snapshot.org/graphql', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify({ query }),\n      signal: AbortSignal.timeout(10_000),\n    });\n\n    if (!response.ok) {\n      throw new Error(`Snapshot VP query failed: ${response.status}`);\n    }\n\n    const data: any = await response.json();\n    const vp = data?.data?.vp;\n\n    return {\n      space,\n      vp: vp?.vp ?? 0,\n      vpByStrategy: vp?.vp_by_strategy ?? [],\n      vpState: vp?.vp_state ?? 'unknown',\n    };\n  }\n\n  // ── On-chain Governor ──────────────────────────────────────────────\n\n  /**\n   * Resolve a governor name or address.\n   */\n  resolveGovernor(input: string): GovernorInfo | null {\n    const lower = input.toLowerCase();\n    if (KNOWN_GOVERNORS[lower]) return KNOWN_GOVERNORS[lower]!;\n\n    // Try by address\n    for (const gov of Object.values(KNOWN_GOVERNORS)) {\n      if (gov.address.toLowerCase() === lower) return gov;\n    }\n    return null;\n  }\n\n  /**\n   * Get all known governors.\n   */\n  getKnownGovernors() {\n    return Object.entries(KNOWN_GOVERNORS).map(([id, g]) => ({\n      id,\n      name: g.name,\n      address: g.address,\n      chainId: g.chainId,\n    }));\n  }\n\n  /**\n   * Cast an on-chain vote.\n   * Support: 0 = Against, 1 = For, 2 = Abstain\n   */\n  async castVote(\n    governorAddress: Address,\n    proposalId: bigint,\n    support: number,\n    reason: string | undefined,\n    walletClient: any,\n    publicClient: any,\n  ): Promise<{ hash: string }> {\n    let hash: string;\n\n    if (reason) {\n      hash = await walletClient.writeContract({\n        address: governorAddress,\n        abi: GOVERNOR_ABI,\n        functionName: 'castVoteWithReason',\n        args: [proposalId, support, reason],\n      });\n    } else {\n      hash = await walletClient.writeContract({\n        address: governorAddress,\n        abi: GOVERNOR_ABI,\n        functionName: 'castVote',\n        args: [proposalId, support],\n      });\n    }\n\n    await publicClient.waitForTransactionReceipt({ hash });\n    return { hash };\n  }\n\n  /**\n   * Delegate voting power on a governance token.\n   */\n  async delegate(\n    tokenAddress: Address,\n    delegatee: Address,\n    walletClient: any,\n    publicClient: any,\n  ): Promise<{ hash: string }> {\n    const hash = await walletClient.writeContract({\n      address: tokenAddress,\n      abi: ERC20_VOTES_ABI,\n      functionName: 'delegate',\n      args: [delegatee],\n    });\n\n    await publicClient.waitForTransactionReceipt({ hash });\n    return { hash };\n  }\n\n  /**\n   * Get on-chain voting power for an address on a governance token.\n   */\n  async getOnchainVotingPower(\n    tokenAddress: Address,\n    userAddress: Address,\n    publicClient: any,\n  ): Promise<{ votes: string; balance: string; delegate: string }> {\n    const [votes, balance, delegateTo] = await Promise.all([\n      publicClient.readContract({\n        address: tokenAddress,\n        abi: ERC20_VOTES_ABI,\n        functionName: 'getVotes',\n        args: [userAddress],\n      }) as Promise<bigint>,\n      publicClient.readContract({\n        address: tokenAddress,\n        abi: ERC20_VOTES_ABI,\n        functionName: 'balanceOf',\n        args: [userAddress],\n      }) as Promise<bigint>,\n      publicClient.readContract({\n        address: tokenAddress,\n        abi: ERC20_VOTES_ABI,\n        functionName: 'delegates',\n        args: [userAddress],\n      }) as Promise<string>,\n    ]);\n\n    return {\n      votes: formatUnits(votes, 18),\n      balance: formatUnits(balance, 18),\n      delegate: delegateTo,\n    };\n  }\n\n  /**\n   * Get proposal state from an on-chain Governor.\n   */\n  async getProposalState(\n    governorAddress: Address,\n    proposalId: bigint,\n    publicClient: any,\n  ): Promise<string> {\n    const stateIndex = await publicClient.readContract({\n      address: governorAddress,\n      abi: GOVERNOR_ABI,\n      functionName: 'state',\n      args: [proposalId],\n    }) as number;\n\n    return PROPOSAL_STATES[stateIndex] ?? `Unknown (${stateIndex})`;\n  }\n}\n\n// ── Singleton ────────────────────────────────────────────────────────────\n\nlet _instance: GovernanceService | null = null;\n\nexport function getGovernanceService(): GovernanceService {\n  if (!_instance) {\n    _instance = new GovernanceService();\n  }\n  return _instance;\n}\n\nexport function resetGovernanceService(): void {\n  _instance = null;\n}\n"],"mappings":"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"}