From b24be8289e5388ec300c9dc789f8c6d0b85acd1b Mon Sep 17 00:00:00 2001 From: bran21 Date: Mon, 20 Apr 2026 18:56:03 +0700 Subject: [PATCH 1/6] chore: save state, investigate API ratelimits --- {skills => .agents/skills}/chains/LICENSE.txt | 0 {skills => .agents/skills}/chains/SKILL.md | 0 .../skills}/wallet-analysis/EXAMPLES.md | 0 .../skills}/wallet-analysis/LICENSE.txt | 0 .../skills}/wallet-analysis/README.md | 0 .../skills}/wallet-analysis/SKILL.md | 0 .../skills}/wallet-trading/LICENSE.txt | 0 .../skills}/wallet-trading/SKILL.md | 0 {skills => .agents/skills}/zerion/LICENSE.txt | 0 {skills => .agents/skills}/zerion/SKILL.md | 0 .claude/skills/chains | 1 + .claude/skills/wallet-analysis | 1 + .claude/skills/wallet-trading | 1 + .claude/skills/zerion | 1 + .qwen/skills/chains | 1 + .qwen/skills/wallet-analysis | 1 + .qwen/skills/wallet-trading | 1 + .qwen/skills/zerion | 1 + package-lock.json | 16 ++---------- skills-lock.json | 25 +++++++++++++++++++ skills/chains | 1 + skills/wallet-analysis | 1 + skills/wallet-trading | 1 + skills/zerion | 1 + 24 files changed, 39 insertions(+), 14 deletions(-) rename {skills => .agents/skills}/chains/LICENSE.txt (100%) rename {skills => .agents/skills}/chains/SKILL.md (100%) rename {skills => .agents/skills}/wallet-analysis/EXAMPLES.md (100%) rename {skills => .agents/skills}/wallet-analysis/LICENSE.txt (100%) rename {skills => .agents/skills}/wallet-analysis/README.md (100%) rename {skills => .agents/skills}/wallet-analysis/SKILL.md (100%) rename {skills => .agents/skills}/wallet-trading/LICENSE.txt (100%) rename {skills => .agents/skills}/wallet-trading/SKILL.md (100%) rename {skills => .agents/skills}/zerion/LICENSE.txt (100%) rename {skills => .agents/skills}/zerion/SKILL.md (100%) create mode 120000 .claude/skills/chains create mode 120000 .claude/skills/wallet-analysis create mode 120000 .claude/skills/wallet-trading create mode 120000 .claude/skills/zerion create mode 120000 .qwen/skills/chains create mode 120000 .qwen/skills/wallet-analysis create mode 120000 .qwen/skills/wallet-trading create mode 120000 .qwen/skills/zerion create mode 100644 skills-lock.json create mode 120000 skills/chains create mode 120000 skills/wallet-analysis create mode 120000 skills/wallet-trading create mode 120000 skills/zerion diff --git a/skills/chains/LICENSE.txt b/.agents/skills/chains/LICENSE.txt similarity index 100% rename from skills/chains/LICENSE.txt rename to .agents/skills/chains/LICENSE.txt diff --git a/skills/chains/SKILL.md b/.agents/skills/chains/SKILL.md similarity index 100% rename from skills/chains/SKILL.md rename to .agents/skills/chains/SKILL.md diff --git a/skills/wallet-analysis/EXAMPLES.md b/.agents/skills/wallet-analysis/EXAMPLES.md similarity index 100% rename from skills/wallet-analysis/EXAMPLES.md rename to .agents/skills/wallet-analysis/EXAMPLES.md diff --git a/skills/wallet-analysis/LICENSE.txt b/.agents/skills/wallet-analysis/LICENSE.txt similarity index 100% rename from skills/wallet-analysis/LICENSE.txt rename to .agents/skills/wallet-analysis/LICENSE.txt diff --git a/skills/wallet-analysis/README.md b/.agents/skills/wallet-analysis/README.md similarity index 100% rename from skills/wallet-analysis/README.md rename to .agents/skills/wallet-analysis/README.md diff --git a/skills/wallet-analysis/SKILL.md b/.agents/skills/wallet-analysis/SKILL.md similarity index 100% rename from skills/wallet-analysis/SKILL.md rename to .agents/skills/wallet-analysis/SKILL.md diff --git a/skills/wallet-trading/LICENSE.txt b/.agents/skills/wallet-trading/LICENSE.txt similarity index 100% rename from skills/wallet-trading/LICENSE.txt rename to .agents/skills/wallet-trading/LICENSE.txt diff --git a/skills/wallet-trading/SKILL.md b/.agents/skills/wallet-trading/SKILL.md similarity index 100% rename from skills/wallet-trading/SKILL.md rename to .agents/skills/wallet-trading/SKILL.md diff --git a/skills/zerion/LICENSE.txt b/.agents/skills/zerion/LICENSE.txt similarity index 100% rename from skills/zerion/LICENSE.txt rename to .agents/skills/zerion/LICENSE.txt diff --git a/skills/zerion/SKILL.md b/.agents/skills/zerion/SKILL.md similarity index 100% rename from skills/zerion/SKILL.md rename to .agents/skills/zerion/SKILL.md diff --git a/.claude/skills/chains b/.claude/skills/chains new file mode 120000 index 0000000..a77f143 --- /dev/null +++ b/.claude/skills/chains @@ -0,0 +1 @@ +../../.agents/skills/chains \ No newline at end of file diff --git a/.claude/skills/wallet-analysis b/.claude/skills/wallet-analysis new file mode 120000 index 0000000..ba9face --- /dev/null +++ b/.claude/skills/wallet-analysis @@ -0,0 +1 @@ +../../.agents/skills/wallet-analysis \ No newline at end of file diff --git a/.claude/skills/wallet-trading b/.claude/skills/wallet-trading new file mode 120000 index 0000000..9c94fa5 --- /dev/null +++ b/.claude/skills/wallet-trading @@ -0,0 +1 @@ +../../.agents/skills/wallet-trading \ No newline at end of file diff --git a/.claude/skills/zerion b/.claude/skills/zerion new file mode 120000 index 0000000..50c9c56 --- /dev/null +++ b/.claude/skills/zerion @@ -0,0 +1 @@ +../../.agents/skills/zerion \ No newline at end of file diff --git a/.qwen/skills/chains b/.qwen/skills/chains new file mode 120000 index 0000000..a77f143 --- /dev/null +++ b/.qwen/skills/chains @@ -0,0 +1 @@ +../../.agents/skills/chains \ No newline at end of file diff --git a/.qwen/skills/wallet-analysis b/.qwen/skills/wallet-analysis new file mode 120000 index 0000000..ba9face --- /dev/null +++ b/.qwen/skills/wallet-analysis @@ -0,0 +1 @@ +../../.agents/skills/wallet-analysis \ No newline at end of file diff --git a/.qwen/skills/wallet-trading b/.qwen/skills/wallet-trading new file mode 120000 index 0000000..9c94fa5 --- /dev/null +++ b/.qwen/skills/wallet-trading @@ -0,0 +1 @@ +../../.agents/skills/wallet-trading \ No newline at end of file diff --git a/.qwen/skills/zerion b/.qwen/skills/zerion new file mode 120000 index 0000000..50c9c56 --- /dev/null +++ b/.qwen/skills/zerion @@ -0,0 +1 @@ +../../.agents/skills/zerion \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c6abf2e..e28b664 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "zerion", + "name": "zerion-cli", "version": "0.4.2", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "zerion", + "name": "zerion-cli", "version": "0.4.2", "license": "MIT", "dependencies": { @@ -2935,18 +2935,6 @@ } } }, - "node_modules/node-gyp-build": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", - "license": "MIT", - "optional": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, "node_modules/ox": { "version": "0.14.7", "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.7.tgz", diff --git a/skills-lock.json b/skills-lock.json new file mode 100644 index 0000000..5256217 --- /dev/null +++ b/skills-lock.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "skills": { + "chains": { + "source": "zeriontech/zerion-ai", + "sourceType": "github", + "computedHash": "e9b8be3bc514f149c1a5de2a112c3a5bd9fbc34c746ab60f3950169fc5c9fdbd" + }, + "wallet-analysis": { + "source": "zeriontech/zerion-ai", + "sourceType": "github", + "computedHash": "34882e2db5707cb24937a3d41ce60f6b407fa525580632dac370d4d00e11f165" + }, + "wallet-trading": { + "source": "zeriontech/zerion-ai", + "sourceType": "github", + "computedHash": "a8c52aa096f7e41f453bc591a0e7d1d3392118e82ef98dca12d253bfebb3b633" + }, + "zerion": { + "source": "zeriontech/zerion-ai", + "sourceType": "github", + "computedHash": "069f8369a861d61a12c98c214e3522f9831ae21b78a17a4a839d71e4439f4778" + } + } +} diff --git a/skills/chains b/skills/chains new file mode 120000 index 0000000..1befdca --- /dev/null +++ b/skills/chains @@ -0,0 +1 @@ +../.agents/skills/chains \ No newline at end of file diff --git a/skills/wallet-analysis b/skills/wallet-analysis new file mode 120000 index 0000000..292d7e8 --- /dev/null +++ b/skills/wallet-analysis @@ -0,0 +1 @@ +../.agents/skills/wallet-analysis \ No newline at end of file diff --git a/skills/wallet-trading b/skills/wallet-trading new file mode 120000 index 0000000..2f99d65 --- /dev/null +++ b/skills/wallet-trading @@ -0,0 +1 @@ +../.agents/skills/wallet-trading \ No newline at end of file diff --git a/skills/zerion b/skills/zerion new file mode 120000 index 0000000..3090f08 --- /dev/null +++ b/skills/zerion @@ -0,0 +1 @@ +../.agents/skills/zerion \ No newline at end of file From 6a88517f8b23119f410a3a98a20238038724260b Mon Sep 17 00:00:00 2001 From: bran21 Date: Tue, 21 Apr 2026 20:59:23 +0700 Subject: [PATCH 2/6] landing page --- README.md | 8 + docs/discord-telegram-bots.md | 211 ++++++++++++++++ landing-page/index.html | 216 +++++++++++++++++ landing-page/script.js | 51 ++++ landing-page/style.css | 444 ++++++++++++++++++++++++++++++++++ 5 files changed, 930 insertions(+) create mode 100644 docs/discord-telegram-bots.md create mode 100644 landing-page/index.html create mode 100644 landing-page/script.js create mode 100644 landing-page/style.css diff --git a/README.md b/README.md index 94eee52..bd5f274 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,14 @@ Start here: - [OpenClaw example](./examples/openclaw/README.md) - [CLI usage](./cli/README.md) +### Chat Bots (Discord / Telegram) + +Use this if you are building an interactive AI bot using popular chat platform APIs. + +Start here: + +- [Discord & Telegram implementations](./docs/discord-telegram-bots.md) + ## 4. Run the first wallet analysis ### MCP quickstart diff --git a/docs/discord-telegram-bots.md b/docs/discord-telegram-bots.md new file mode 100644 index 0000000..e1c5184 --- /dev/null +++ b/docs/discord-telegram-bots.md @@ -0,0 +1,211 @@ +# Attaching Zerion AI to Discord and Telegram + +The `zerion-ai` repository provides the **skills** and underlying AI integrations (via MCP or CLI capabilities) that let an AI agent evaluate crypto wallets and execute trades. It does not provide native listener bots for platforms like Discord or Telegram. + +To bring your AI agent to a chat platform, you need a "bot wrapper." This wrapper listens for chat messages, forwards them to an AI framework equipped with Zerion skills, and returns the response to the user. + +--- + +## 1. Discord Bot Implementation (Node.js) + +For Discord, use `discord.js` along with the OpenAI SDK. The agent invokes the `zerion` CLI under the hood. + +### Prerequisites +Installs: +```bash +npm install discord.js openai +``` + +### Boilerplate `discord-bot.js` + +```javascript +import { Client, GatewayIntentBits, Partials } from 'discord.js'; +import OpenAI from 'openai'; +import { exec } from 'child_process'; +import { promisify } from 'util'; + +const execAsync = promisify(exec); +const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); + +// Setup Discord Client +const client = new Client({ + intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent], + partials: [Partials.Message, Partials.Channel], +}); + +// The CLI tool configuration for the LLM +const tools = [ + { + type: 'function', + function: { + name: 'execute_zerion_cli', + description: 'Execute the zerion CLI for wallet evaluation or token swaps.', + parameters: { + type: 'object', + properties: { + command: { + type: 'string', + description: 'The exact zerion CLI command to run (e.g. "zerion wallet analyze
", "zerion swap ETH USDC 0.01")', + }, + }, + required: ['command'], + }, + }, + }, +]; + +client.on('messageCreate', async (message) => { + if (message.author.bot) return; + if (!message.content.startsWith('!zerion')) return; + + const userQuery = message.content.replace('!zerion', '').trim(); + const loadingMsg = await message.reply('Thinking...'); + + try { + // 1. Ask OpenAI to form a plan + const response = await openai.chat.completions.create({ + model: 'gpt-4o', + messages: [{ role: 'user', content: userQuery }], + tools: tools, + }); + + const completion = response.choices[0].message; + + // 2. Check if OpenAI wants to use the CLI tool + if (completion.tool_calls) { + const toolCall = completion.tool_calls[0]; + const args = JSON.parse(toolCall.function.arguments); + + // Execute the requested Zerion command + const { stdout, stderr } = await execAsync(args.command); + + // 3. Send results back to the LLM to format for the user + const finalResponse = await openai.chat.completions.create({ + model: 'gpt-4o', + messages: [ + { role: 'user', content: userQuery }, + completion, + { + role: 'tool', + tool_call_id: toolCall.id, + name: 'execute_zerion_cli', + content: stdout || stderr, + }, + ], + }); + + await loadingMsg.edit(finalResponse.choices[0].message.content); + } else { + await loadingMsg.edit(completion.content); + } + } catch (error) { + console.error(error); + await loadingMsg.edit('An error occurred while communicating with Zerion AI.'); + } +}); + +client.login(process.env.DISCORD_TOKEN); +``` + +--- + +## 2. Telegram Bot Implementation (Node.js) + +For Telegram, you can use the `telegraf` library using a very similar architecture to the Discord bot. + +### Prerequisites +Installs: +```bash +npm install telegraf openai +``` + +### Boilerplate `telegram-bot.js` + +```javascript +import { Telegraf } from 'telegraf'; +import OpenAI from 'openai'; +import { exec } from 'child_process'; +import { promisify } from 'util'; + +const execAsync = promisify(exec); +const bot = new Telegraf(process.env.TELEGRAM_BOT_TOKEN); +const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); + +const tools = [ + { + type: 'function', + function: { + name: 'execute_zerion_cli', + description: 'Run the zerion CLI to evaluate wallets, tokens, and PnL.', + parameters: { + type: 'object', + properties: { + command: { + type: 'string', + description: 'The exact zerion CLI command to run (e.g., "zerion portfolio
").' + } + }, + required: ['command'] + } + } + } +]; + +bot.command('zerion', async (ctx) => { + const userQuery = ctx.message.text.replace('/zerion', '').trim(); + if (!userQuery) return ctx.reply('Please provide a prompt. Example: /zerion What is the portfolio of vitalik.eth?'); + + const loadingMsg = await ctx.reply('Reviewing blockchain data...'); + + try { + const response = await openai.chat.completions.create({ + model: 'gpt-4o', + messages: [{ role: 'user', content: userQuery }], + tools: tools + }); + + const completion = response.choices[0].message; + + if (completion.tool_calls) { + const toolCall = completion.tool_calls[0]; + const args = JSON.parse(toolCall.function.arguments); + + const { stdout, stderr } = await execAsync(args.command); + + const finalResponse = await openai.chat.completions.create({ + model: 'gpt-4o', + messages: [ + { role: 'user', content: userQuery }, + completion, + { + role: 'tool', + tool_call_id: toolCall.id, + name: 'execute_zerion_cli', + content: stdout || stderr + } + ] + }); + + await ctx.telegram.editMessageText(ctx.chat.id, loadingMsg.message_id, undefined, finalResponse.choices[0].message.content); + } else { + await ctx.telegram.editMessageText(ctx.chat.id, loadingMsg.message_id, undefined, completion.content); + } + } catch (error) { + console.error(error); + await ctx.telegram.editMessageText(ctx.chat.id, loadingMsg.message_id, undefined, 'An error occurred during evaluation.'); + } +}); + +bot.launch(); + +process.once('SIGINT', () => bot.stop('SIGINT')); +process.once('SIGTERM', () => bot.stop('SIGTERM')); +``` + +--- + +## Alternative Solutions + +Instead of custom Node.js scripts, you can integrate the `zerion` skills inside comprehensive multi-agent frameworks that feature native chat integration, such as: +- **[Eliza](https://github.com/elizaOS/eliza)**: Supports native Discord, Twitter, and Telegram clients by importing your LLM tasks. +- **[LangChain](https://js.langchain.com/) / LangGraph**: Offers extensive tool wrapping using predefined Node/Python chains. diff --git a/landing-page/index.html b/landing-page/index.html new file mode 100644 index 0000000..e1f3a48 --- /dev/null +++ b/landing-page/index.html @@ -0,0 +1,216 @@ + + + + + + + Smart Transactoors | Unified Wallet Analysis & Trading + + + + + + + + + + + + + + + + + +
+
+
Now open for AI developers
+

