{"version":3,"file":"bankr-automate.mjs","names":[],"sources":["../../../src/tools/bankr-automate.ts"],"sourcesContent":["/**\n * Bankr Automate Tool — set up trading automations via Bankr Agent API.\n *\n * Supports: limit buy, limit sell, stop-loss, DCA, TWAP, cancel, list.\n * All automations are submitted as natural language prompts to Bankr's\n * prompt API and polled for completion.\n *\n * Automations are EVM-only, primarily Base.\n */\n\nimport { Type } from '@sinclair/typebox';\nimport { stringEnum, jsonResult, errorResult, readStringParam } from '../lib/tool-helpers.js';\nimport { hasBankrApi } from '../services/bankr-api.js';\n\nconst ACTIONS = ['limit_buy', 'limit_sell', 'stop_loss', 'dca', 'twap', 'cancel', 'list'] as const;\n\nconst BankrAutomateSchema = Type.Object({\n  action: stringEnum(ACTIONS, {\n    description:\n      'limit_buy: buy when price drops. limit_sell: sell when price rises. ' +\n      'stop_loss: sell if price drops below threshold. ' +\n      'dca: dollar-cost average on a schedule. twap: time-weighted sell. ' +\n      'cancel: cancel an automation. list: show active automations.',\n  }),\n  token: Type.Optional(Type.String({\n    description: 'Token symbol or address. Required for buy/sell/dca/twap.',\n  })),\n  amount: Type.Optional(Type.String({\n    description: 'Dollar amount or token amount (e.g. \"$100\", \"0.5 ETH\").',\n  })),\n  trigger: Type.Optional(Type.String({\n    description: 'Trigger condition for limit/stop (e.g. \"drops 10%\", \"rises 20%\", \"reaches $50000\").',\n  })),\n  interval: Type.Optional(Type.String({\n    description: 'DCA interval (e.g. \"every day\", \"every 6 hours\", \"every week\").',\n  })),\n  duration: Type.Optional(Type.String({\n    description: 'Duration for DCA/TWAP (e.g. \"for 7 days\", \"over 4 hours\").',\n  })),\n  time: Type.Optional(Type.String({\n    description: 'Execution time for DCA (e.g. \"at 9am\", \"at midnight UTC\").',\n  })),\n  chain: Type.Optional(Type.String({\n    description: 'Chain (default: base). Automations are primarily Base.',\n  })),\n  automation_id: Type.Optional(Type.String({\n    description: 'ID of automation to cancel.',\n  })),\n});\n\n// ─── Input Sanitization (C3: prevent prompt injection) ──────────────────\n// Bankr NL prompts are built from user-supplied fields. Validate that\n// token symbols/addresses, amounts, and triggers contain only expected chars.\n\nconst SAFE_TOKEN_RE = /^[a-zA-Z0-9_.\\-\\/]{1,60}$/;\nconst SAFE_ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;\nconst SAFE_AMOUNT_RE = /^\\$?[0-9][0-9,._]*\\s*[a-zA-Z]*$/;\nconst SAFE_TRIGGER_RE = /^[a-zA-Z0-9$%.,\\s\\-+<>=!]{1,120}$/;\nconst SAFE_INTERVAL_RE = /^[a-zA-Z0-9\\s]{1,60}$/;\nconst SAFE_ID_RE = /^[a-zA-Z0-9_\\-]{1,80}$/;\n\nfunction sanitizeToken(input: string): string {\n  const trimmed = input.trim();\n  if (SAFE_ADDRESS_RE.test(trimmed) || SAFE_TOKEN_RE.test(trimmed)) return trimmed;\n  throw new Error(`Invalid token: \"${trimmed.slice(0, 30)}\". Use a symbol (e.g. \"ETH\") or address (0x...).`);\n}\n\nfunction sanitizeAmount(input: string): string {\n  const trimmed = input.trim();\n  if (SAFE_AMOUNT_RE.test(trimmed)) return trimmed;\n  throw new Error(`Invalid amount: \"${trimmed.slice(0, 30)}\". Use a number like \"$100\" or \"0.5 ETH\".`);\n}\n\nfunction sanitizeTrigger(input: string): string {\n  const trimmed = input.trim();\n  if (SAFE_TRIGGER_RE.test(trimmed)) return trimmed;\n  throw new Error(`Invalid trigger: \"${trimmed.slice(0, 30)}\". Use a simple condition like \"drops 10%\" or \"reaches $50000\".`);\n}\n\nfunction sanitizeInterval(input: string): string {\n  const trimmed = input.trim();\n  if (SAFE_INTERVAL_RE.test(trimmed)) return trimmed;\n  throw new Error(`Invalid interval: \"${trimmed.slice(0, 30)}\". Use something like \"every day\" or \"every 6 hours\".`);\n}\n\nfunction sanitizeId(input: string): string {\n  const trimmed = input.trim();\n  if (SAFE_ID_RE.test(trimmed)) return trimmed;\n  throw new Error(`Invalid automation ID: \"${trimmed.slice(0, 30)}\".`);\n}\n\n// Prompt templates for each action\nfunction buildPrompt(action: string, params: Record<string, unknown>): string {\n  const token = readStringParam(params, 'token') ? sanitizeToken(readStringParam(params, 'token')!) : '';\n  const amount = readStringParam(params, 'amount') ? sanitizeAmount(readStringParam(params, 'amount')!) : '';\n  const trigger = readStringParam(params, 'trigger') ? sanitizeTrigger(readStringParam(params, 'trigger')!) : '';\n  const interval = readStringParam(params, 'interval') ? sanitizeInterval(readStringParam(params, 'interval')!) : '';\n  const duration = readStringParam(params, 'duration') ? sanitizeInterval(readStringParam(params, 'duration')!) : '';\n  const time = readStringParam(params, 'time') ? sanitizeInterval(readStringParam(params, 'time')!) : '';\n  const automationId = readStringParam(params, 'automation_id') ? sanitizeId(readStringParam(params, 'automation_id')!) : '';\n\n  switch (action) {\n    case 'limit_buy':\n      return `buy ${amount} of ${token} if it ${trigger}`;\n    case 'limit_sell':\n      return `sell my ${token} when it ${trigger}`;\n    case 'stop_loss':\n      return `sell all my ${token} if it ${trigger}`;\n    case 'dca':\n      return `DCA ${amount} into ${token} ${interval}${time ? ` at ${time}` : ''}${duration ? ` ${duration}` : ''}`;\n    case 'twap':\n      return `sell ${amount} ${token} ${duration}`;\n    case 'cancel':\n      return automationId\n        ? `cancel my automation ${automationId}`\n        : 'cancel my automations';\n    case 'list':\n      return 'show my active automations';\n    default:\n      return action;\n  }\n}\n\nexport function createBankrAutomateTool() {\n  return {\n    name: 'bankr_automate',\n    label: 'Bankr Automate',\n    ownerOnly: true,\n    description:\n      'Set up trading automations via Bankr: limit orders (buy/sell), stop-loss, ' +\n      'dollar-cost averaging (DCA), and time-weighted average price (TWAP) sells. ' +\n      'Automations execute server-side on Base. Use \"list\" to see active automations, ' +\n      '\"cancel\" to stop them. Requires Bankr wallet (/connect_bankr).',\n    parameters: BankrAutomateSchema,\n    execute: async (_toolCallId: string, args: unknown) => {\n      const params = args as Record<string, unknown>;\n      const action = readStringParam(params, 'action', { required: true })!;\n\n      if (!hasBankrApi()) {\n        return errorResult(\n          'Bankr API key not configured. Connect via /connect_bankr first.'\n        );\n      }\n\n      // Validate required params per action\n      if (['limit_buy', 'limit_sell', 'stop_loss'].includes(action)) {\n        if (!readStringParam(params, 'token')) {\n          return errorResult(`\"token\" is required for ${action}.`);\n        }\n        if (!readStringParam(params, 'trigger')) {\n          return errorResult(`\"trigger\" is required for ${action} (e.g. \"drops 10%\", \"reaches $50000\").`);\n        }\n        if (action === 'limit_buy' && !readStringParam(params, 'amount')) {\n          return errorResult('\"amount\" is required for limit_buy.');\n        }\n      }\n\n      if (action === 'dca') {\n        if (!readStringParam(params, 'token')) {\n          return errorResult('\"token\" is required for DCA.');\n        }\n        if (!readStringParam(params, 'amount')) {\n          return errorResult('\"amount\" is required for DCA.');\n        }\n        if (!readStringParam(params, 'interval')) {\n          return errorResult('\"interval\" is required for DCA (e.g. \"every day\", \"every 6 hours\").');\n        }\n      }\n\n      if (action === 'twap') {\n        if (!readStringParam(params, 'token')) {\n          return errorResult('\"token\" is required for TWAP.');\n        }\n        if (!readStringParam(params, 'amount')) {\n          return errorResult('\"amount\" is required for TWAP.');\n        }\n        if (!readStringParam(params, 'duration')) {\n          return errorResult('\"duration\" is required for TWAP (e.g. \"over 4 hours\").');\n        }\n      }\n\n      try {\n        const { bankrPromptAndPoll } = await import('../services/bankr-api.js');\n\n        const prompt = buildPrompt(action, params);\n        const timeoutMs = action === 'list' ? 30_000 : 60_000;\n        const result = await bankrPromptAndPoll(prompt, { timeoutMs });\n\n        if (result.status === 'failed') {\n          return errorResult(`Automation failed: ${result.error ?? 'Unknown error'}`);\n        }\n\n        return jsonResult({\n          status: 'success',\n          action,\n          response: result.response,\n          richData: result.richData,\n          transactions: result.transactions,\n        });\n      } catch (err) {\n        return errorResult(\n          `Automation failed: ${err instanceof Error ? err.message : String(err)}`\n        );\n      }\n    },\n  };\n}\n"],"mappings":"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"}