{"version":3,"file":"connect-command.mjs","names":[],"sources":["../../../src/commands/connect-command.ts"],"sourcesContent":["/**\n * /connect commands — connect a mobile wallet via WalletConnect.\n *\n * Each wallet gets its own slash command so it's tappable in Telegram:\n *   /connect           — shows wallet menu\n *   /connect_metamask  — connect MetaMask\n *   /connect_rainbow   — connect Rainbow\n *   /connect_coinbase  — connect Coinbase Wallet\n *   /connect_trust     — connect Trust Wallet\n *   /connect_zerion    — connect Zerion\n *   /connect_uniswap   — connect Uniswap Wallet\n *   /connect_rabby     — connect Rabby (desktop, raw URI)\n *   /connect_other     — any wallet (raw URI)\n */\n\nimport {\n  initWalletService,\n  waitForWalletSession,\n  getWalletState,\n  isBankrMode,\n  disconnectWallet,\n} from '../services/walletconnect-service.js';\nimport { getBankrUserInfo, hasBankrApi } from '../services/bankr-api.js';\nimport { createChannelSender, extractChannelId, type ChannelId } from '../services/channel-sender.js';\nimport { getOnboardingFlow } from '../services/onboarding-flow.js';\nimport { getCredentialVault } from '../services/credential-vault.js';\n\n// ── Wallet Deep Link Configuration ──────────────────────────────────────────\n\ninterface WalletOption {\n  id: string;\n  label: string;\n  deepLink?: string; // undefined = desktop/raw URI\n}\n\nconst WALLETS: WalletOption[] = [\n  { id: 'metamask',  label: 'MetaMask',         deepLink: 'https://metamask.app.link/wc?uri=' },\n  { id: 'rainbow',   label: 'Rainbow',          deepLink: 'https://rnbwapp.com/wc?uri=' },\n  { id: 'coinbase',  label: 'Coinbase Wallet',  deepLink: 'https://go.cb-w.com/wc?uri=' },\n  { id: 'trust',     label: 'Trust Wallet',     deepLink: 'https://link.trustwallet.com/wc?uri=' },\n  { id: 'zerion',    label: 'Zerion',           deepLink: 'https://wallet.zerion.io/wc?uri=' },\n  { id: 'uniswap',   label: 'Uniswap Wallet',  deepLink: 'https://uniswap.org/app/wc?uri=' },\n  { id: 'rabby',     label: 'Rabby (desktop)' },\n  { id: 'other',     label: 'Other wallet' },\n];\n\n/** Stored plugin API reference for sending messages after session establishes. */\nlet _api: any = null;\n/** Guard against duplicate waitForWalletSession callbacks. */\nlet _pendingSessionWait = false;\n\nexport function setConnectCommandApi(api: any): void {\n  _api = api;\n}\n\n// ── Shared Connect Logic ────────────────────────────────────────────────────\n\nasync function doConnect(wallet: WalletOption, ctx: any): Promise<{ text: string }> {\n  const state = getWalletState();\n  if (state.connected && state.address) {\n    return {\n      text: `Wallet already connected: ${state.address.slice(0, 6)}...${state.address.slice(-4)} (chain ${state.chainId})\\n\\nUse /disconnect to disconnect first.`,\n    };\n  }\n\n  const projectId = getCredentialVault().getSecret('walletconnect.projectId', 'connect-command') ?? undefined;\n  const privateKey = getCredentialVault().getSecret('wallet.privateKey', 'connect-command') ?? undefined;\n\n  if (!projectId && !privateKey) {\n    return {\n      text: `WalletConnect is not configured.\\n\\nTo enable it, run \\`openclawnch init\\` or add WALLETCONNECT_PROJECT_ID to your .env file.\\n\\nGet a project ID at https://cloud.reown.com`,\n    };\n  }\n\n  try {\n    const result = await initWalletService({\n      privateKey,\n      walletConnectProjectId: projectId,\n      rpcUrl: process.env.CLAWNCHER_RPC_URL,\n      network: (process.env.CLAWNCHER_NETWORK as 'mainnet' | 'sepolia') || 'mainnet',\n      sessionPath: process.env.WALLETCONNECT_SESSION\n        || `${process.env.HOME ?? ''}/.openclawnch/wc-session.json`,\n    });\n\n    if (result.mode === 'private_key') {\n      return {\n        text: `Connected via private key (headless mode).\\nAddress: ${result.address}\\n\\nTransactions will be auto-signed.`,\n      };\n    }\n\n    if (result.address && !result.pairingUri) {\n      return {\n        text: `Wallet already connected: ${result.address.slice(0, 6)}...${result.address.slice(-4)}\\n\\nSession restored from previous connection.`,\n      };\n    }\n\n    if (result.pairingUri) {\n      // Get the sender's chat ID and channel for the callback\n      const chatId = ctx?.conversationId ?? ctx?.senderId ?? ctx?.from;\n      const channel: ChannelId = extractChannelId(ctx) ?? 'telegram';\n\n      // Start background session wait — when approved, send confirmation + advance onboarding\n      // Guard: only one wait at a time to prevent duplicate callbacks\n      const connectUserId = ctx?.senderId ?? ctx?.from ?? chatId;\n      if (_pendingSessionWait) {\n        // Already waiting — don't start another\n      } else {\n      _pendingSessionWait = true;\n      waitForWalletSession(300_000)\n        .then((session) => {\n          console.log(`[/connect] Session established: ${session.address} (chain ${session.chainId})`);\n\n          // Advance onboarding if user was on connect_wallet step\n          if (connectUserId) {\n            const flow = getOnboardingFlow(String(connectUserId));\n            const onboardingMsg = flow.onWalletConnected(\n              session.address,\n              `chain ${session.chainId}`,\n            );\n            if (onboardingMsg && _api) {\n              // Send the onboarding progression message instead of the generic confirmation\n              const sender = createChannelSender(_api);\n              sender.send(channel, String(chatId), onboardingMsg.text)\n                .catch((err: any) => console.log(`[/connect] Failed to send onboarding msg: ${err}`));\n              return; // Don't send the generic confirmation too\n            }\n          }\n\n          // Generic confirmation (user not in onboarding)\n          if (_api && chatId) {\n            const sender = createChannelSender(_api);\n            sender.send(channel, String(chatId), `Wallet connected!\\n\\nAddress: ${session.address.slice(0, 6)}...${session.address.slice(-4)}\\nChain: ${session.chainId}\\n\\nYou're ready to trade. Try: \"What's the price of ETH?\" or \"Show my balance\"`)\n              .catch((err: any) => console.log(`[/connect] Failed to send confirmation: ${err}`));\n          }\n        })\n        .catch((err) => {\n          console.log(`[/connect] Session wait failed: ${err instanceof Error ? err.message : String(err)}`);\n          // Notify user if we have a channel to send to\n          if (_api && chatId) {\n            const sender = createChannelSender(_api);\n            sender.send(channel, String(chatId), 'Connection timed out. Run /connect to try again.')\n              .catch((sendErr: any) => console.log(`[/connect] Failed to send timeout msg: ${sendErr}`));\n          }\n        })\n        .finally(() => { _pendingSessionWait = false; });\n      } // end else (_pendingSessionWait guard)\n\n      const encodedUri = encodeURIComponent(result.pairingUri);\n\n      // Desktop wallets get raw URI (no deep link available)\n      if (!wallet.deepLink) {\n        return {\n          text: `Copy this code and paste it in your wallet's WalletConnect input:\\n\\n\\`${result.pairingUri}\\`\\n\\nExpires in 5 minutes.`,\n        };\n      }\n\n      // Mobile wallets get a clean hyperlink\n      const url = `${wallet.deepLink}${encodedUri}`;\n      return {\n        text: `[Connect ${wallet.label}](${url})\\n\\nTap the link above to open ${wallet.label} and approve the connection. Expires in 5 minutes.`,\n      };\n    }\n\n    return {\n      text: 'WalletConnect failed to generate a pairing link. The relay server may be down. Try again in a minute.',\n    };\n  } catch (err) {\n    return {\n      text: `Connection failed: ${err instanceof Error ? err.message : String(err)}`,\n    };\n  }\n}\n\n// ── Command Definitions ─────────────────────────────────────────────────────\n\n/** /connect — shows wallet menu with tappable options */\nexport const connectCommand = {\n  name: 'connect',\n  description: 'Connect your mobile wallet via WalletConnect',\n  acceptsArgs: false,\n  requireAuth: true,\n  handler: async (_ctx: any) => {\n    const state = getWalletState();\n    if (state.connected && state.address) {\n      return {\n        text: `Wallet already connected: ${state.address.slice(0, 6)}...${state.address.slice(-4)} (chain ${state.chainId})\\n\\nUse /disconnect to disconnect first.`,\n      };\n    }\n\n    const lines = ['Which wallet do you use?\\n'];\n    for (const w of WALLETS) {\n      lines.push(`  /connect_${w.id} — ${w.label}`);\n    }\n    lines.push('  /connect_bankr — Bankr (custodial, multi-chain)');\n    lines.push('\\nTap one to connect.');\n    return { text: lines.join('\\n') };\n  },\n};\n\n/** Generate a /connect_<wallet> command */\nfunction makeWalletCommand(wallet: WalletOption) {\n  return {\n    name: `connect_${wallet.id}`,\n    description: `Connect ${wallet.label} via WalletConnect`,\n    acceptsArgs: false,\n    requireAuth: true,\n    handler: async (ctx: any) => doConnect(wallet, ctx),\n  };\n}\n\n/** All individual wallet commands: /connect_metamask, /connect_rainbow, etc. */\nexport const walletConnectCommands = WALLETS.map(makeWalletCommand);\n\n// ── /connect_bankr ──────────────────────────────────────────────────────────\n\nexport const connectBankrCommand = {\n  name: 'connect_bankr',\n  description: 'Connect Bankr custodial wallet (multi-chain)',\n  acceptsArgs: false,\n  requireAuth: true,\n  handler: async (_ctx: any) => {\n    // Already connected?\n    const state = getWalletState();\n    if (state.connected && state.mode === 'bankr') {\n      return {\n        text: [\n          '**Already connected via Bankr**',\n          '',\n          `EVM: ${state.bankrEvmAddress ?? 'unknown'}`,\n          state.bankrSolAddress ? `Solana: ${state.bankrSolAddress}` : null,\n          state.bankrClub ? 'Bankr Club: Active' : null,\n          '',\n          'Use /connect to switch to a different wallet.',\n        ].filter(Boolean).join('\\n'),\n      };\n    }\n    if (state.connected) {\n      return {\n        text: `Wallet already connected (${state.mode}): ${state.address?.slice(0, 6)}...${state.address?.slice(-4)}\\n\\nDisconnect first to switch to Bankr.`,\n      };\n    }\n\n    // Check for API key\n    const apiKey = getCredentialVault().getSecret('bankr.apiKey', 'connect-command');\n    if (!apiKey) {\n      return {\n        text: [\n          '**BANKR_API_KEY not set**',\n          '',\n          'To use Bankr as your wallet:',\n          '1. Create an account at https://bankr.bot',\n          '2. Get an API key with Agent API enabled: https://bankr.bot/api',\n          '3. Set the environment variable:',\n          '   Fly.io: `fly secrets set BANKR_API_KEY=\"bk_your_key\" -a <your-app>`',\n          '   Docker: add `BANKR_API_KEY=bk_your_key` to your `.env` file',\n        ].join('\\n'),\n      };\n    }\n\n    try {\n      const result = await initWalletService({ bankrApiKey: apiKey });\n\n      if (result.mode !== 'bankr') {\n        return {\n          text: 'Failed to initialize Bankr wallet. Check your BANKR_API_KEY.',\n        };\n      }\n\n      const newState = getWalletState();\n      return {\n        text: [\n          '**Connected via Bankr** (custodial wallet)',\n          '',\n          `EVM: ${newState.bankrEvmAddress ?? result.address ?? 'unknown'}`,\n          newState.bankrSolAddress ? `Solana: ${newState.bankrSolAddress}` : null,\n          newState.bankrClub ? `Bankr Club: Active` : null,\n          '',\n          'Transactions execute server-side. No phone approval needed.',\n          'Bankr\\'s Sentinel security system screens all transactions.',\n          '',\n          'This is a custodial wallet — Bankr holds the keys.',\n          'For self-custody, use /connect_metamask or another wallet.',\n        ].filter(Boolean).join('\\n'),\n      };\n    } catch (err) {\n      const msg = err instanceof Error ? err.message : String(err);\n      if (msg.includes('Agent API not enabled') || msg.includes('403')) {\n        return {\n          text: [\n            '**Agent API not enabled on this key**',\n            '',\n            'Your Bankr API key was recognized but doesn\\'t have Agent API access.',\n            '',\n            '**Fix it:**',\n            '1. Go to https://bankr.bot/api',\n            '2. Find your API key',\n            '3. Enable the \"Agent API\" toggle',\n          ].join('\\n'),\n        };\n      }\n      return {\n        text: `Bankr connection failed: ${msg}`,\n      };\n    }\n  },\n};\n\n// ── /disconnect ─────────────────────────────────────────────────────────────\n\nexport const disconnectCommand = {\n  name: 'disconnect',\n  description: 'Disconnect the current wallet',\n  acceptsArgs: false,\n  requireAuth: true,\n  handler: async (_ctx: any) => {\n    const state = getWalletState();\n    if (!state.connected) {\n      return { text: 'No wallet connected.' };\n    }\n\n    const addr = state.address ?? 'unknown';\n    const short = `${addr.slice(0, 6)}...${addr.slice(-4)}`;\n    const mode = state.mode ?? 'unknown';\n\n    try {\n      await disconnectWallet();\n      return {\n        text: `Disconnected wallet ${short} (${mode}).\\n\\nUse /connect to pair a new wallet.`,\n      };\n    } catch (err) {\n      return {\n        text: `Failed to disconnect: ${err instanceof Error ? err.message : String(err)}`,\n      };\n    }\n  },\n};\n"],"mappings":"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"}