# Backend Implementation (/docs/cookbook/risex-telegram-bot/backend)

import { Callout } from 'fumadocs-ui/components/callout';

## Building the Backend

The backend consists of four main parts: encryption service, storage services, RISEx API integration, and the Telegram bot with LLM routing. The complete implementation can be found in the [GitHub repository](https://github.com/awesamarth/risex-tg-bot).

### Encryption Service

Implement AES-256-GCM encryption for secure key storage:

```typescript title="apps/tg-bot/src/services/encryption.ts"
import { createCipheriv, createDecipheriv, randomBytes, createHash } from 'crypto';

const ALGORITHM = 'aes-256-gcm';
const IV_LENGTH = 16;
const AUTH_TAG_LENGTH = 16;
const SALT_LENGTH = 32;

function deriveKey(secret: string, salt: Buffer): Buffer {
  return createHash('sha256')
    .update(secret + salt.toString('hex'))
    .digest();
}

export function encrypt(text: string, secret: string): string {
  const salt = randomBytes(SALT_LENGTH);
  const key = deriveKey(secret, salt);
  const iv = randomBytes(IV_LENGTH);

  const cipher = createCipheriv(ALGORITHM, key, iv);

  let encrypted = cipher.update(text, 'utf8', 'hex');
  encrypted += cipher.final('hex');

  const authTag = cipher.getAuthTag();

  // Combine salt + iv + authTag + encrypted
  return Buffer.concat([
    salt,
    iv,
    authTag,
    Buffer.from(encrypted, 'hex')
  ]).toString('hex');
}

export function decrypt(encryptedHex: string, secret: string): string {
  const buffer = Buffer.from(encryptedHex, 'hex');

  const salt = buffer.subarray(0, SALT_LENGTH);
  const iv = buffer.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
  const authTag = buffer.subarray(
    SALT_LENGTH + IV_LENGTH,
    SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH
  );
  const encrypted = buffer.subarray(SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);

  const key = deriveKey(secret, salt);

  const decipher = createDecipheriv(ALGORITHM, key, iv);
  decipher.setAuthTag(authTag);

  let decrypted = decipher.update(encrypted);
  decrypted = Buffer.concat([decrypted, decipher.final()]);

  return decrypted.toString('utf8');
}
```

This implements authenticated encryption to prevent tampering with stored keys.

***

### Storage Services

**Signer Storage:**

```typescript title="apps/tg-bot/src/services/signersStore.ts"
import fs from 'fs';
import path from 'path';
import { encrypt, decrypt } from './encryption';

const SIGNERS_FILE = path.join(__dirname, '../data/signers.json');
const SECRET = process.env.ENCRYPTION_SECRET!;

interface SignersData {
  [walletAddress: string]: string; // encrypted private key
}

function readSigners(): SignersData {
  if (!fs.existsSync(SIGNERS_FILE)) {
    return {};
  }
  return JSON.parse(fs.readFileSync(SIGNERS_FILE, 'utf8'));
}

function writeSigners(data: SignersData): void {
  fs.writeFileSync(SIGNERS_FILE, JSON.stringify(data, null, 2));
}

export function storeSigner(walletAddress: string, privateKey: string): void {
  const signers = readSigners();
  signers[walletAddress.toLowerCase()] = encrypt(privateKey, SECRET);
  writeSigners(signers);
}

export function getSigner(walletAddress: string): string | null {
  const signers = readSigners();
  const encrypted = signers[walletAddress.toLowerCase()];
  if (!encrypted) return null;
  return decrypt(encrypted, SECRET);
}

export function hasSigner(walletAddress: string): boolean {
  const signers = readSigners();
  return !!signers[walletAddress.toLowerCase()];
}
```

**Telegram Link Storage:**

```typescript title="apps/tg-bot/src/services/verifiedLinksStore.ts"
import fs from 'fs';
import path from 'path';

const LINKS_FILE = path.join(__dirname, '../data/verified-links.json');

interface VerifiedLinks {
  [telegramId: string]: string; // wallet address
}

function readLinks(): VerifiedLinks {
  if (!fs.existsSync(LINKS_FILE)) {
    return {};
  }
  return JSON.parse(fs.readFileSync(LINKS_FILE, 'utf8'));
}

function writeLinks(data: VerifiedLinks): void {
  fs.writeFileSync(LINKS_FILE, JSON.stringify(data, null, 2));
}

export function linkTelegramToWallet(telegramId: string, walletAddress: string): void {
  const links = readLinks();
  links[telegramId] = walletAddress.toLowerCase();
  writeLinks(links);
}

export function getWalletForTelegram(telegramId: string): string | null {
  const links = readLinks();
  return links[telegramId] || null;
}

export function hasLink(telegramId: string): boolean {
  const links = readLinks();
  return !!links[telegramId];
}
```

***

## RISEx Service

Create the RISEx API integration with EIP-712 signing:

```typescript title="apps/tg-bot/src/services/risexService.ts"
import axios from 'axios';
import { privateKeyToAccount } from 'viem/accounts';
import { formatUnits, parseUnits } from 'viem';

const RISEX_API = process.env.RISEX_API_URL!;

// EIP-712 domain for RISEx
const domain = {
  name: 'RISEx',
  version: '1',
  chainId: 8008135,
  verifyingContract: '0x3d47aa78fe75e8616dbadfbdce4ede1e73b35b27' as `0x${string}`,
};

// EIP-712 types for order placement
const types = {
  VerifySignature: [
    { name: 'signer', type: 'address' },
    { name: 'data', type: 'bytes' },
  ],
};

export async function placeOrder(
  signerPrivateKey: string,
  walletAddress: string,
  market: 'BTC' | 'ETH',
  side: 'long' | 'short',
  size: string,
  price: string,
  orderType: 'market' | 'limit'
) {
  const account = privateKeyToAccount(signerPrivateKey as `0x${string}`);

  // Prepare order payload
  const orderPayload = {
    account: walletAddress,
    market_id: market === 'BTC' ? 'BTC-PERP' : 'ETH-PERP',
    side: side === 'long' ? 0 : 1,
    size: parseUnits(size, 18).toString(),
    price: parseUnits(price, 18).toString(),
    order_type: orderType === 'market' ? 0 : 1,
    expiry: Math.floor(Date.now() / 1000) + 300, // 5 minutes
  };

  // Encode data for signing
  const data = JSON.stringify(orderPayload);

  // Sign EIP-712 message
  const signature = await account.signTypedData({
    domain,
    types,
    primaryType: 'VerifySignature',
    message: {
      signer: account.address,
      data: `0x${Buffer.from(data).toString('hex')}`,
    },
  });

  // Call RISEx API
  const response = await axios.post(`${RISEX_API}/v1/orders/place`, {
    ...orderPayload,
    signature,
    signer: account.address,
  });

  return response.data;
}

export async function getBalance(walletAddress: string) {
  const response = await axios.get(`${RISEX_API}/v1/account/balance`, {
    params: {
      account: walletAddress,
      token: '0x6bf6e258b3c5650b448cb1112835048ba5619dc1', // USDC
    },
  });

  const balance = response.data?.data?.balance || '0';
  return formatUnits(BigInt(balance), 18);
}

export async function getPositions(walletAddress: string) {
  const response = await axios.get(`${RISEX_API}/v1/positions`, {
    params: { account: walletAddress },
  });

  return response.data?.data?.positions || [];
}

export async function depositUSDC(
  signerPrivateKey: string,
  walletAddress: string,
  amount: string
) {
  const account = privateKeyToAccount(signerPrivateKey as `0x${string}`);

  const depositPayload = {
    account: walletAddress,
    amount: parseUnits(amount, 18).toString(),
  };

  const data = JSON.stringify(depositPayload);

  const signature = await account.signTypedData({
    domain: {
      ...domain,
      verifyingContract: '0x8e6c7d87fc4c35ab519127629c56bca07006cfca' as `0x${string}`,
    },
    types,
    primaryType: 'VerifySignature',
    message: {
      signer: account.address,
      data: `0x${Buffer.from(data).toString('hex')}`,
    },
  });

  const response = await axios.post(`${RISEX_API}/v1/account/deposit`, {
    ...depositPayload,
    signature,
    signer: account.address,
  });

  return response.data;
}
```

For additional functions like `getOpenOrders()`, see the [complete implementation on GitHub](https://github.com/awesamarth/risex-tg-bot/blob/main/apps/tg-bot/src/services/risexService.ts).

***

## LLM Router

Implement natural language processing with GPT-4o-mini:

```typescript title="apps/tg-bot/src/llm/router.ts"
import OpenAI from 'openai';
import * as risex from '../services/risexService';
import { getWalletForTelegram } from '../services/verifiedLinksStore';
import { getSigner } from '../services/signersStore';

const openai = new OpenAI({
  baseURL: 'https://openrouter.ai/api/v1',
  apiKey: process.env.OPENROUTER_API_KEY,
});

// Define tool schemas
const tools = [
  {
    type: 'function',
    function: {
      name: 'get_balance',
      description: 'Get the USDC balance for the user account',
      parameters: { type: 'object', properties: {} },
    },
  },
  {
    type: 'function',
    function: {
      name: 'place_order',
      description: 'Place a perpetual futures order (market or limit)',
      parameters: {
        type: 'object',
        properties: {
          market: { type: 'string', enum: ['BTC', 'ETH'] },
          side: { type: 'string', enum: ['long', 'short'] },
          size: { type: 'string', description: 'Contract size (e.g., "0.02")' },
          price: { type: 'string', description: 'Limit price or "market"' },
          orderType: { type: 'string', enum: ['market', 'limit'] },
        },
        required: ['market', 'side', 'size', 'orderType'],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'get_positions',
      description: 'Get all open perpetual positions',
      parameters: { type: 'object', properties: {} },
    },
  },
  {
    type: 'function',
    function: {
      name: 'deposit',
      description: 'Deposit testnet USDC to trading account',
      parameters: {
        type: 'object',
        properties: {
          amount: { type: 'string', description: 'Amount of USDC (default: 1000)' },
        },
      },
    },
  },
];

export async function handleMessage(telegramId: string, message: string): Promise<string> {
  // Get wallet and signer
  const walletAddress = getWalletForTelegram(telegramId);
  if (!walletAddress) {
    return '❌ No wallet linked. Use /link to get started.';
  }

  const signerKey = getSigner(walletAddress);
  if (!signerKey) {
    return '❌ No trading signer registered. Please complete registration.';
  }

  // Call GPT-4o-mini
  const response = await openai.chat.completions.create({
    model: 'openai/gpt-4o-mini',
    messages: [
      {
        role: 'system',
        content: 'You are a trading assistant for RISEx perpetual futures. Parse user requests and call the appropriate tools.',
      },
      { role: 'user', content: message },
    ],
    tools: tools as any,
    tool_choice: 'auto',
  });

  const toolCall = response.choices[0]?.message?.tool_calls?.[0];
  if (!toolCall) {
    return response.choices[0]?.message?.content || '❓ Could not understand request.';
  }

  const functionName = toolCall.function.name;
  const args = JSON.parse(toolCall.function.arguments);

  // Execute tool
  try {
    switch (functionName) {
      case 'get_balance':
        const balance = await risex.getBalance(walletAddress);
        return `💰 Balance: ${balance} USDC`;

      case 'place_order':
        const orderResult = await risex.placeOrder(
          signerKey,
          walletAddress,
          args.market,
          args.side,
          args.size,
          args.price || '0',
          args.orderType
        );
        return `✅ Order placed!\nMarket: ${args.market}-PERP\nSide: ${args.side.toUpperCase()}\nSize: ${args.size}\nTx: ${orderResult.data?.tx_hash || 'N/A'}`;

      case 'get_positions':
        const positions = await risex.getPositions(walletAddress);
        if (positions.length === 0) {
          return '📊 No open positions.';
        }
        return positions.map((p: any) =>
          `${p.market_id} ${p.side === 0 ? 'LONG' : 'SHORT'} ${formatUnits(BigInt(p.size), 18)}`
        ).join('\n');

      case 'deposit':
        const depositAmount = args.amount || '1000';
        await risex.depositUSDC(signerKey, walletAddress, depositAmount);
        return `✅ Deposited ${depositAmount} USDC to your account!`;

      default:
        return '❓ Unknown command.';
    }
  } catch (error: any) {
    console.error('Tool execution error:', error);
    return `❌ Error: ${error.message}`;
  }
}
```

***

## Main Bot Implementation

Create the Telegram bot entry point:

```typescript title="apps/tg-bot/src/index.ts"
import { Telegraf } from 'telegraf';
import express from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
import { handleMessage } from './llm/router';
import * as risex from './services/risexService';
import { getWalletForTelegram, linkTelegramToWallet, hasLink } from './services/verifiedLinksStore';
import { getSigner, storeSigner, hasSigner } from './services/signersStore';

dotenv.config();

const bot = new Telegraf(process.env.TELEGRAM_BOT_TOKEN!);
const app = express();

app.use(cors({ origin: process.env.FRONTEND_URL }));
app.use(express.json());

// Slash commands
bot.command('start', (ctx) => {
  ctx.reply(
    `👋 Welcome to RISEx Trading Bot!\n\n` +
    `Use /link to connect your wallet, then:\n` +
    `• Natural language: "buy 0.02 BTC"\n` +
    `• /balance - Check USDC balance\n` +
    `• /deposit - Get testnet USDC\n` +
    `• /positions - View open positions`
  );
});

bot.command('link', (ctx) => {
  const telegramId = ctx.from.id.toString();
  ctx.reply(
    `🔗 Your Telegram ID: \`${telegramId}\`\n\n` +
    `Visit: ${process.env.FRONTEND_URL}\n` +
    `Enter this ID and complete registration.`,
    { parse_mode: 'Markdown' }
  );
});

