├── .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 |
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 |

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 |
11 |
--------------------------------------------------------------------------------
/public/logo-full.svg:
--------------------------------------------------------------------------------
1 |
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 |
--------------------------------------------------------------------------------