Added advanced telegram bot examples (#193)
# Pull Request Description ## Related Issue Fixes #192 ## Changes Made This PR adds the following changes: Now we have the below detailed examples in the tg bot examples: 1. **Basic TG Bot**: This guide explains how to run a simple Solana Agent Kit on a Telegram bot for a single user. 2. **Advanced TG Bot**: This guide includes advanced features such as: - Storing chat history for each user in a PostgreSQL database. - Maintaining a unique wallet for each user in a Firebase database. - Managing the chats of multiple users simultaneously. 3. **Group TG Bot**: This example demonstrates how to create a Telegram bot using the Solana Agent Kit by Send AI. It includes advanced features such as: - Storing chat history for each user in a PostgreSQL database. - Maintaining a unique wallet for each user in a Firebase database. - This special bot can be run in Telegram groups and handle private conversations, such as wallet addresses, in private chats. It works in groups, maintains a separate context for each user, responds to each user by tagging them, and can handle multiple requests simultaneously. It will only reply if the bot is tagged in the group or its message is replied to. ## Checklist - [ x ] I have tested these changes locally
@@ -1,24 +1,13 @@
|
||||
# Telegram Bot Starter with Solana Agent Kit
|
||||
We have three guides on how you can host Solana Agent Kit as a Telegram bot:
|
||||
|
||||
This example showcases how we can make a telegram bot with the Solana Agent Kit by Send AI.
|
||||
1. **Basic TG Bot**: This guide explains how to run a simple Solana Agent Kit on a Telegram bot for a single user.
|
||||
|
||||
## Quick Deploy
|
||||
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fsendaifun%2Fsolana-agent-kit%2Ftree%2Fmain%2Fexamples%2Ftg-bot-starter&env=OPENAI_API_KEY,RPC_URL,SOLANA_PRIVATE_KEY,TELEGRAM_BOT_TOKEN&project-name=solana-agent-kit&repository-name=sak-yourprojectname)
|
||||
|
||||
## How to get the telegram bot token
|
||||
|
||||
You can check [here](https://help.zoho.com/portal/en/kb/desk/support-channels/instant-messaging/telegram/articles/telegram-integration-with-zoho-desk#How_to_find_a_token_for_an_existing_Telegram_Bot) how you can obtain a bot token for your telegram bot.
|
||||
|
||||
## How to setup the project
|
||||
|
||||
- Set env variables
|
||||
- Run ``` pnpm install ```
|
||||
- Run ``` pnpm run dev ```
|
||||
- Run ``` ngrok http 3000 ```
|
||||
- With the URL you got from ngrok, where your bot is hosted at https://yourUrl.app/api/bot
|
||||
- Set the webhook by using this command ``` curl https://api.telegram.org/bot<telegram_bot_token>/setWebhook?url=https://<your-deployment-url>.app/api/bot ``` or simply clicking on that link.
|
||||
- You can host it on Vercel too as we have used NextJs in this.
|
||||
- Once the URL is set successfully, you will see this ``` {"ok":true,"result":true,"description":"Webhook was set"} ```
|
||||
|
||||
Done!!! Congratulations you just hosted Solana Agent Kit on a Telegram bot.
|
||||
2. **Advanced TG Bot**: This guide includes advanced features such as:
|
||||
- Storing chat history for each user in a PostgreSQL database.
|
||||
- Maintaining a unique wallet for each user in a Firebase database.
|
||||
- Managing the chats of multiple users simultaneously.
|
||||
|
||||
3. **Group TG Bot**: This example demonstrates how to create a Telegram bot using the Solana Agent Kit by Send AI. It includes advanced features such as:
|
||||
- Storing chat history for each user in a PostgreSQL database.
|
||||
- Maintaining a unique wallet for each user in a Firebase database.
|
||||
- This special bot can be run in Telegram groups and handle private conversations, such as wallet addresses, in private chats. It works in groups, maintains a separate context for each user, responds to each user by tagging them, and can handle multiple requests simultaneously. It will only reply if the bot is tagged in the group or its message is replied to.
|
||||
10
examples/tg-bot-starter/advanced-tg-bot/.env.example
Normal file
@@ -0,0 +1,10 @@
|
||||
OPENAI_API_KEY=
|
||||
RPC_URL=
|
||||
TELEGRAM_BOT_TOKEN=
|
||||
NEXT_PUBLIC_FIREBASE_API_KEY=
|
||||
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=
|
||||
NEXT_PUBLIC_FIREBASE_PROJECT_ID=
|
||||
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=
|
||||
NEXT_PUBLIC_FIREBASE_APP_ID=
|
||||
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=
|
||||
POSTGRES_LINK=
|
||||
24
examples/tg-bot-starter/advanced-tg-bot/README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Telegram Bot Starter with Solana Agent Kit
|
||||
|
||||
This example demonstrates how to create a Telegram bot using the Solana Agent Kit by Send AI. It includes advanced features such as storing chat history for each user in a PostgreSQL database, maintaining a unique wallet for each user in a Firebase database, and managing the state of multiple users simultaneously, each with their own unique wallet.
|
||||
|
||||
## Quick Deploy
|
||||
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fsendaifun%2Fsolana-agent-kit%2Ftree%2Fmain%2Fexamples%2Ftg-bot-starter&env=OPENAI_API_KEY,RPC_URL,SOLANA_PRIVATE_KEY,TELEGRAM_BOT_TOKEN&project-name=solana-agent-kit&repository-name=sak-yourprojectname)
|
||||
|
||||
## How to get the telegram bot token
|
||||
|
||||
You can check [here](https://help.zoho.com/portal/en/kb/desk/support-channels/instant-messaging/telegram/articles/telegram-integration-with-zoho-desk#How_to_find_a_token_for_an_existing_Telegram_Bot) how you can obtain a bot token for your telegram bot.
|
||||
|
||||
## How to setup the project
|
||||
|
||||
- Set env variables
|
||||
- Run ``` pnpm install ```
|
||||
- Run ``` pnpm run dev ```
|
||||
- Run ``` ngrok http 3000 ```
|
||||
- With the URL you got from ngrok, where your bot is hosted at https://yourUrl.app/api/bot
|
||||
- Set the webhook by using this command ``` curl https://api.telegram.org/bot<telegram_bot_token>/setWebhook?url=https://<your-deployment-url>.app/api/bot ``` or simply clicking on that link.
|
||||
- You can host it on Vercel too as we have used NextJs in this.
|
||||
- Once the URL is set successfully, you will see this ``` {"ok":true,"result":true,"description":"Webhook was set"} ```
|
||||
|
||||
Done!!! Congratulations you just hosted Solana Agent Kit on a Telegram bot.
|
||||
|
||||
33
examples/tg-bot-starter/advanced-tg-bot/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "tg-bot-starter",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@langchain/core": "^0.3.26",
|
||||
"@langchain/langgraph": "^0.2.36",
|
||||
"@langchain/langgraph-checkpoint-postgres": "^0.0.2",
|
||||
"@langchain/openai": "^0.3.16",
|
||||
"firebase": "^11.1.0",
|
||||
"grammy": "^1.33.0",
|
||||
"messages": "link:@langchain/core/messages",
|
||||
"next": "15.1.3",
|
||||
"prebuilt": "link:@langchain/langgraph/prebuilt",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"solana-agent-kit": "^1.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
5178
examples/tg-bot-starter/advanced-tg-bot/pnpm-lock.yaml
generated
Normal file
|
Before Width: | Height: | Size: 391 B After Width: | Height: | Size: 391 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 128 B After Width: | Height: | Size: 128 B |
|
Before Width: | Height: | Size: 385 B After Width: | Height: | Size: 385 B |
157
examples/tg-bot-starter/advanced-tg-bot/src/app/api/bot/route.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
export const dynamic = 'force-dynamic';
|
||||
export const fetchCache = 'force-no-store';
|
||||
export const maxDuration = 60; // can use 300 with vercel premium
|
||||
|
||||
import { Bot, webhookCallback } from 'grammy';
|
||||
import { SolanaAgentKit, createSolanaTools } from "solana-agent-kit";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { HumanMessage } from "@langchain/core/messages";
|
||||
import { getApps, initializeApp, getApp } from 'firebase/app';
|
||||
import { getFirestore, doc, getDoc, setDoc, updateDoc } from 'firebase/firestore';
|
||||
import { bs58 } from '@coral-xyz/anchor/dist/cjs/utils/bytes';
|
||||
import { Keypair } from '@solana/web3.js';
|
||||
import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres";
|
||||
|
||||
const token = process.env.TELEGRAM_BOT_TOKEN;
|
||||
if (!token) throw new Error('TELEGRAM_BOT_TOKEN environment variable not found.');
|
||||
const bot = new Bot(token);
|
||||
|
||||
const checkpointer = PostgresSaver.fromConnString(
|
||||
process.env.POSTGRES_LINK!,
|
||||
);
|
||||
|
||||
const firebaseConfig = {
|
||||
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
|
||||
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
|
||||
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
|
||||
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
|
||||
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
|
||||
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
|
||||
measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
|
||||
};
|
||||
const app = !getApps.length ? initializeApp(firebaseConfig) : getApp();
|
||||
const db = getFirestore(app);
|
||||
|
||||
async function getOrCreateUserKeyPair(userId: string) {
|
||||
const userDocRef = doc(db, 'users', userId);
|
||||
const userDocSnap = await getDoc(userDocRef);
|
||||
if (userDocSnap.exists()) {
|
||||
return userDocSnap.data();
|
||||
}
|
||||
const keypair = Keypair.generate();
|
||||
const keypairData = {
|
||||
publicKey: keypair.publicKey.toString(),
|
||||
privateKey: String(bs58.encode(keypair.secretKey)),
|
||||
inProgress: false,
|
||||
inGame: false,
|
||||
};
|
||||
await setDoc(userDocRef, keypairData);
|
||||
return keypairData;
|
||||
}
|
||||
|
||||
async function initializeAgent(userId: string, keyPair: any) {
|
||||
try {
|
||||
const llm = new ChatOpenAI({
|
||||
modelName: "gpt-4o-mini",
|
||||
temperature: 0.7,
|
||||
});
|
||||
|
||||
const solanaKit = new SolanaAgentKit(
|
||||
keyPair.privateKey,
|
||||
process.env.RPC_URL,
|
||||
process.env.OPENAI_API_KEY!
|
||||
);
|
||||
|
||||
const tools = createSolanaTools(solanaKit);
|
||||
await checkpointer.setup();
|
||||
const config = { configurable: { thread_id: userId } };
|
||||
const agent = createReactAgent({
|
||||
llm,
|
||||
tools,
|
||||
checkpointSaver: checkpointer,
|
||||
messageModifier: `
|
||||
You are a helpful Telegram bot agent that can interact onchain using the Solana Agent Kit. You are
|
||||
empowered to interact onchain using your tools. If you ever need funds, you can request them from the user at ${keyPair.publicKey}.
|
||||
If user asks for his funds back, you can send them their priate key ${keyPair.privateKey}.
|
||||
If there is a 5XX (internal) HTTP error code, ask the user to try again later. If someone asks you to do something you
|
||||
can't do with your currently available tools, you must say so, and encourage them to implement it
|
||||
themselves using the Solana Agent Kit, recommend they go to https://www.solanaagentkit.xyz for more information. Be
|
||||
concise and helpful with your responses. Refrain from restating your tools' descriptions unless it is explicitly requested.
|
||||
`,
|
||||
});
|
||||
return { agent, config };
|
||||
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize agent:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
// Telegram bot handler
|
||||
bot.on('message:text', async (ctx:any) => {
|
||||
const userId = ctx.from?.id.toString();
|
||||
if (!userId) return;
|
||||
const userDocRef = doc(db, 'users', userId);
|
||||
const userDocSnap = await getDoc(userDocRef);
|
||||
|
||||
if (!userDocSnap.exists()) {
|
||||
const codeDocRef = doc(db, 'inviteCodes', ctx.message.text);
|
||||
const codeDocSnap = await getDoc(codeDocRef);
|
||||
if (!codeDocSnap.exists()) {
|
||||
await ctx.reply(`Invalid invite code. Please try again.`);
|
||||
return;
|
||||
}
|
||||
const data = await getDoc(codeDocRef);
|
||||
const codeData = data.data();
|
||||
if (codeData?.usedBy != null) {
|
||||
await ctx.reply(`Invite code has already been used. Please try again.`);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
await updateDoc(codeDocRef, { usedBy: userId });
|
||||
const keyPair = await getOrCreateUserKeyPair(userId);
|
||||
await ctx.reply(`Looks like you are using the Game agent first time. You can fund your agent and start playing. Your unique Solana wallet is:`);
|
||||
await ctx.reply(`${String(keyPair.publicKey)}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const keyPair = await getOrCreateUserKeyPair(userId);
|
||||
if (keyPair.inProgress) {
|
||||
await ctx.reply(`Hold on! I'm still processing...`);
|
||||
return;
|
||||
}
|
||||
const { agent, config } = await initializeAgent(userId, keyPair);
|
||||
const stream = await agent.stream({ messages: [new HumanMessage(ctx.message.text)] }, config);
|
||||
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 20000));
|
||||
try {
|
||||
await updateDoc(userDocRef, { inProgress: true });
|
||||
for await (const chunk of await Promise.race([stream, timeoutPromise]) as AsyncIterable<{ agent?: any; tools?: any }>) {
|
||||
if ("agent" in chunk) {
|
||||
if (chunk.agent.messages[0].content) await ctx.reply(String(chunk.agent.messages[0].content));
|
||||
}
|
||||
// Add the below if you want to show direct output from the tools.
|
||||
// else if ("tools" in chunk) {
|
||||
// if (chunk.tools.messages[0].content) await ctx.reply(String(chunk.tools.messages[0].content));
|
||||
// }
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.message === 'Timeout') {
|
||||
await ctx.reply("I'm sorry, the operation took too long and timed out. Please try again.");
|
||||
} else {
|
||||
console.error("Error processing stream:", error);
|
||||
await ctx.reply("I'm sorry, an error occurred while processing your request.");
|
||||
await updateDoc(userDocRef, { inProgress: false });
|
||||
}
|
||||
}
|
||||
finally {
|
||||
await updateDoc(userDocRef, { inProgress: false });
|
||||
}
|
||||
});
|
||||
|
||||
// Export webhook handler
|
||||
export const POST = async (req: Request) => {
|
||||
const headers = new Headers();
|
||||
headers.set('x-vercel-background', 'true');
|
||||
const handler = webhookCallback(bot, 'std/http');
|
||||
return handler(req);
|
||||
};
|
||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
41
examples/tg-bot-starter/basic-tg-bot/.gitignore
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
24
examples/tg-bot-starter/basic-tg-bot/README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Telegram Bot Starter with Solana Agent Kit
|
||||
|
||||
This example showcases how we can make a telegram bot with the Solana Agent Kit by Send AI.
|
||||
|
||||
## Quick Deploy
|
||||
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fsendaifun%2Fsolana-agent-kit%2Ftree%2Fmain%2Fexamples%2Ftg-bot-starter&env=OPENAI_API_KEY,RPC_URL,SOLANA_PRIVATE_KEY,TELEGRAM_BOT_TOKEN&project-name=solana-agent-kit&repository-name=sak-yourprojectname)
|
||||
|
||||
## How to get the telegram bot token
|
||||
|
||||
You can check [here](https://help.zoho.com/portal/en/kb/desk/support-channels/instant-messaging/telegram/articles/telegram-integration-with-zoho-desk#How_to_find_a_token_for_an_existing_Telegram_Bot) how you can obtain a bot token for your telegram bot.
|
||||
|
||||
## How to setup the project
|
||||
|
||||
- Set env variables
|
||||
- Run ``` pnpm install ```
|
||||
- Run ``` pnpm run dev ```
|
||||
- Run ``` ngrok http 3000 ```
|
||||
- With the URL you got from ngrok, where your bot is hosted at https://yourUrl.app/api/bot
|
||||
- Set the webhook by using this command ``` curl https://api.telegram.org/bot<telegram_bot_token>/setWebhook?url=https://<your-deployment-url>.app/api/bot ``` or simply clicking on that link.
|
||||
- You can host it on Vercel too as we have used NextJs in this.
|
||||
- Once the URL is set successfully, you will see this ``` {"ok":true,"result":true,"description":"Webhook was set"} ```
|
||||
|
||||
Done!!! Congratulations you just hosted Solana Agent Kit on a Telegram bot.
|
||||
|
||||
7
examples/tg-bot-starter/basic-tg-bot/next.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
8
examples/tg-bot-starter/basic-tg-bot/postcss.config.mjs
Normal file
@@ -0,0 +1,8 @@
|
||||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
1
examples/tg-bot-starter/basic-tg-bot/public/file.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 391 B |
1
examples/tg-bot-starter/basic-tg-bot/public/globe.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
1
examples/tg-bot-starter/basic-tg-bot/public/next.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
examples/tg-bot-starter/basic-tg-bot/public/vercel.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 128 B |
1
examples/tg-bot-starter/basic-tg-bot/public/window.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
||||
|
After Width: | Height: | Size: 385 B |
@@ -1,6 +1,6 @@
|
||||
export const dynamic = "force-dynamic";
|
||||
export const fetchCache = "force-no-store";
|
||||
export const maxDuration = 300;
|
||||
export const maxDuration = 60; // can use 300 with vercel premium
|
||||
|
||||
import { Bot, webhookCallback } from "grammy";
|
||||
import { SolanaAgentKit, createSolanaTools } from "solana-agent-kit";
|
||||
BIN
examples/tg-bot-starter/basic-tg-bot/src/app/favicon.ico
Normal file
|
After Width: | Height: | Size: 25 KiB |
21
examples/tg-bot-starter/basic-tg-bot/src/app/globals.css
Normal file
@@ -0,0 +1,21 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--foreground);
|
||||
background: var(--background);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
34
examples/tg-bot-starter/basic-tg-bot/src/app/layout.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
101
examples/tg-bot-starter/basic-tg-bot/src/app/page.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import Image from "next/image";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
||||
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={180}
|
||||
height={38}
|
||||
priority
|
||||
/>
|
||||
<ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
|
||||
<li className="mb-2">
|
||||
Get started by editing{" "}
|
||||
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
|
||||
src/app/page.tsx
|
||||
</code>
|
||||
.
|
||||
</li>
|
||||
<li>Save and see your changes instantly.</li>
|
||||
</ol>
|
||||
|
||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
||||
<a
|
||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
Deploy now
|
||||
</a>
|
||||
<a
|
||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Read our docs
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/file.svg"
|
||||
alt="File icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/window.svg"
|
||||
alt="Window icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Examples
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/globe.svg"
|
||||
alt="Globe icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Go to nextjs.org →
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
18
examples/tg-bot-starter/basic-tg-bot/tailwind.config.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { Config } from "tailwindcss";
|
||||
|
||||
export default {
|
||||
content: [
|
||||
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
background: "var(--background)",
|
||||
foreground: "var(--foreground)",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
} satisfies Config;
|
||||
27
examples/tg-bot-starter/basic-tg-bot/tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
10
examples/tg-bot-starter/group-tg-bot/.env.example
Normal file
@@ -0,0 +1,10 @@
|
||||
OPENAI_API_KEY=
|
||||
RPC_URL=
|
||||
TELEGRAM_BOT_TOKEN=
|
||||
NEXT_PUBLIC_FIREBASE_API_KEY=
|
||||
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=
|
||||
NEXT_PUBLIC_FIREBASE_PROJECT_ID=
|
||||
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=
|
||||
NEXT_PUBLIC_FIREBASE_APP_ID=
|
||||
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=
|
||||
POSTGRES_LINK=
|
||||
41
examples/tg-bot-starter/group-tg-bot/.gitignore
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
30
examples/tg-bot-starter/group-tg-bot/README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Telegram Bot Starter with Solana Agent Kit
|
||||
|
||||
This example demonstrates how to create a Telegram bot using the Solana Agent Kit by Send AI. It includes advanced features such as:
|
||||
|
||||
- Storing chat history for each user in a PostgreSQL database.
|
||||
- Maintaining a unique wallet for each user in a Firebase database.
|
||||
- Managing the state of multiple users simultaneously, each with their own unique wallet.
|
||||
|
||||
This special bot can be run on Telegram groups and handle private conversations, such as wallet addresses, in private chats. It works in groups, maintains a separate context for each user, responds to each user by tagging them, and can handle multiple requests simultaneously. It will only reply if the bot is tagged in group or its message is replied to.
|
||||
|
||||
## Quick Deploy
|
||||
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fsendaifun%2Fsolana-agent-kit%2Ftree%2Fmain%2Fexamples%2Ftg-bot-starter&env=OPENAI_API_KEY,RPC_URL,SOLANA_PRIVATE_KEY,TELEGRAM_BOT_TOKEN&project-name=solana-agent-kit&repository-name=sak-yourprojectname)
|
||||
|
||||
## How to get the telegram bot token
|
||||
|
||||
You can check [here](https://help.zoho.com/portal/en/kb/desk/support-channels/instant-messaging/telegram/articles/telegram-integration-with-zoho-desk#How_to_find_a_token_for_an_existing_Telegram_Bot) how you can obtain a bot token for your telegram bot.
|
||||
|
||||
## How to setup the project
|
||||
|
||||
- Set env variables
|
||||
- Run ``` pnpm install ```
|
||||
- Run ``` pnpm run dev ```
|
||||
- Run ``` ngrok http 3000 ```
|
||||
- With the URL you got from ngrok, where your bot is hosted at https://yourUrl.app/api/bot
|
||||
- Set the webhook by using this command ``` curl https://api.telegram.org/bot<telegram_bot_token>/setWebhook?url=https://<your-deployment-url>.app/api/bot ``` or simply clicking on that link.
|
||||
- You can host it on Vercel too as we have used NextJs in this.
|
||||
- Once the URL is set successfully, you will see this ``` {"ok":true,"result":true,"description":"Webhook was set"} ```
|
||||
|
||||
Done!!! Congratulations you just hosted Solana Agent Kit on a Telegram bot.
|
||||
|
||||
7
examples/tg-bot-starter/group-tg-bot/next.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
33
examples/tg-bot-starter/group-tg-bot/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "tg-bot-starter",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@langchain/core": "^0.3.26",
|
||||
"@langchain/langgraph": "^0.2.36",
|
||||
"@langchain/langgraph-checkpoint-postgres": "^0.0.2",
|
||||
"@langchain/openai": "^0.3.16",
|
||||
"firebase": "^11.1.0",
|
||||
"grammy": "^1.33.0",
|
||||
"messages": "link:@langchain/core/messages",
|
||||
"next": "15.1.3",
|
||||
"prebuilt": "link:@langchain/langgraph/prebuilt",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"solana-agent-kit": "^1.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
5178
examples/tg-bot-starter/group-tg-bot/pnpm-lock.yaml
generated
Normal file
8
examples/tg-bot-starter/group-tg-bot/postcss.config.mjs
Normal file
@@ -0,0 +1,8 @@
|
||||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
1
examples/tg-bot-starter/group-tg-bot/public/file.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 391 B |
1
examples/tg-bot-starter/group-tg-bot/public/globe.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
1
examples/tg-bot-starter/group-tg-bot/public/next.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
examples/tg-bot-starter/group-tg-bot/public/vercel.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 128 B |
1
examples/tg-bot-starter/group-tg-bot/public/window.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
||||
|
After Width: | Height: | Size: 385 B |
160
examples/tg-bot-starter/group-tg-bot/src/app/api/bot/route.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
export const dynamic = 'force-dynamic';
|
||||
export const fetchCache = 'force-no-store';
|
||||
export const maxDuration = 60; // can use 300 with vercel premium
|
||||
|
||||
import { Bot, webhookCallback } from 'grammy';
|
||||
import { SolanaAgentKit, createSolanaTools } from "solana-agent-kit";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { HumanMessage } from "@langchain/core/messages";
|
||||
import { getApps, initializeApp, getApp } from 'firebase/app';
|
||||
import { getFirestore, doc, getDoc, setDoc, updateDoc } from 'firebase/firestore';
|
||||
import { bs58 } from '@coral-xyz/anchor/dist/cjs/utils/bytes';
|
||||
import { Keypair } from '@solana/web3.js';
|
||||
import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres";
|
||||
|
||||
const token = process.env.TELEGRAM_BOT_TOKEN;
|
||||
if (!token) throw new Error('TELEGRAM_BOT_TOKEN environment variable not found.');
|
||||
const bot = new Bot(token);
|
||||
|
||||
const checkpointer = PostgresSaver.fromConnString(
|
||||
process.env.POSTGRES_LINK!,
|
||||
);
|
||||
|
||||
const firebaseConfig = {
|
||||
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
|
||||
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
|
||||
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
|
||||
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
|
||||
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
|
||||
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
|
||||
measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
|
||||
};
|
||||
const app = !getApps.length ? initializeApp(firebaseConfig) : getApp();
|
||||
const db = getFirestore(app);
|
||||
|
||||
async function getOrCreateUserKeyPair(userId: string) {
|
||||
const userDocRef = doc(db, 'gusers', userId);
|
||||
const userDocSnap = await getDoc(userDocRef);
|
||||
if (userDocSnap.exists()) {
|
||||
return userDocSnap.data();
|
||||
}
|
||||
const keypair = Keypair.generate();
|
||||
const keypairData = {
|
||||
publicKey: keypair.publicKey.toString(),
|
||||
privateKey: String(bs58.encode(keypair.secretKey)),
|
||||
inProgress: false,
|
||||
inGame: false,
|
||||
};
|
||||
await setDoc(userDocRef, keypairData);
|
||||
return keypairData;
|
||||
}
|
||||
|
||||
async function initializeAgent(userId: string, keyPair: any) {
|
||||
try {
|
||||
const llm = new ChatOpenAI({
|
||||
modelName: "gpt-4o-mini",
|
||||
temperature: 0.7,
|
||||
});
|
||||
|
||||
const solanaKit = new SolanaAgentKit(
|
||||
keyPair.privateKey,
|
||||
process.env.RPC_URL,
|
||||
process.env.OPENAI_API_KEY!
|
||||
);
|
||||
|
||||
const tools = createSolanaTools(solanaKit);
|
||||
await checkpointer.setup();
|
||||
const config = { configurable: { thread_id: userId } };
|
||||
const agent = createReactAgent({
|
||||
llm,
|
||||
tools,
|
||||
checkpointSaver: checkpointer,
|
||||
messageModifier: `
|
||||
Ignore the @ tag to your bot id.
|
||||
You are a helpful Telegram bot agent that can interact onchain using the Solana Agent Kit. You are
|
||||
empowered to interact onchain using your tools. If you ever need funds, you can request them from the user by asking them to message you privately.
|
||||
If there is a 5XX (internal) HTTP error code, ask the user to try again later. If someone asks you to do something you
|
||||
can't do with your currently available tools, you must say so, and encourage them to implement it
|
||||
themselves using the Solana Agent Kit, recommend they go to https://www.solanaagentkit.xyz for more information. Be
|
||||
concise and helpful with your responses. Refrain from restating your tools' descriptions unless it is explicitly requested.
|
||||
`,
|
||||
});
|
||||
return { agent, config };
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize agent:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
// Telegram bot handler
|
||||
bot.on('message:text', async (ctx) => {
|
||||
const userId = ctx.from?.id.toString();
|
||||
if (!userId) return;
|
||||
const botUsername = `@${ctx.me.username}`;
|
||||
if (!(ctx.chat.type === 'private')) {
|
||||
if (!(ctx.message.text && ctx.message.text.includes(botUsername)) && !(ctx.message.reply_to_message !== undefined && ctx.message.reply_to_message.from?.username === ctx.me.username)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const userDocRef = doc(db, 'gusers', userId);
|
||||
const userDocSnap = await getDoc(userDocRef);
|
||||
if (!userDocSnap.exists() && ctx.chat.type == 'private') {
|
||||
const keyPair = await getOrCreateUserKeyPair(userId);
|
||||
await ctx.reply(`Looks like you are using the agent first time. You can fund your agent and start using. Your unique Solana wallet is:`);
|
||||
await ctx.reply(`${String(keyPair.publicKey)}`);
|
||||
await ctx.reply("Your private key is:");
|
||||
await ctx.reply(`${String(keyPair.privateKey)}`);
|
||||
return;
|
||||
}
|
||||
else if (userDocSnap.exists() && ctx.chat.type == 'private') {
|
||||
const keyPair = await getOrCreateUserKeyPair(userId);
|
||||
await ctx.reply("Looks like you already have a wallet. Your public key is:");
|
||||
await ctx.reply(`${String(keyPair.publicKey)}`);
|
||||
await ctx.reply("Your private key is:");
|
||||
await ctx.reply(`${String(keyPair.privateKey)}`);
|
||||
return;
|
||||
}
|
||||
else if (!userDocSnap.exists()) {
|
||||
await ctx.reply("Looks like you haven't got a wallet yet. Please start a private chat with me to get your wallet.", { reply_to_message_id: ctx.message.message_id });
|
||||
return;
|
||||
}
|
||||
const keyPair = await getOrCreateUserKeyPair(userId);
|
||||
if (keyPair.inProgress) {
|
||||
await ctx.reply(`Hold on! I'm still processing...`, { reply_to_message_id: ctx.message.message_id });
|
||||
return;
|
||||
}
|
||||
const { agent, config } = await initializeAgent(userId, keyPair);
|
||||
const stream = await agent.stream({ messages: [new HumanMessage(ctx.message.text)] }, config);
|
||||
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 20000));
|
||||
try {
|
||||
await updateDoc(userDocRef, { inProgress: true });
|
||||
for await (const chunk of await Promise.race([stream, timeoutPromise]) as AsyncIterable<{ agent?: any; tools?: any }>) {
|
||||
if ("agent" in chunk) {
|
||||
if (chunk.agent.messages[0].content) await ctx.reply(String(chunk.agent.messages[0].content), { reply_to_message_id: ctx.message.message_id });
|
||||
}
|
||||
// Add the below if you want to show direct output from the tools.
|
||||
// else if ("tools" in chunk) {
|
||||
// if (chunk.tools.messages[0].content) await ctx.reply(String(chunk.tools.messages[0].content));
|
||||
// }
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.message === 'Timeout') {
|
||||
await ctx.reply("I'm sorry, the operation took too long and timed out. Please try again.", { reply_to_message_id: ctx.message.message_id });
|
||||
} else {
|
||||
console.error("Error processing stream:", error);
|
||||
await ctx.reply("I'm sorry, an error occurred while processing your request.", { reply_to_message_id: ctx.message.message_id });
|
||||
await updateDoc(userDocRef, { inProgress: false });
|
||||
}
|
||||
}
|
||||
finally {
|
||||
await updateDoc(userDocRef, { inProgress: false });
|
||||
}
|
||||
});
|
||||
|
||||
// Export webhook handler
|
||||
export const POST = async (req: Request) => {
|
||||
const headers = new Headers();
|
||||
headers.set('x-vercel-background', 'true');
|
||||
const handler = webhookCallback(bot, 'std/http');
|
||||
return handler(req);
|
||||
};
|
||||
BIN
examples/tg-bot-starter/group-tg-bot/src/app/favicon.ico
Normal file
|
After Width: | Height: | Size: 25 KiB |
21
examples/tg-bot-starter/group-tg-bot/src/app/globals.css
Normal file
@@ -0,0 +1,21 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--foreground);
|
||||
background: var(--background);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
34
examples/tg-bot-starter/group-tg-bot/src/app/layout.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
101
examples/tg-bot-starter/group-tg-bot/src/app/page.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import Image from "next/image";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
||||
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={180}
|
||||
height={38}
|
||||
priority
|
||||
/>
|
||||
<ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
|
||||
<li className="mb-2">
|
||||
Get started by editing{" "}
|
||||
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
|
||||
src/app/page.tsx
|
||||
</code>
|
||||
.
|
||||
</li>
|
||||
<li>Save and see your changes instantly.</li>
|
||||
</ol>
|
||||
|
||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
||||
<a
|
||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
Deploy now
|
||||
</a>
|
||||
<a
|
||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Read our docs
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/file.svg"
|
||||
alt="File icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/window.svg"
|
||||
alt="Window icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Examples
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/globe.svg"
|
||||
alt="Globe icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Go to nextjs.org →
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
18
examples/tg-bot-starter/group-tg-bot/tailwind.config.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { Config } from "tailwindcss";
|
||||
|
||||
export default {
|
||||
content: [
|
||||
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
background: "var(--background)",
|
||||
foreground: "var(--foreground)",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
} satisfies Config;
|
||||
27
examples/tg-bot-starter/group-tg-bot/tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||