bot.command('debug', (ctx) => {
  const telegramId = ctx.from.id.toString();
  const wallet = getWalletForTelegram(telegramId);
  ctx.reply(
    `🐛 Debug Info:\n` +
    `Telegram ID: ${telegramId}\n` +
    `Wallet: ${wallet || 'Not linked'}\n` +
    `Signer: ${wallet && hasSigner(wallet) ? 'Registered' : 'Not registered'}`
  );
});

bot.command('balance', async (ctx) => {
  const telegramId = ctx.from.id.toString();
  const wallet = getWalletForTelegram(telegramId);

  if (!wallet) {
    return ctx.reply('❌ No wallet linked. Use /link first.');
  }

  const balance = await risex.getBalance(wallet);
  ctx.reply(`💰 Balance: ${balance} USDC`);
});

bot.command('deposit', async (ctx) => {
  const telegramId = ctx.from.id.toString();
  const wallet = getWalletForTelegram(telegramId);

  if (!wallet) {
    return ctx.reply('❌ No wallet linked. Use /link first.');
  }

  const signerKey = getSigner(wallet);
  if (!signerKey) {
    return ctx.reply('❌ No signer registered. Complete registration first.');
  }

  const amount = ctx.message.text.split(' ')[1] || '1000';
  await risex.depositUSDC(signerKey, wallet, amount);
  ctx.reply(`✅ Deposited ${amount} USDC!`);
});

