├── .env.sample ├── .eslintrc.json ├── .gitignore ├── README.md ├── app ├── Spinner.tsx ├── favicon.ico ├── globals.css ├── layout.tsx └── page.tsx ├── lib └── dynamic.js ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── background-pattern.svg ├── logo-full.svg ├── logo.png ├── next.svg └── vercel.svg ├── scripts └── bot.ts ├── tailwind.config.ts └── tsconfig.json /.env.sample: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_DYNAMIC_ENV_ID=YOUR_DYNAMIC_ENV_ID 2 | LOGIN_URL=YOUR_WEBSITE_URL 3 | TELEGRAM_BOT_TOKEN=YOUR_BOT_TOKEN -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # env 28 | .env 29 | # local env files 30 | .env*.local 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | 39 | .idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Telegram Mini App + Dynamic connect 2 | 3 | Steps to have the Telegram Mini App (TMA) + Bot running 4 | 5 | 1. Create a Bot on Telegram using Botfather, [link to tutorial](https://core.telegram.org/bots/tutorial#getting-ready) 6 | 2. Clone this repo, run `cp .env.sample .env` and use your own Dynamic environment ID by replacing `NEXT_PUBLIC_DYNAMIC_ENV_ID` in the `.env` file 7 | 3. Deploy your website online. [link to tutorial](https://vercel.com/docs/deployments/git#deploying-a-git-repository) 8 | 4. Using Botfather, add the website url that should be opened for your TMA. [link to tutorial](https://docs.ton.org/develop/dapps/telegram-apps/step-by-step-guide#3-set-up-bot-mini-app) 9 | 5. Use Bot `TOKEN` from Telegram and your website url as LOGIN_URL in the `.env` file. 10 | 6. Run telegram bot `ts-node scripts/bot.ts`. If you do not have `ts-node` you can install it by running `npm -g i ts-node` 11 | 7. Go to your Telegram Bot and type `/start` 12 | 13 | [Build Around the Booming Telegram Ecosystem](https://www.dynamic.xyz/ecosystems/telegram) 14 | -------------------------------------------------------------------------------- /app/Spinner.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Spinner = () => { 4 | return ( 5 |
6 |
7 |
8 | ); 9 | }; 10 | 11 | export default Spinner; 12 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dynamic-labs/telegram-miniapp-dynamic/89104ff1e5b3ccbcf4b70cabeef7826e2d824d32/app/favicon.ico -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | body { 20 | color: rgb(var(--foreground-rgb)); 21 | background: linear-gradient( 22 | to bottom, 23 | transparent, 24 | rgb(var(--background-end-rgb)) 25 | ) 26 | rgb(var(--background-start-rgb)); 27 | } 28 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "./globals.css"; 2 | import type { Metadata } from "next"; 3 | import { Inter } from "next/font/google"; 4 | import { 5 | DynamicContextProvider, 6 | EthereumWalletConnectors, 7 | SolanaWalletConnectors, 8 | } from "../lib/dynamic"; 9 | 10 | import { GlobalWalletExtension } from "@dynamic-labs/global-wallet"; 11 | 12 | const inter = Inter({ subsets: ["latin"] }); 13 | 14 | export const metadata: Metadata = { 15 | title: "Create Next App", 16 | description: "Generated by create next app", 17 | }; 18 | const dynamicEnvId = process.env.NEXT_PUBLIC_DYNAMIC_ENV_ID; 19 | 20 | export default function RootLayout({ 21 | children, 22 | }: { 23 | children: React.ReactNode; 24 | }) { 25 | if (!dynamicEnvId) { 26 | const errMsg = 27 | "Please add your Dynamic Environment to this project's .env file"; 28 | console.error(errMsg); 29 | throw new Error(errMsg); 30 | } 31 | return ( 32 | 33 | 40 | {children} 41 | 42 | 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useEffect, useState } from "react"; 3 | import { 4 | DynamicWidget, 5 | useTelegramLogin, 6 | useDynamicContext, 7 | } from "../lib/dynamic"; 8 | 9 | import Spinner from "./Spinner"; 10 | 11 | export default function Main() { 12 | const { sdkHasLoaded, user } = useDynamicContext(); 13 | const { telegramSignIn } = useTelegramLogin(); 14 | const [isLoading, setIsLoading] = useState(true); 15 | 16 | useEffect(() => { 17 | if (!sdkHasLoaded) return; 18 | 19 | const signIn = async () => { 20 | if (!user) { 21 | await telegramSignIn({ forceCreateUser: true }); 22 | } 23 | setIsLoading(false); 24 | }; 25 | 26 | signIn(); 27 | }, [sdkHasLoaded]); 28 | 29 | return ( 30 |
31 |
32 |
33 |
34 | logo 35 |
36 |
37 | 38 |
39 |

You got an auto-wallet!

40 |
41 | {isLoading ? : } 42 |
43 |

44 | Zero clicks, one multi-chain wallet. We automatically created an embedded wallet for you. 45 |

46 |

How This works

47 |
    48 |
  • We utilize the Telegram authentication token
  • 49 |
  • Token is verified and used to create the end user wallet
  • 50 |
  • The same wallet is accessible on desktop and mobile platforms
  • 51 |
  • If the end user logs in with Telegram later on your site, your wallet is still available
  • 52 |
53 | 54 | Learn More in Our Docs 55 | 56 |
57 |
58 |
59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /lib/dynamic.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | export * from "@dynamic-labs/ethereum"; 4 | export * from "@dynamic-labs/sdk-react-core"; 5 | export * from "@dynamic-labs/solana"; 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dynamic-labs/nextjs-viem", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@dynamic-labs/ethereum": "3.6.2", 13 | "@dynamic-labs/sdk-react-core": "3.6.2", 14 | "@dynamic-labs/solana": "3.6.2", 15 | "@dynamic-labs/global-wallet": "3.6.2", 16 | "@types/node": "20.5.4", 17 | "@types/react": "18.2.21", 18 | "@types/react-dom": "18.2.7", 19 | "autoprefixer": "10.4.15", 20 | "dotenv": "^16.4.5", 21 | "eslint": "8.47.0", 22 | "eslint-config-next": "13.4.19", 23 | "jsonwebtoken": "^9.0.2", 24 | "next": "13.4.19", 25 | "postcss": "8.4.28", 26 | "react": "18.2.0", 27 | "react-dom": "18.2.0", 28 | "tailwindcss": "3.3.3", 29 | "telegraf": "^4.16.3", 30 | "typescript": "5.1.6", 31 | "viem": "^2.7.6" 32 | }, 33 | "resolutions": { 34 | "viem": "^2.7.6", 35 | "@solana/web3.js": "1.91.6", 36 | "rpc-websockets": "7.10.0" 37 | }, 38 | "devDependencies": { 39 | "@types/jsonwebtoken": "^9.0.6", 40 | "tailwindcss": "^3.4.3" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/background-pattern.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/logo-full.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dynamic-labs/telegram-miniapp-dynamic/89104ff1e5b3ccbcf4b70cabeef7826e2d824d32/public/logo.png -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/bot.ts: -------------------------------------------------------------------------------- 1 | const { Telegraf } = require("telegraf"); 2 | const jwt = require("jsonwebtoken"); 3 | const nodeCrypto = require("crypto"); 4 | require('dotenv').config(); 5 | 6 | // Environment variables 7 | const TOKEN = process.env.TELEGRAM_BOT_TOKEN; 8 | const LOGIN_URL = process.env.LOGIN_URL; 9 | 10 | if (!TOKEN || !LOGIN_URL) { 11 | console.error( 12 | "Please add your Telegram bot token and app URL to the .env file" 13 | ); 14 | process.exit(1); 15 | } 16 | 17 | // Initialize the bot 18 | const bot = new Telegraf(TOKEN); 19 | 20 | /** 21 | * Start command handling for the bot 22 | */ 23 | bot.start((ctx: any) => { 24 | // Extract user data from the context 25 | const userData = { 26 | authDate: Math.floor(new Date().getTime()), 27 | firstName: ctx.update.message.from.first_name, 28 | lastName: "", 29 | username: ctx.update.message.from.username, 30 | id: ctx.update.message.from.id, 31 | photoURL: "", 32 | }; 33 | 34 | // Generate the hash for Telegram authentication 35 | const hash = generateTelegramHash(userData); 36 | 37 | // Create JWT with user data and hash 38 | const telegramAuthToken = jwt.sign( 39 | { 40 | ...userData, 41 | hash, 42 | }, 43 | TOKEN, // Use the bot token to sign the JWT 44 | { algorithm: "HS256" } 45 | ); 46 | console.log("[DEBUG] JWT generated for user", userData); 47 | 48 | // URL-encode the generated JWT for safe usage in a URL 49 | const encodedTelegramAuthToken = encodeURIComponent(telegramAuthToken); 50 | 51 | // Create the inline keyboard with the Mini Web App button 52 | const keyboard = { 53 | reply_markup: { 54 | inline_keyboard: [ 55 | [ 56 | { 57 | text: "Open Mini Web App 🚀", 58 | web_app: { 59 | url: `${LOGIN_URL}/?telegramAuthToken=${encodedTelegramAuthToken}`, 60 | }, 61 | }, 62 | ], 63 | ], 64 | }, 65 | }; 66 | 67 | // Send a welcome message with the inline keyboard 68 | ctx.reply("Welcome to XYZ Mini Web App", keyboard); 69 | }); 70 | 71 | // Launch the bot 72 | bot.launch(); 73 | console.log('[DEBUG] Bot script connected...'); 74 | 75 | /** 76 | * Function to generate HMAC hash for Telegram authentication 77 | * @param {Object} data - User data to be hashed 78 | * @returns {string} - Generated HMAC hash 79 | */ 80 | const generateTelegramHash = (data: any) => { 81 | // Prepare the data object with required fields 82 | const useData = { 83 | auth_date: String(data.authDate), 84 | first_name: data.firstName, 85 | id: String(data.id), 86 | last_name: data.lastName, 87 | photo_url: data.photoURL, 88 | username: data.username, 89 | }; 90 | 91 | // Filter out undefined or empty values from the data object 92 | const filteredUseData = Object.entries(useData).reduce( 93 | (acc: { [key: string]: any }, [key, value]) => { 94 | if (value) acc[key] = value; 95 | return acc; 96 | }, 97 | {} as { [key: string]: any } 98 | ); 99 | 100 | // Sort the entries and create the data check string 101 | const dataCheckArr = Object.entries(filteredUseData) 102 | .map(([key, value]) => `${key}=${String(value)}`) 103 | .sort((a, b) => a.localeCompare(b)) 104 | .join("\n"); 105 | 106 | // Create SHA-256 hash from the bot token 107 | const TELEGRAM_SECRET = nodeCrypto 108 | .createHash("sha256") 109 | .update(TOKEN) 110 | .digest(); 111 | 112 | // Generate HMAC-SHA256 hash from the data check string 113 | return nodeCrypto 114 | .createHmac("sha256", TELEGRAM_SECRET) 115 | .update(dataCheckArr) 116 | .digest("hex"); 117 | }; 118 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'tailwindcss' 2 | 3 | const config: Config = { 4 | content: [ 5 | './pages/**/*.{js,ts,jsx,tsx,mdx}', 6 | './components/**/*.{js,ts,jsx,tsx,mdx}', 7 | './app/**/*.{js,ts,jsx,tsx,mdx}', 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 13 | 'gradient-conic': 14 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | } 20 | export default config 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | --------------------------------------------------------------------------------