Empower Your Agents with Zerion

+

The unified integration toolkit for providing AI agents with real-time wallet + analysis, portfolio tracking, and autonomous trading capabilities.

+ +
+
+ + + +
+
+
$ npx skills add zeriontech/zerion-ai
+Installing skills: wallet-analysis, wallet-trading...
+Done! Your agent is now ready to interact with DeFi.
+
+
+
+
+ + +
+
+

Everything your Agent Needs

+

A self-contained integration path bridging the gap between LLMs and DeFi.

+ +
+ +
+
+ + + + +
+

Dual Integration Paths

+

Native support for Hosted MCP (used by Cursor, Claude) and an elegant + OpenClaw/JSON-first CLI for raw programmatic access. +

+
+ + +
+
+ + + + +
+

Wallet Analysis

+

Read-only operations to fetch portfolio holdings, granular token positions, transaction + histories, and real-time PnL analytics via wallet-analysis.

+
+ + +
+
+ + + + +
+

Autonomous Trading

+

Execute advanced on-chain primitives: token swaps, cross-chain bridging, and targeted buy/sell + operations via the wallet-trading skill.

+
+ + +
+
+ + + +
+

Frictionless x402 Protocol

+

No API keys required. Natively integrates x402 to auto-pay fractions of a cent ($0.01 USDC) per + data pull directly from agent-managed wallets.