// Natural language messages
bot.on('text', async (ctx) => {
  const text = ctx.message.text;

  // Skip commands
  if (text.startsWith('/')) return;

  const telegramId = ctx.from.id.toString();
  const response = await handleMessage(telegramId, text);
  ctx.reply(response);
});

// API routes for frontend
app.post('/api/link-telegram', (req, res) => {
  const { telegramId, walletAddress } = req.body;
  linkTelegramToWallet(telegramId, walletAddress);
  res.json({ success: true });
});

app.post('/api/store-signer', (req, res) => {
  const { walletAddress, privateKey } = req.body;
  storeSigner(walletAddress, privateKey);
  res.json({ success: true });
});

app.get('/api/check-signer/:address', (req, res) => {
  const exists = hasSigner(req.params.address);
  res.json({ exists });
});

// Start server
const PORT = process.env.PORT || 8008;
app.listen(PORT, () => {
  console.log(`✅ API server running on port ${PORT}`);
});

// Start bot
bot.launch();
console.log('✅ Telegram bot started');

// Graceful shutdown
process.once('SIGINT', () => bot.stop('SIGINT'));
process.once('SIGTERM', () => bot.stop('SIGTERM'));
```

For additional slash commands like `/positions` and `/orders`, see the [GitHub repository](https://github.com/awesamarth/risex-tg-bot).

Next, we'll build the [frontend registration UI](/docs/cookbook/risex-telegram-bot/frontend).
