Telegram Mini App Development for Crypto Wallet
Telegram Mini App (TMA) is a web application embedded in Telegram via WebView. Available to 900+ million users without separate app installation. For crypto wallet this radically changes onboarding: instead of "download app → create seed phrase → write 12 words → don't lose it" — just open bot in Telegram.
But TMA wallet has strict limitations: no file system access, localStorage is unreliable (Telegram can clear it anytime), WebView on iOS is limited in crypto API, and most importantly — storing seed phrase in TMA is unsafe. This determines architectural choice: either custodial, or MPC, or embedded wallet via Account Abstraction.
Technical Platform
TON vs EVM
Two main options for TMA wallet:
TON (The Open Network) — native integration with Telegram. TON Space (Telegram's built-in wallet) already uses TON. TonConnect protocol — standard for dApp connections in TON ecosystem. SDK: @tonconnect/sdk, @ton/core. TON audience is already in Telegram, DeFi ecosystem growing fast.
EVM via Abstract Wallet. Wallet for Ethereum/BSC/Polygon in TMA — broader DeFi ecosystem but no native integration. Uses Web3Auth, Privy, or own MPC backend. Suitable if your audience is oriented to existing EVM DeFi.
Combined approach: TON for native TG activities + EVM for DeFi. Technically more complex but provides maximum coverage.
Telegram WebApp API
// TMA initialization
import WebApp from '@twa-dev/sdk';
WebApp.ready(); // signal to Telegram app is ready
WebApp.expand(); // expand to full screen
// User data (IMPORTANT: verify on server)
const user = WebApp.initDataUnsafe.user;
// initData — string for Telegram signature verification
// Buttons
WebApp.MainButton.setText('Confirm transaction');
WebApp.MainButton.onClick(() => handleTransaction());
WebApp.MainButton.show();
// Haptic feedback for transactions
WebApp.HapticFeedback.notificationOccurred('success');
// Telegram color scheme
const isDark = WebApp.colorScheme === 'dark';
Verification of initData on server is mandatory for any wallet operation:
// Backend: Node.js
import crypto from 'crypto';
function verifyTelegramData(initData: string, botToken: string): boolean {
const params = new URLSearchParams(initData);
const hash = params.get('hash');
params.delete('hash');
const dataCheckString = Array.from(params.entries())
.sort(([a], [b]) => a.localeCompare(b))
.map(([k, v]) => `${k}=${v}`)
.join('\n');
const secretKey = crypto
.createHmac('sha256', 'WebAppData')
.update(botToken)
.digest();
const expectedHash = crypto
.createHmac('sha256', secretKey)
.update(dataCheckString)
.digest('hex');
return hash === expectedHash;
}
Telegram signs initData with bot token. Forging without token knowledge is impossible. Every wallet API request must include initData and pass this verification.
Wallet Architecture: Three Approaches
1. Custodial with MPC Backend
User doesn't manage keys directly. Platform stores keys in MPC (multiple server shards). Identification: telegram_user_id.
User (TMA) → Backend API (initData verification) → MPC Signing Service → Blockchain
Pros: no client key storage problem, simple recovery, fast UX. Cons: custodial risk, may need license (depends on jurisdiction), user trusts platform.
Implementation: Web3Auth MPC Core Kit on server, or Fireblocks API. Telegram user ID → deterministic wallet address via service. EVM + TON support in one system.
2. Embedded Wallet with AA (Account Abstraction)
Use ERC-4337 Smart Account. Key generated on device (in TMA memory) but wallet contract can have multiple owners and recovery mechanism.
// AA wallet creation via ZeroDev SDK
import { createKernelAccount, createKernelAccountClient } from '@zerodev/sdk';
import { signerToEcdsaValidator } from '@zerodev/ecdsa-validator';
// Signer from Telegram initData (deterministic from user_id + secret)
const signer = privateKeyToAccount(deriveKeyFromTelegram(initData));
const ecdsaValidator = await signerToEcdsaValidator(publicClient, {
signer,
kernelVersion: KERNEL_V3_1,
});
const account = await createKernelAccount(publicClient, {
plugins: { sudo: ecdsaValidator },
kernelVersion: KERNEL_V3_1,
});
// Gasless transactions via Paymaster
const kernelClient = createKernelAccountClient({
account,
paymaster: { getPaymasterData: async (userOp) => ... },
});
Advantage: gasless transactions (paymaster pays gas), batched operations, social recovery via smart contract. Disadvantage: key in TMA memory — on app close either store on server (custodial element) or require re-derivation.
3. TonConnect for TON
import TonConnect from '@tonconnect/sdk';
const connector = new TonConnect({
manifestUrl: 'https://yourapp.com/tonconnect-manifest.json',
});
// Open wallet list (TON Space, Tonkeeper, etc.)
const walletsList = await connector.getWallets();
// Connect specific wallet
await connector.connect({
universalLink: wallet.universalLink,
bridgeUrl: wallet.bridgeUrl,
});
// Send transaction
await connector.sendTransaction({
validUntil: Math.floor(Date.now() / 1000) + 300,
messages: [{
address: recipientAddress,
amount: toNano('0.5').toString(),
payload: cell.toBoc().toString('base64'),
}],
});
TonConnect opens TON Space or Tonkeeper via deep link. For app's own wallet — use TON SDK with keys stored via your backend.
Secure Key Storage in TMA
Main TMA problem: no safe storage. What doesn't work:
-
localStorage— cleared by Telegram, readable by JavaScript -
sessionStorage— lives only session - IndexedDB — same security issues as localStorage
Solution: server-side storage with encryption. Key (or MPC share) stored on server, encrypted with key known only to user. User enters PIN → PIN transformed to encryption key via PBKDF2/Argon2 → decrypts share.
// Client-side: encrypt share before sending to server
async function encryptShare(share: Uint8Array, pin: string): Promise<string> {
const pinKey = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(pin),
'PBKDF2',
false,
['deriveKey'],
);
const salt = crypto.getRandomValues(new Uint8Array(16));
const aesKey = await crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt, iterations: 600_000, hash: 'SHA-256' },
pinKey,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt'],
);
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
aesKey,
share,
);
return JSON.stringify({
salt: btoa(String.fromCharCode(...salt)),
iv: btoa(String.fromCharCode(...iv)),
data: btoa(String.fromCharCode(...new Uint8Array(encrypted))),
});
}
Server stores encrypted blob, decryption without PIN impossible. PIN not transmitted to server.
UI/UX Patterns for TMA Wallet
Adaptation to Telegram UI
TMA should look native in Telegram. Use Telegram CSS variables:
:root {
--tg-theme-bg-color: var(--tg-color-scheme-bg);
--tg-theme-text-color: var(--tg-color-scheme-text);
--tg-theme-button-color: var(--tg-color-scheme-button);
--tg-theme-button-text-color: var(--tg-color-scheme-button-text);
}
Component library: @telegram-apps/telegram-ui — ready components in Telegram style (Cell, List, Section, Modal). Use instead of custom design — users immediately feel familiar interface.
Transaction UX
Transaction confirmation in TMA is critical moment. User must clearly see: what they're signing, how much it costs, gas fee (or "free" if gasless). Use WebApp.showConfirm() for simple operations or custom modal for complex ones.
// Show native Telegram confirm for critical operations
const confirmed = await new Promise<boolean>((resolve) => {
WebApp.showConfirm(
`Send ${amount} USDT to ${shortAddress(recipient)}?`,
resolve,
);
});
Infrastructure and Stack
Frontend: React 18 + TypeScript + Vite. @twa-dev/sdk for Telegram API. wagmi + viem for EVM. @ton/ton for TON. Tailwind CSS with Telegram CSS variables.
Backend: Node.js + Fastify. Telegram Bot API for notifications. PostgreSQL for user data + wallet metadata. Redis for sessions and rate limiting.
Web3 infrastructure: Alchemy / Infura for EVM RPC. TON Center or orbs.com for TON. Bundler + Paymaster (Pimlico, ZeroDev) for AA.
| Component | Technology |
|---|---|
| TMA Framework | @twa-dev/sdk + React |
| EVM Wallet | Web3Auth MPC / ZeroDev AA |
| TON Wallet | TonConnect + @ton/core |
| Auth | Telegram initData verification |
| Key storage | Server-side encrypted shares |
| Notifications | Telegram Bot API |
Timeline Estimates
MVP (custodial, one chain, basic send/receive): 4–6 weeks. Full-featured wallet with MPC, AA gasless, multi-chain, TON + EVM: 3–5 months.
Separately: any wallet working with real user money requires security audit before public release.