+
+
+
+
+ + +
+
+

Frequently Asked Questions

+ +
+
+ +
+

Zerion AI offers two paths: 1) Traditional API Keys (suitable for high-volume deployments), + or 2) x402 Pay-per-call authentication, which securely processes microtransactions using the + agent's EVM or Solana wallet without requiring centralized registration.

+
+
+ +
+ +
+

Zerion AI provides universal coverage across major environments. The chains + skill dynamically lists all actively supported networks, including Ethereum, Base, Optimism, + Solana, and more.

+
+
+ +
+ +
+

Yes. Using the wallet-trading component, agents can be provisioned with + operational wallets to perform autonomous token swaps and bridging operations. Proper use of + security policies is advised for production systems.

+
+
+
+
+
+ + +
+ +
+ + +
+ +
+ + + + + \ No newline at end of file diff --git a/landing-page/script.js b/landing-page/script.js new file mode 100644 index 0000000..0fa5e9a --- /dev/null +++ b/landing-page/script.js @@ -0,0 +1,51 @@ +document.addEventListener('DOMContentLoaded', () => { + // FAQ Accordion Logic + const accordionHeaders = document.querySelectorAll('.accordion-header'); + + accordionHeaders.forEach(header => { + header.addEventListener('click', () => { + const item = header.parentElement; + + // Close other open items + const activeItem = document.querySelector('.accordion-item.active'); + if (activeItem && activeItem !== item) { + activeItem.classList.remove('active'); + activeItem.querySelector('.accordion-content').style.maxHeight = null; + } + + // Toggle current item + item.classList.toggle('active'); + const content = item.querySelector('.accordion-content'); + + if (item.classList.contains('active')) { + content.style.maxHeight = content.scrollHeight + "px"; + } else { + content.style.maxHeight = null; + } + }); + }); + + // Simple smooth scroll behavior for anchor links + document.querySelectorAll('a[href^="#"]').forEach(anchor => { + anchor.addEventListener('click', function (e) { + e.preventDefault(); + + const targetId = this.getAttribute('href'); + if(targetId === '#') return; + + const targetElement = document.querySelector(targetId); + + if(targetElement) { + // Adjust for sticky header + const headerOffset = 80; + const elementPosition = targetElement.getBoundingClientRect().top; + const offsetPosition = elementPosition + window.pageYOffset - headerOffset; + + window.scrollTo({ + top: offsetPosition, + behavior: "smooth" + }); + } + }); + }); +}); diff --git a/landing-page/style.css b/landing-page/style.css new file mode 100644 index 0000000..9484bf7 --- /dev/null +++ b/landing-page/style.css @@ -0,0 +1,444 @@ +/* Global Styles & Variables */ +:root { + --primary-color: #2a59fa; /* Zerion Blue */ + --primary-hover: #1f42cc; + --text-color: #1a1b1e; + --text-muted: #6b7280; + --bg-color: #ffffff; + --bg-light: #f9fafb; + --border-color: #e5e7eb; + + --font-stack: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + + --transition: all 0.2s ease-in-out; + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; + font-size: 16px; +} + +body { + font-family: var(--font-stack); + background-color: var(--bg-color); + color: var(--text-color); + line-height: 1.5; + -webkit-font-smoothing: antialiased; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 24px; +} + +.section-padding { + padding: 80px 0; +} + +.bg-light { + background-color: var(--bg-light); +} + +.text-center { + text-align: center; +} + +/* Typography */ +h1, h2, h3, h4, h5, h6 { + font-weight: 700; + letter-spacing: -0.02em; + line-height: 1.2; +} + +.section-title { + font-size: 2.25rem; + margin-bottom: 16px; + text-align: center; +} + +.section-subtitle { + font-size: 1.125rem; + color: var(--text-muted); + text-align: center; + max-width: 600px; + margin: 0 auto 48px; +} + +/* Buttons */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 10px 20px; + border-radius: 8px; + font-weight: 600; + text-decoration: none; + transition: var(--transition); + cursor: pointer; + border: none; + font-size: 0.95rem; +} + +.btn-large { + padding: 14px 28px; + font-size: 1.05rem; +} + +.btn-primary { + background-color: var(--primary-color); + color: white; +} + +.btn-primary:hover { + background-color: var(--primary-hover); + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(42, 89, 250, 0.3); +} + +.btn-secondary { + background-color: transparent; + color: var(--text-color); + border: 1px solid var(--border-color); +} + +.btn-secondary:hover { + background-color: var(--bg-light); +} + +/* Navbar */ +.navbar { + position: sticky; + top: 0; + background-color: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(8px); + border-bottom: 1px solid var(--border-color); + z-index: 100; + padding: 16px 0; +} + +.nav-container { + display: flex; + justify-content: space-between; + align-items: center; +} + +.logo { + display: flex; + align-items: center; + gap: 12px; + font-weight: 700; + font-size: 1.25rem; + color: var(--text-color); + text-decoration: none; +} + +.logo-mark { + width: 24px; + height: 24px; + background: linear-gradient(135deg, var(--primary-color), #8b5cf6); + border-radius: 6px; + display: inline-block; +} + +.nav-links { + display: flex; + align-items: center; + gap: 32px; +} + +.nav-links a:not(.btn) { + color: var(--text-muted); + text-decoration: none; + font-weight: 500; + transition: var(--transition); +} + +.nav-links a:not(.btn):hover { + color: var(--primary-color); +} + +/* Hero Section */ +.hero { + padding: 120px 0 80px; + text-align: center; + background: radial-gradient(circle at 50% 0%, rgba(42, 89, 250, 0.08) 0%, rgba(255, 255, 255, 0) 60%); +} + +.hero-content { + max-width: 800px; + margin: 0 auto; +} + +.hero-badge { + display: inline-block; + padding: 6px 14px; + background-color: rgba(42, 89, 250, 0.1); + color: var(--primary-color); + border-radius: 20px; + font-size: 0.875rem; + font-weight: 600; + margin-bottom: 24px; +} + +.hero-title { + font-size: 4rem; + margin-bottom: 24px; + letter-spacing: -0.03em; +} + +.highlight { + color: var(--primary-color); +} + +.hero-subtitle { + font-size: 1.25rem; + color: var(--text-muted); + margin-bottom: 40px; + max-width: 600px; + margin-left: auto; + margin-right: auto; +} + +.hero-actions { + display: flex; + gap: 16px; + justify-content: center; + margin-bottom: 64px; +} + +.terminal-mock { + background-color: #111827; + border-radius: 12px; + box-shadow: var(--shadow-lg); + overflow: hidden; + text-align: left; + max-width: 640px; + margin: 0 auto; + border: 1px solid rgba(255,255,255,0.1); +} + +.terminal-header { + background-color: #1f2937; + padding: 12px 16px; + display: flex; + gap: 8px; + border-bottom: 1px solid rgba(255,255,255,0.05); +} + +.dot { + width: 12px; + height: 12px; + border-radius: 50%; +} + +.dot.red { background-color: #ef4444; } +.dot.yellow { background-color: #f59e0b; } +.dot.green { background-color: #10b981; } + +.terminal-body { + padding: 24px; + color: #e5e7eb; + font-family: 'JetBrains Mono', 'Fira Code', monospace; + font-size: 0.9rem; + overflow-x: auto; +} + +.terminal-body pre { + margin: 0; +} + +.terminal-body code { + line-height: 1.6; +} + +/* Grids */ +.grid { + display: grid; + gap: 24px; +} + +.features-grid { + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); +} + +/* Feature Cards */ +.feature-card { + background: white; + border: 1px solid var(--border-color); + border-radius: 16px; + padding: 32px 24px; + transition: var(--transition); +} + +.feature-card:hover { + transform: translateY(-4px); + box-shadow: var(--shadow-md); + border-color: rgba(42, 89, 250, 0.3); +} + +.icon-wrapper { + width: 48px; + height: 48px; + background-color: rgba(42, 89, 250, 0.1); + color: var(--primary-color); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 20px; +} + +.feature-card h3 { + font-size: 1.25rem; + margin-bottom: 12px; +} + +.feature-card p { + color: var(--text-muted); + font-size: 0.95rem; +} + +/* FAQ Accordion */ +.accordion-container { + max-width: 800px; + margin: 0 auto; +} + +.accordion-item { + border: 1px solid var(--border-color); + border-radius: 8px; + margin-bottom: 16px; + background: white; + overflow: hidden; +} + +.accordion-header { + width: 100%; + padding: 20px 24px; + display: flex; + justify-content: space-between; + align-items: center; + background: none; + border: none; + font-size: 1.05rem; + font-weight: 600; + color: var(--text-color); + cursor: pointer; + text-align: left; + transition: var(--transition); +} + +.accordion-header:hover { + color: var(--primary-color); +} + +.accordion-header .icon { + font-size: 1.5rem; + font-weight: 300; + transition: transform 0.3s ease; +} + +.accordion-content { + max-height: 0; + overflow: hidden; + transition: max-height 0.3s ease-out; + background-color: white; +} + +.accordion-content p { + padding: 0 24px 24px; + color: var(--text-muted); +} + +.accordion-item.active .accordion-header .icon { + transform: rotate(45deg); +} + +/* Docs Links Cards */ +.docs-cta .grid { + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + max-width: 1000px; + margin: 0 auto; +} + +.doc-link-card { + display: block; + padding: 32px; + background: white; + border: 1px solid var(--border-color); + border-radius: 12px; + text-decoration: none; + color: var(--text-color); + transition: var(--transition); +} + +.doc-link-card:hover { + border-color: var(--primary-color); + box-shadow: var(--shadow-sm); +} + +.doc-link-card h4 { + font-size: 1.15rem; + margin-bottom: 8px; + color: var(--primary-color); +} + +.doc-link-card p { + color: var(--text-muted); + font-size: 0.95rem; +} + +/* Footer */ +footer { + border-top: 1px solid var(--border-color); + padding: 40px 0; + background-color: white; +} + +.footer-content { + display: flex; + justify-content: space-between; + align-items: center; +} + +.footer-brand { + display: flex; + align-items: center; + gap: 8px; +} + +.footer-copy { + color: var(--text-muted); + font-size: 0.875rem; +} + +/* Responsive */ +@media (max-width: 768px) { + .hero-title { + font-size: 2.5rem; + } + + .nav-links { + display: none; + } + + .hero-actions { + flex-direction: column; + } + + .footer-content { + flex-direction: column; + gap: 16px; + text-align: center; + } +} From 7c20092d3b168cd9535afdb106b6f3ffe45b91bd Mon Sep 17 00:00:00 2001 From: bran21 Date: Wed, 22 Apr 2026 17:36:53 +0700 Subject: [PATCH 3/6] telegram integration --- package-lock.json | 233 ++++++++++++++++++++++++++++++++++++---------- package.json | 3 + telegram-bot.js | 138 +++++++++++++++++++++++++++ 3 files changed, 327 insertions(+), 47 deletions(-) create mode 100644 telegram-bot.js diff --git a/package-lock.json b/package-lock.json index e28b664..b163877 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,10 @@ "@x402/evm": "^2.6.0", "@x402/fetch": "^2.6.0", "@x402/svm": "^2.8.0", + "dotenv": "^17.4.2", + "openai": "^6.34.0", "qrcode-terminal": "^0.12.0", + "telegraf": "^4.16.3", "viem": "^2.0.0" }, "bin": { @@ -1707,27 +1710,6 @@ "node": ">=20" } }, - "node_modules/@solana/rpc-subscriptions-channel-websocket/node_modules/ws": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/@solana/rpc-subscriptions-spec": { "version": "5.5.1", "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-spec/-/rpc-subscriptions-spec-5.5.1.tgz", @@ -2553,6 +2535,12 @@ "tslib": "^2.8.0" } }, + "node_modules/@telegraf/types": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@telegraf/types/-/types-7.1.0.tgz", + "integrity": "sha512-kGevOIbpMcIlCDeorKGpwZmdH7kHbqlk/Yj6dEpJMKEQw5lk0KVQY0OLXaCswy8GqlIVLd5625OB+rAntP9xVw==", + "license": "MIT" + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -2659,6 +2647,18 @@ } } }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/agentkeepalive": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", @@ -2750,6 +2750,28 @@ "ieee754": "^1.2.1" } }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "license": "MIT", + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "license": "MIT" + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", + "license": "MIT" + }, "node_modules/chalk": { "version": "5.6.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", @@ -2771,6 +2793,23 @@ "node": ">=20" } }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/delay": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", @@ -2783,6 +2822,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/es6-promise": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", @@ -2798,6 +2849,15 @@ "es6-promise": "^4.0.3" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/eventemitter3": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", @@ -2903,12 +2963,42 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "license": "MIT" }, + "node_modules/jayson/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "license": "ISC" }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2935,6 +3025,27 @@ } } }, + "node_modules/openai": { + "version": "6.34.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.34.0.tgz", + "integrity": "sha512-yEr2jdGf4tVFYG6ohmr3pF6VJuveP0EA/sS8TBx+4Eq5NT10alu5zg2dmxMXMgqpihRDQlFGpRt2XwsGj+Fyxw==", + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/ox": { "version": "0.14.7", "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.7.tgz", @@ -2986,6 +3097,15 @@ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "license": "MIT" }, + "node_modules/p-timeout": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-4.1.0.tgz", + "integrity": "sha512-+/wmHtzJuWii1sXn3HCuH/FTwGhrp4tmJTxSKJbfS+vkipci6osxXM5mY0jUiRzWKMTgUT8l7HFbeSwZAynqHw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/qrcode-terminal": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", @@ -3039,27 +3159,6 @@ "uuid": "dist/esm/bin/uuid" } }, - "node_modules/rpc-websockets/node_modules/ws": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -3080,6 +3179,24 @@ ], "license": "MIT" }, + "node_modules/safe-compare": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-compare/-/safe-compare-1.1.4.tgz", + "integrity": "sha512-b9wZ986HHCo/HbKrRpBJb2kqXMK9CEWIE1egeEvZsYn69ay3kdfl9nG3RyOcR+jInTDf7a86WQ1d4VJX7goSSQ==", + "license": "MIT", + "dependencies": { + "buffer-alloc": "^1.2.0" + } + }, + "node_modules/sandwich-stream": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/sandwich-stream/-/sandwich-stream-2.0.2.tgz", + "integrity": "sha512-jLYV0DORrzY3xaz/S9ydJL6Iz7essZeAfnAavsJ+zsJGZ1MOnsS52yRjU3uF3pJa/lla7+wisp//fxOwOH8SKQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/stream-chain": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", @@ -3104,6 +3221,28 @@ "node": ">=14.0.0" } }, + "node_modules/telegraf": { + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/telegraf/-/telegraf-4.16.3.tgz", + "integrity": "sha512-yjEu2NwkHlXu0OARWoNhJlIjX09dRktiMQFsM678BAH/PEPVwctzL67+tvXqLCRQQvm3SDtki2saGO9hLlz68w==", + "license": "MIT", + "dependencies": { + "@telegraf/types": "^7.1.0", + "abort-controller": "^3.0.0", + "debug": "^4.3.4", + "mri": "^1.2.0", + "node-fetch": "^2.7.0", + "p-timeout": "^4.1.0", + "safe-compare": "^1.1.4", + "sandwich-stream": "^2.0.2" + }, + "bin": { + "telegraf": "lib/cli.mjs" + }, + "engines": { + "node": "^12.20.0 || >=14.13.1" + } + }, "node_modules/text-encoding-utf-8": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", @@ -3233,17 +3372,17 @@ } }, "node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", "license": "MIT", "peer": true, "engines": { - "node": ">=8.3.0" + "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { diff --git a/package.json b/package.json index 8db73da..51492a2 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,10 @@ "@x402/evm": "^2.6.0", "@x402/fetch": "^2.6.0", "@x402/svm": "^2.8.0", + "dotenv": "^17.4.2", + "openai": "^6.34.0", "qrcode-terminal": "^0.12.0", + "telegraf": "^4.16.3", "viem": "^2.0.0" } } diff --git a/telegram-bot.js b/telegram-bot.js new file mode 100644 index 0000000..dddd6ef --- /dev/null +++ b/telegram-bot.js @@ -0,0 +1,138 @@ +import 'dotenv/config'; +import { Telegraf } from 'telegraf'; +import OpenAI from 'openai'; +import { exec } from 'child_process'; +import { promisify } from 'util'; + +const execAsync = promisify(exec); + +// Safety check +if (!process.env.TELEGRAM_BOT_TOKEN || !process.env.OPENROUTER_API_KEY) { + console.error("❌ Missing TELEGRAM_BOT_TOKEN or OPENROUTER_API_KEY in your .env file"); + process.exit(1); +} + +const bot = new Telegraf(process.env.TELEGRAM_BOT_TOKEN); +const openai = new OpenAI({ + baseURL: 'https://openrouter.ai/api/v1', + apiKey: process.env.OPENROUTER_API_KEY, + defaultHeaders: { + "HTTP-Referer": "http://localhost:3000", + "X-Title": "Zerion Agent Bot", + } +}); + +// Tell OpenAI about our CLI tool +const tools = [ + { + type: 'function', + function: { + name: 'execute_zerion_cli', + description: 'Run the zerion CLI to evaluate wallets, tokens, swap, bridge, or analyze PnL.', + parameters: { + type: 'object', + properties: { + command: { + type: 'string', + description: 'The exact zerion CLI command to run. IMPORTANT: ALWAYS prefix with "npx"! (e.g., "npx zerion portfolio
", "npx zerion swap ETH USDC 0.001 --wallet main --chain base").' + } + }, + required: ['command'] + } + } + } +]; + +// Listen for the /zerion command in Telegram +bot.command('zerion', async (ctx) => { + const userQuery = ctx.message.text.replace('/zerion', '').trim(); + + if (!userQuery) { + return ctx.reply('Please provide a prompt.\nExample: /zerion Swap 0.001 ETH to USDC on base'); + } + + const loadingMsg = await ctx.reply('🤖 Agent is thinking...'); + + try { + // Define the "Brain" or "Training" of your agent using a System Prompt + const conversationHistory = [ + { + role: 'system', + content: `You are an expert Web3 trading assistant. Your job is to translate human requests into accurate 'zerion' CLI commands. +Rule 1: If a user says "buy X", use "npx zerion swap X" +Rule 2: "What is my balance?" -> "npx zerion portfolio main" +Rule 3: Always add "--wallet main" to trading commands unless specified otherwise. +Rule 4: SCOPED POLICY ENFORCEMENT - You are chain-locked to 'base'. You must add '--chain base' to all swap/send commands. Do NOT execute swaps larger than 0.1 ETH. Expire any trading session state after 24 hours.` + }, + { role: 'user', content: userQuery } + ]; + + // 1. Give the prompt directly to OpenAI + const response = await openai.chat.completions.create({ + model: process.env.OPENROUTER_MODEL || 'openai/gpt-4o', + messages: conversationHistory, + tools: tools + }); + + const completion = response.choices[0].message; + + // 2. See if OpenAI wants to use the CLI tool automatically + if (completion.tool_calls) { + const toolCall = completion.tool_calls[0]; + const args = JSON.parse(toolCall.function.arguments); + + console.log(`Executing Agent CLI: ${args.command}`); + + // We send the command to the bash terminal + const { stdout, stderr } = await execAsync(args.command); + const executionResult = stdout || stderr; + + console.log(`Result: ${executionResult.substring(0, 50)}...`); + + // 3. Send the result back to OpenAI so it can summarize it normally + const finalResponse = await openai.chat.completions.create({ + model: process.env.OPENROUTER_MODEL || 'openai/gpt-4o', + messages: [ + ...conversationHistory, + completion, + { + role: 'tool', + tool_call_id: toolCall.id, + name: 'execute_zerion_cli', + content: executionResult + } + ] + }); + + await ctx.telegram.editMessageText( + ctx.chat.id, + loadingMsg.message_id, + undefined, + finalResponse.choices[0].message.content + ); + } else { + // If no tool was used, just reply with the raw GPT text + await ctx.telegram.editMessageText( + ctx.chat.id, + loadingMsg.message_id, + undefined, + completion.content + ); + } + } catch (error) { + console.error("Bot Error: ", error.message); + await ctx.telegram.editMessageText( + ctx.chat.id, + loadingMsg.message_id, + undefined, + '❌ An error occurred during execution! Check the console.' + ); + } +}); + +bot.launch(); + +console.log("⚡ Telegram Bot is online! Open Telegram and type: /zerion "); + +process.once('SIGINT', () => bot.stop('SIGINT')); +process.once('SIGTERM', () => bot.stop('SIGTERM')); From 4907fb4807bdfaa36f8331d7be0d3dbdc9aa8dfc Mon Sep 17 00:00:00 2001 From: bran21 Date: Thu, 7 May 2026 11:11:44 +0700 Subject: [PATCH 4/6] fixing and readjusting --- .agents/skills/chains/SKILL.md | 1 - cli/commands/analytics/overview.js | 3 +- cli/commands/apikey.js | 66 +++++++++++++++++++++ cli/lib/api/client.js | 8 ++- cli/lib/util/format.js | 95 ++++++++++++++++++++++++++++++ cli/router.js | 3 + cli/zerion.js | 2 + tests/skills.test.mjs | 11 +++- 8 files changed, 184 insertions(+), 5 deletions(-) create mode 100644 cli/commands/apikey.js diff --git a/.agents/skills/chains/SKILL.md b/.agents/skills/chains/SKILL.md index cccbedc..76bd750 100644 --- a/.agents/skills/chains/SKILL.md +++ b/.agents/skills/chains/SKILL.md @@ -60,7 +60,6 @@ These are the chain IDs currently accepted by the wallet commands in this repo: | Solana | `solana` | | Zora | `zora` | | Blast | `blast` | - `zerion chains` may return a broader catalog. For `positions`, `history`, and `analyze`, use the IDs above unless the CLI validator is expanded. ## Using with analysis commands diff --git a/cli/commands/analytics/overview.js b/cli/commands/analytics/overview.js index 495297b..d9fc544 100644 --- a/cli/commands/analytics/overview.js +++ b/cli/commands/analytics/overview.js @@ -6,6 +6,7 @@ import { fetchAPI } from "../../lib/api/client.js"; import { summarizeAnalyze } from "../../lib/util/analyze.js"; import { print, printError } from "../../lib/util/output.js"; +import { formatOverview } from "../../lib/util/format.js"; import { isX402Enabled } from "../../lib/api/x402.js"; import { resolveAddressOrWallet } from "../../lib/wallet/resolve.js"; import { validateChain } from "../../lib/util/validate.js"; @@ -50,7 +51,7 @@ export default async function walletAnalyze(args, flags) { if (failures.length) summary.failures = failures; if (useX402) summary.auth = "x402"; - print(summary); + print(summary, formatOverview); } catch (err) { printError(err.code || "analyze_error", err.message); process.exit(1); diff --git a/cli/commands/apikey.js b/cli/commands/apikey.js new file mode 100644 index 0000000..27a926f --- /dev/null +++ b/cli/commands/apikey.js @@ -0,0 +1,66 @@ +/** + * zerion apikey — quick shortcut for viewing / setting the Zerion API key. + * + * Usage: + * zerion apikey → show current key (redacted) + * zerion apikey set → save key to config + * zerion apikey unset → remove saved key from config + */ + +import { getApiKey, getConfigValue, setConfigValue, unsetConfigValue } from "../lib/config.js"; +import { print, printError } from "../lib/util/output.js"; + +function redact(val) { + if (!val) return null; + return val.length > 8 ? val.slice(0, 8) + "..." : "***"; +} + +export default async function apikeyCmd(args, flags) { + const [action, ...valueParts] = args; + + // No action → show current key + source + if (!action) { + const envKey = process.env.ZERION_API_KEY || null; + const configKey = getConfigValue("apiKey") || null; + const active = envKey || configKey; + print({ + apiKey: redact(active), + source: envKey ? "ZERION_API_KEY env var" : configKey ? "config file" : null, + hint: active + ? null + : "Set via: zerion apikey set OR export ZERION_API_KEY=", + }); + return; + } + + if (action === "set") { + const key = valueParts.join(" ").trim() || flags.key; + if (!key) { + printError("missing_value", "Usage: zerion apikey set ", { + hint: "Get your key at https://developers.zerion.io", + }); + process.exit(1); + } + setConfigValue("apiKey", key); + print({ apiKey: redact(key), updated: true }); + return; + } + + if (action === "unset" || action === "remove" || action === "delete") { + unsetConfigValue("apiKey"); + print({ apiKey: null, removed: true }); + return; + } + + // If the first arg looks like a key value, treat it as `apikey set ` + if (action.startsWith("zk_") || action.length > 20) { + setConfigValue("apiKey", action); + print({ apiKey: redact(action), updated: true }); + return; + } + + printError("invalid_action", "Usage: zerion apikey [set | unset]", { + hint: "Run 'zerion apikey' to see current key", + }); + process.exit(1); +} diff --git a/cli/lib/api/client.js b/cli/lib/api/client.js index 87aa4da..f77d73c 100644 --- a/cli/lib/api/client.js +++ b/cli/lib/api/client.js @@ -10,7 +10,7 @@ export function basicAuthHeader(key) { return `Basic ${Buffer.from(`${key}:`).toString("base64")}`; } -export async function fetchAPI(pathname, params = {}, useX402 = false) { +export async function fetchAPI(pathname, params = {}, useX402 = false, isRetry = false) { const apiKey = useX402 ? null : getApiKey(); if (!useX402 && !apiKey) { const err = new Error( @@ -48,6 +48,12 @@ export async function fetchAPI(pathname, params = {}, useX402 = false) { } if (!response.ok) { + // Hybrid fallback: If rate-limited on standard API and we have a wallet key, fallback to x402 + if (response.status === 429 && !useX402 && !isRetry && process.env.WALLET_PRIVATE_KEY) { + process.stderr.write(`\\x1b[33m⚠ API rate limit hit (429). Falling back to x402 pay-per-call for ${pathname}...\\x1b[0m\\n`); + return fetchAPI(pathname, params, true, true); + } + const err = new Error( `Zerion API error: ${response.status} ${response.statusText}` ); diff --git a/cli/lib/util/format.js b/cli/lib/util/format.js index 77604ea..a86b7a8 100644 --- a/cli/lib/util/format.js +++ b/cli/lib/util/format.js @@ -231,3 +231,98 @@ export function formatPnl(data) { if (p.totalFees != null) lines.push(` Fees Paid: ${usd(p.totalFees)}`); return lines.join("\n"); } + +export function formatOverview(data) { + const walletLabel = data.label || data.wallet?.query || "Unknown"; + const addr = data.wallet?.query || ""; + const shortAddr = addr.length > 12 ? `${addr.slice(0, 6)}...${addr.slice(-4)}` : addr; + const lines = []; + + // Header + lines.push(`${BOLD}━━━ Wallet Analysis ━━━${RESET}`); + lines.push(` ${BOLD}${walletLabel}${RESET} ${DIM}${shortAddr}${RESET}`); + lines.push(""); + + // Portfolio + if (data.portfolio) { + const total = data.portfolio.total; + lines.push(` ${BOLD}💰 Portfolio${RESET} ${BOLD}${usd(total)}${RESET}`); + const ch = data.portfolio.change_1d; + if (ch) { + const absChange = usd(ch.absolute_1d); + const pctChange = pct(ch.percent_1d); + lines.push(` ${DIM}24h:${RESET} ${pctChange} (${absChange})`); + } + + // Top chains by value + if (data.portfolio.chains) { + const chainEntries = Object.entries(data.portfolio.chains) + .sort((a, b) => b[1] - a[1]) + .slice(0, 5); + if (chainEntries.length > 0) { + lines.push(""); + lines.push(` ${DIM}Top chains:${RESET}`); + for (const [chain, val] of chainEntries) { + lines.push(` ${pad(chain, 22)} ${padStart(usd(val), 14)}`); + } + } + } + lines.push(""); + } + + // Top Positions + if (data.positions && data.positions.count > 0) { + lines.push(` ${BOLD}📊 Top Positions${RESET} (${data.positions.count} total)`); + lines.push(` ${DIM}${pad("Token", 20)} ${pad("Chain", 14)} ${padStart("Value", 14)} ${padStart("Amount", 16)}${RESET}`); + lines.push(` ${DIM}${"─".repeat(66)}${RESET}`); + for (const p of data.positions.top) { + const sym = p.symbol ? `${p.name} (${p.symbol})` : p.name; + const qty = p.quantity != null ? p.quantity.toFixed(4) : "-"; + lines.push(` ${pad(sym, 20)} ${pad(p.chain || "?", 14)} ${padStart(usd(p.value), 14)} ${padStart(qty, 16)}`); + } + lines.push(""); + } else { + lines.push(` ${DIM}📊 No positions data${RESET}`); + lines.push(""); + } + + // Recent Transactions + if (data.transactions && data.transactions.sampled > 0) { + lines.push(` ${BOLD}📝 Recent Transactions${RESET} (${data.transactions.sampled} sampled)`); + for (const tx of data.transactions.recent) { + const status = tx.status === "confirmed" ? `${GREEN}✓${RESET}` : tx.status === "pending" ? `${YELLOW}⏳${RESET}` : `${DIM}${tx.status || "?"}${RESET}`; + const type = tx.operation_type || "unknown"; + const time = tx.mined_at ? new Date(tx.mined_at * 1000).toISOString().replace("T", " ").slice(0, 16) : "?"; + lines.push(` ${status} ${DIM}${time}${RESET} ${type}`); + for (const t of tx.transfers || []) { + const dir = t.direction === "in" ? `${GREEN}+${RESET}` : `${RED}-${RESET}`; + const name = t.fungible_info?.symbol || t.fungible_info?.name || "?"; + lines.push(` ${dir} ${t.quantity || "?"} ${name} ${DIM}${usd(t.value)}${RESET}`); + } + } + lines.push(""); + } else { + lines.push(` ${DIM}📝 No recent transactions${RESET}`); + lines.push(""); + } + + // PnL + if (data.pnl && data.pnl.available && data.pnl.summary) { + const s = data.pnl.summary; + lines.push(` ${BOLD}📈 PnL${RESET}`); + if (s.total_gain != null) lines.push(` Total Gain: ${usd(s.total_gain)}`); + if (s.realized_gain != null) lines.push(` Realized: ${usd(s.realized_gain)}`); + if (s.unrealized_gain != null) lines.push(` Unrealized: ${usd(s.unrealized_gain)}`); + } else { + lines.push(` ${DIM}📈 PnL not available${RESET}`); + } + + // Failures + if (data.failures && data.failures.length > 0) { + lines.push(""); + lines.push(` ${YELLOW}⚠ Some data unavailable: ${data.failures.join(", ")}${RESET}`); + } + + return lines.join("\n"); +} + diff --git a/cli/router.js b/cli/router.js index fb528fe..3a0989a 100644 --- a/cli/router.js +++ b/cli/router.js @@ -68,6 +68,9 @@ function printUsage() { }, other: { "chains": "List supported chains", + "apikey": "Show current API key (redacted) and its source", + "apikey set ": "Save API key to config", + "apikey unset": "Remove saved API key from config", "config set ": "Set config (apiKey, defaultWallet, defaultChain, slippage)", "config unset ": "Remove a config value (resets to default)", "config list": "Show current configuration", diff --git a/cli/zerion.js b/cli/zerion.js index 67b2a7f..0862272 100755 --- a/cli/zerion.js +++ b/cli/zerion.js @@ -86,7 +86,9 @@ register("agent", "delete-policy", agentDeletePolicy); // --- Config --- import configCmd from "./commands/config.js"; +import apikeyCmd from "./commands/apikey.js"; registerSingle("config", configCmd); +registerSingle("apikey", apikeyCmd); // --- Dispatch --- diff --git a/tests/skills.test.mjs b/tests/skills.test.mjs index 2f23877..73038f3 100644 --- a/tests/skills.test.mjs +++ b/tests/skills.test.mjs @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import { describe, it } from "node:test"; -import { readFileSync, readdirSync, existsSync } from "node:fs"; +import { readFileSync, readdirSync, existsSync, statSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { dirname, join } from "node:path"; @@ -30,7 +30,14 @@ function parseSkillFrontmatter(content) { describe("skills directory", () => { it("has all expected skill directories", () => { const dirs = readdirSync(skillsDir, { withFileTypes: true }) - .filter((d) => d.isDirectory()) + .filter((d) => { + // Follow symlinks: isDirectory() returns false for symlinks + if (d.isDirectory()) return true; + if (d.isSymbolicLink()) { + try { return statSync(join(skillsDir, d.name)).isDirectory(); } catch { return false; } + } + return false; + }) .map((d) => d.name) .sort(); for (const name of EXPECTED_SKILLS) { From 49062160e4d200557da3bd741faebe429a2556c1 Mon Sep 17 00:00:00 2001 From: bran21 Date: Thu, 7 May 2026 11:53:33 +0700 Subject: [PATCH 5/6] feat: integrate x402 fallback and telegram bot start welcome message --- .agents/skills/wallet-analysis/SKILL.md | 2 +- .agents/skills/zerion/SKILL.md | 4 ++-- cli/lib/api/client.js | 6 +++--- package.json | 3 ++- telegram-bot.js | 16 +++++++++++++++- 5 files changed, 23 insertions(+), 8 deletions(-) diff --git a/.agents/skills/wallet-analysis/SKILL.md b/.agents/skills/wallet-analysis/SKILL.md index 7df43ee..65fe770 100644 --- a/.agents/skills/wallet-analysis/SKILL.md +++ b/.agents/skills/wallet-analysis/SKILL.md @@ -138,7 +138,7 @@ Use `zerion chains` to inspect the broader chain catalog, but stick to the IDs a - **`missing_api_key`**: Set `ZERION_API_KEY` or add `--x402` flag - **`unsupported_chain`**: Run `zerion chains` to check valid chain IDs - **Empty positions/transactions**: Wallet may be inactive or very new -- **`api_error` with status 429**: Rate limited -- wait or switch to x402 +- **`api_error` with status 401/403/429**: API key limit reached. The CLI will automatically fallback to x402 if `WALLET_PRIVATE_KEY` is present. Otherwise, wait or append the `--x402` flag. - **ENS name fails**: Retry with the resolved 0x address if upstream name resolution is unavailable For worked examples, see [EXAMPLES.md](EXAMPLES.md). diff --git a/.agents/skills/zerion/SKILL.md b/.agents/skills/zerion/SKILL.md index dec8cc1..c3f4564 100644 --- a/.agents/skills/zerion/SKILL.md +++ b/.agents/skills/zerion/SKILL.md @@ -287,8 +287,8 @@ Wallets are encrypted with AES-256-GCM via the Open Wallet Standard (OWS) vault | `wallet_not_found` | Wallet name doesn't exist in OWS vault | Run `zerion wallet list` to check | | `unsupported_chain` | Invalid `--chain` value | Run `zerion chains` for valid IDs | | `unsupported_positions_filter` | Invalid `--positions` value | Use `all`, `simple`, or `defi` | -| `api_error` status 401 | Invalid API key | Check key at dashboard.zerion.io | -| `api_error` status 429 | Rate limited | Wait, reduce frequency, or use x402 | +| `api_error` status 401/403 | Invalid/Overused key | Check key at dashboard.zerion.io or use x402 (auto-fallback if WALLET_PRIVATE_KEY is set) | +| `api_error` status 429 | Rate limited | Wait, reduce frequency, or use x402 (auto-fallback if WALLET_PRIVATE_KEY is set) | | `api_error` status 400 | Invalid address or ENS failed | Retry with 0x hex address | | `unexpected_error` | `WALLET_PRIVATE_KEY` missing in x402 | Export the private key or disable x402 | | `unexpected_error` | Node.js < 20 | Upgrade Node.js | diff --git a/cli/lib/api/client.js b/cli/lib/api/client.js index f77d73c..e4239e3 100644 --- a/cli/lib/api/client.js +++ b/cli/lib/api/client.js @@ -48,9 +48,9 @@ export async function fetchAPI(pathname, params = {}, useX402 = false, isRetry = } if (!response.ok) { - // Hybrid fallback: If rate-limited on standard API and we have a wallet key, fallback to x402 - if (response.status === 429 && !useX402 && !isRetry && process.env.WALLET_PRIVATE_KEY) { - process.stderr.write(`\\x1b[33m⚠ API rate limit hit (429). Falling back to x402 pay-per-call for ${pathname}...\\x1b[0m\\n`); + // Hybrid fallback: If rate-limited or quota exceeded on standard API and we have a wallet key, fallback to x402 + if ([401, 403, 429].includes(response.status) && !useX402 && !isRetry && process.env.WALLET_PRIVATE_KEY) { + process.stderr.write(`\\x1b[33m⚠ API error (${response.status}). Falling back to x402 pay-per-call for ${pathname}...\\x1b[0m\\n`); return fetchAPI(pathname, params, true, true); } diff --git a/package.json b/package.json index 51492a2..b3fa068 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "test": "node --test tests/*.test.mjs", "test:unit": "node --test tests/unit.test.mjs tests/tool-catalog.test.mjs tests/examples.test.mjs tests/consistency.test.mjs", "test:integration": "node --test tests/integration.test.mjs", - "prepublishOnly": "npm test" + "prepublishOnly": "npm test", + "start:telegram": "node telegram-bot.js" }, "engines": { "node": ">=20" diff --git a/telegram-bot.js b/telegram-bot.js index dddd6ef..a462d9d 100644 --- a/telegram-bot.js +++ b/telegram-bot.js @@ -62,7 +62,8 @@ bot.command('zerion', async (ctx) => { Rule 1: If a user says "buy X", use "npx zerion swap X" Rule 2: "What is my balance?" -> "npx zerion portfolio main" Rule 3: Always add "--wallet main" to trading commands unless specified otherwise. -Rule 4: SCOPED POLICY ENFORCEMENT - You are chain-locked to 'base'. You must add '--chain base' to all swap/send commands. Do NOT execute swaps larger than 0.1 ETH. Expire any trading session state after 24 hours.` +Rule 4: SCOPED POLICY ENFORCEMENT - You are chain-locked to 'base'. You must add '--chain base' to all swap/send commands. Do NOT execute swaps larger than 0.1 ETH. Expire any trading session state after 24 hours. +Rule 5: If the CLI returns an API error (e.g. rate limit, overused API key, 401, 403, 429), retry the exact same command but append the "--x402" flag to use pay-per-call.` }, { role: 'user', content: userQuery } ]; @@ -130,6 +131,19 @@ Rule 4: SCOPED POLICY ENFORCEMENT - You are chain-locked to 'base'. You must add } }); +// Welcome message for new users +bot.start((ctx) => { + ctx.reply( + "👋 Welcome to the Zerion AI Trading Bot!\n\n" + + "I am your expert Web3 trading assistant. I can analyze wallets, track portfolios, and execute autonomous trades across multiple chains.\n\n" + + "To interact with me, just use the /zerion command followed by your prompt.\n\n" + + "For example:\n" + + "• /zerion What is the portfolio of vitalik.eth?\n" + + "• /zerion Swap 0.001 ETH to USDC on base\n\n" + + "Let's get trading! 🚀" + ); +}); + bot.launch(); console.log("⚡ Telegram Bot is online! Open Telegram and type: /zerion "); From 2dad379027f4e86e32d23215fa053c6647a150bc Mon Sep 17 00:00:00 2001 From: bran21 Date: Thu, 7 May 2026 12:03:34 +0700 Subject: [PATCH 6/6] agent training --- telegram-bot.js | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/telegram-bot.js b/telegram-bot.js index a462d9d..6c033cd 100644 --- a/telegram-bot.js +++ b/telegram-bot.js @@ -59,7 +59,7 @@ bot.command('zerion', async (ctx) => { { role: 'system', content: `You are an expert Web3 trading assistant. Your job is to translate human requests into accurate 'zerion' CLI commands. -Rule 1: If a user says "buy X", use "npx zerion swap X" +Rule 1: If a user says "swap X to Y" or "sell X for Y", use "npx zerion swap X Y ". If they say "buy X", use "npx zerion swap USDC X " (assume USDC is the default funding token). Rule 2: "What is my balance?" -> "npx zerion portfolio main" Rule 3: Always add "--wallet main" to trading commands unless specified otherwise. Rule 4: SCOPED POLICY ENFORCEMENT - You are chain-locked to 'base'. You must add '--chain base' to all swap/send commands. Do NOT execute swaps larger than 0.1 ETH. Expire any trading session state after 24 hours. @@ -140,10 +140,32 @@ bot.start((ctx) => { "For example:\n" + "• /zerion What is the portfolio of vitalik.eth?\n" + "• /zerion Swap 0.001 ETH to USDC on base\n\n" + + "Send /tutorial to see a full list of my skills and commands.\n\n" + "Let's get trading! 🚀" ); }); +// Tutorial command listing skills and commands +bot.command('tutorial', (ctx) => { + ctx.reply( + "📚 *Zerion AI Bot Tutorial*\n\n" + + "I am powered by the Zerion CLI and have two main skills:\n\n" + + "🔍 *1. Wallet Analysis*\n" + + "Use me to track portfolios, check token positions, and see PnL.\n" + + "• `/zerion analyze
` - Full wallet breakdown\n" + + "• `/zerion portfolio
` - Total value & chain breakdown\n" + + "• `/zerion history
` - Recent transactions\n\n" + + "💱 *2. Autonomous Trading*\n" + + "Use me to execute swaps and cross-chain bridges automatically.\n" + + "• `/zerion Swap 0.01 ETH to USDC on base`\n" + + "• `/zerion Bridge 100 USDC to arbitrum`\n\n" + + "💡 *Tips:*\n" + + "• Always use the `/zerion` prefix for prompts that require AI reasoning.\n" + + "• If I hit an API rate limit, I will automatically try to use my x402 fallback logic.", + { parse_mode: 'Markdown' } + ); +}); + bot.launch(); console.log("⚡ Telegram Bot is online! Open Telegram and type: /zerion ");