{"version":3,"file":"defi-balance.mjs","names":[],"sources":["../../../src/tools/defi-balance.ts"],"sourcesContent":["/**\n * DeFi Balance Tool — wallet balance and token holdings\n *\n * Uses RpcManager for fault-tolerant multi-chain RPC access and\n * PriceOracle for cross-validated ETH price.\n */\n\nimport { Type } from '@sinclair/typebox';\nimport { stringEnum, jsonResult, errorResult, readStringParam } from '../lib/tool-helpers.js';\nimport { getWalletState, getPublicClient, isBankrMode } from '../services/walletconnect-service.js';\nimport { getRpcManager } from '../services/rpc-provider.js';\nimport { getPriceOracle } from '../services/price-oracle.js';\nimport { resolveAddressOrEns, isEnsName } from '../lib/ens-resolver.js';\n\nconst ACTIONS = ['overview', 'tokens', 'eth'] as const;\n\nconst DefiBalanceSchema = Type.Object({\n  action: stringEnum(ACTIONS, {\n    description: 'overview: full portfolio summary. tokens: ERC-20 token list. eth: just ETH balance.',\n  }),\n  address: Type.Optional(Type.String({\n    description: 'Wallet address (0x...) or ENS name (e.g. vitalik.eth). Defaults to connected wallet.',\n  })),\n  chain: Type.Optional(Type.String({\n    description: 'Chain to check (default: \"base\"). Options: base, ethereum, arbitrum, optimism, polygon. Bankr mode adds: solana, unichain',\n  })),\n});\n\nexport function createDefiBalanceTool() {\n  return {\n    name: 'defi_balance',\n    label: 'DeFi Balance',\n    ownerOnly: false,\n    description:\n      'Check wallet balances — ETH, ERC-20 tokens, and total portfolio value. ' +\n      'Defaults to the connected wallet on Base. ' +\n      'Supports Base, Ethereum, Arbitrum, Optimism, Polygon via multi-RPC failover. ' +\n      'In Bankr mode, also supports Solana and Unichain with full token breakdowns.',\n    parameters: DefiBalanceSchema,\n    execute: async (_toolCallId: string, args: unknown) => {\n      const params = args as Record<string, unknown>;\n      const action = readStringParam(params, 'action', { required: true })!;\n      const chain = readStringParam(params, 'chain') || 'base';\n\n      // Resolve address (supports ENS names)\n      let address: string;\n      const addressInput = readStringParam(params, 'address');\n      if (!addressInput) {\n        const state = getWalletState();\n        if (!state.connected || !state.address) {\n          return errorResult(\n            'No wallet connected and no address provided. ' +\n            'Connect a wallet first or pass an address parameter.'\n          );\n        }\n        address = state.address;\n      } else if (isEnsName(addressInput)) {\n        try {\n          const publicClient = getPublicClient();\n          if (!publicClient) {\n            return errorResult('Public client not available for ENS resolution.');\n          }\n          const resolved = await resolveAddressOrEns(addressInput, publicClient);\n          address = resolved.address;\n        } catch (err) {\n          return errorResult(err instanceof Error ? err.message : String(err));\n        }\n      } else {\n        address = addressInput;\n      }\n\n      // Bankr mode: route all balance queries through Bankr API\n      if (isBankrMode() && !readStringParam(params, 'address')) {\n        return handleBankrBalance(action, chain);\n      }\n\n      switch (action) {\n        case 'overview':\n          return handleOverview(address as `0x${string}`, chain);\n        case 'tokens':\n          return handleTokens(address as `0x${string}`, chain);\n        case 'eth':\n          return handleEthBalance(address as `0x${string}`, chain);\n        default:\n          return errorResult(`Unknown action: ${action}`);\n      }\n    },\n  };\n}\n\n/**\n * Get a public client for the requested chain.\n * Tries RpcManager first (multi-provider failover), falls back to\n * the WalletConnect service's single client for backward compatibility.\n */\nasync function getClientForChain(chain: string) {\n  try {\n    const rpcManager = getRpcManager();\n    return await rpcManager.getClient(chain);\n  } catch {\n    // Fallback to walletconnect-service's public client (Base only)\n    const wc = getPublicClient();\n    if (wc) return wc;\n    throw new Error(\n      'No RPC available. Set ALCHEMY_API_KEY or configure RPC providers.'\n    );\n  }\n}\n\nasync function handleEthBalance(address: `0x${string}`, chain: string) {\n  try {\n    const publicClient = await getClientForChain(chain);\n    const { formatEther } = await import('viem');\n\n    const balance = await publicClient.getBalance({ address });\n    const ethBalance = formatEther(balance);\n\n    // Get ETH price via cross-validated oracle\n    let ethPriceUsd = 0;\n    let priceConfidence: string | undefined;\n    try {\n      const oracle = getPriceOracle();\n      const ethPrice = await oracle.getEthPrice();\n      ethPriceUsd = ethPrice.priceUsd;\n      priceConfidence = ethPrice.confidence;\n    } catch {\n      // Non-fatal — price is informational\n    }\n\n    return jsonResult({\n      address,\n      chain,\n      ethBalance,\n      ethBalanceWei: balance.toString(),\n      ethPriceUsd: ethPriceUsd || undefined,\n      ethValueUsd: ethPriceUsd ? parseFloat(ethBalance) * ethPriceUsd : undefined,\n      priceConfidence,\n    });\n  } catch (err) {\n    return errorResult(`Balance check failed: ${err instanceof Error ? err.message : String(err)}`);\n  }\n}\n\nasync function handleTokens(address: `0x${string}`, chain: string) {\n  try {\n    const publicClient = await getClientForChain(chain);\n    const rpcManager = getRpcManager();\n    const chainId = rpcManager.resolveChainId(chain);\n\n    // Use Clawnch API to get portfolio if available\n    try {\n      const { ClawnchClient } = await import('@clawnch/clawncher-sdk');\n      const client = new ClawnchClient({\n        baseUrl: process.env.CLAWNCHER_API_URL || 'https://clawn.ch',\n      });\n\n      const fees = await client.getAvailableFees(address);\n      if (fees) {\n        return jsonResult({\n          address,\n          chain,\n          source: 'clawnch-api',\n          ...fees,\n        });\n      }\n    } catch {\n      // Fallback: try basic ERC-20 balance check via public APIs\n    }\n\n    // Fallback: report that detailed token scanning requires an indexer\n    return jsonResult({\n      address,\n      chain,\n      chainId,\n      note: 'For detailed ERC-20 balances, set up an Alchemy/Infura RPC or use Clawnch API. ' +\n        'ETH balance is available via action \"eth\". ' +\n        'Use defi_price tool to check individual token prices.',\n    });\n  } catch (err) {\n    return errorResult(`Token balance check failed: ${err instanceof Error ? err.message : String(err)}`);\n  }\n}\n\n// ─── Bankr Balance Handler ────────────────────────────────────────────────\n\nasync function handleBankrBalance(action: string, chain: string) {\n  try {\n    const { getBankrBalances } = await import('../services/bankr-api.js');\n    const { CHAIN_MAP } = await import('../services/bankr-types.js');\n\n    // Determine which chains to query\n    const bankrChain = CHAIN_MAP[chain.toLowerCase()];\n    const chains = bankrChain\n      ? [bankrChain]\n      : ['base' as const, 'mainnet' as const, 'polygon' as const, 'unichain' as const, 'solana' as const];\n\n    const data = await getBankrBalances(chains);\n\n    if (action === 'eth') {\n      // Just native balances\n      const nativeBalances = data.chains.map(c => ({\n        chain: c.chain,\n        balance: c.nativeBalance,\n        balanceUsd: c.nativeBalanceUsd,\n      }));\n      return jsonResult({\n        source: 'bankr',\n        nativeBalances,\n        totalNativeUsd: nativeBalances.reduce((sum, b) => sum + b.balanceUsd, 0),\n      });\n    }\n\n    if (action === 'tokens') {\n      // Token breakdowns per chain\n      const tokensByChain = data.chains\n        .filter(c => c.tokens.length > 0)\n        .map(c => ({\n          chain: c.chain,\n          tokens: c.tokens.map(t => ({\n            symbol: t.symbol,\n            name: t.name,\n            balance: t.balance,\n            balanceUsd: t.balanceUsd,\n            price: t.price,\n          })),\n        }));\n      return jsonResult({\n        source: 'bankr',\n        tokensByChain,\n        totalTokensUsd: data.chains.reduce(\n          (sum, c) => sum + c.tokens.reduce((s, t) => s + t.balanceUsd, 0), 0\n        ),\n      });\n    }\n\n    // Overview: full portfolio\n    const portfolio = data.chains.map(c => ({\n      chain: c.chain,\n      native: {\n        balance: c.nativeBalance,\n        balanceUsd: c.nativeBalanceUsd,\n      },\n      tokens: c.tokens.map(t => ({\n        symbol: t.symbol,\n        name: t.name,\n        balance: t.balance,\n        balanceUsd: t.balanceUsd,\n        price: t.price,\n      })),\n      totalUsd: c.totalUsd,\n    }));\n\n    return jsonResult({\n      source: 'bankr',\n      portfolio,\n      totalUsd: data.totalUsd,\n    });\n  } catch (err) {\n    return errorResult(`Bankr balance check failed: ${err instanceof Error ? err.message : String(err)}`);\n  }\n}\n\n// ─── Local Balance Handlers ──────────────────────────────────────────────\n\nasync function handleOverview(address: `0x${string}`, chain: string) {\n  try {\n    // Get ETH balance\n    const ethResult = await handleEthBalance(address, chain);\n    const ethData = JSON.parse(ethResult.content[0]!.text);\n\n    // Get token balances  \n    const tokenResult = await handleTokens(address, chain);\n    const tokenData = JSON.parse(tokenResult.content[0]!.text);\n\n    return jsonResult({\n      address,\n      chain,\n      eth: {\n        balance: ethData.ethBalance,\n        priceUsd: ethData.ethPriceUsd,\n        valueUsd: ethData.ethValueUsd,\n        priceConfidence: ethData.priceConfidence,\n      },\n      tokens: tokenData,\n      totalValueUsd: ethData.ethValueUsd ?? 'ETH price unavailable',\n    });\n  } catch (err) {\n    return errorResult(`Portfolio overview failed: ${err instanceof Error ? err.message : String(err)}`);\n  }\n}\n"],"mappings":"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"}