ExamplesTelegram Bot

Telegram Bot Example

A complete Telegram bot that lets users sign XRPL transactions with any supported wallet. Users tap a button, sign on their phone or browser, and the bot confirms the result.

πŸ’‘

This example uses webhooks for production-grade reliability. The bot receives Telegram updates via webhook and XRPL Request delivery events via a separate webhook endpoint.

Prerequisites

Install dependencies

npm install grammy @xrplrequest/sdk
npm install -D typescript @types/node tsx

Environment variables

TELEGRAM_BOT_TOKEN=7123456789:AAF...
XRPL_REQUEST_API_KEY=xrplr_live_...
XRPL_REQUEST_WEBHOOK_SECRET=whsec_...
BASE_URL=https://your-server.example.com
PORT=3000

Full example

import { Bot, InlineKeyboard, webhookCallback } from "grammy";
import { createServer, IncomingMessage, ServerResponse } from "http";
import { XRPLRequest } from "@xrplrequest/sdk";
import { verifyWebhookSignature } from "@xrplrequest/sdk";
 
const bot = new Bot(process.env.TELEGRAM_BOT_TOKEN!);
const client = new XRPLRequest({ apiKey: process.env.XRPL_REQUEST_API_KEY! });
 
// Map payload UUIDs to Telegram chat IDs so we can reply when signing completes
const pendingPayloads = new Map<string, { chatId: number; messageId: number }>();
 
// ────────────────────────────────────────────────────────────
// /sign command β€” create a signing request and send the link
// ────────────────────────────────────────────────────────────
bot.command("sign", async (ctx) => {
  const destination = ctx.match?.trim();
 
  if (!destination || !destination.startsWith("r")) {
    await ctx.reply(
      "Usage: /sign <destination_address>\n\nExample:\n/sign rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"
    );
    return;
  }
 
  const msg = await ctx.reply("⏳ Creating signing request...");
 
  const payload = await client.payloads.create({
    type: "signAndSubmit",
    transaction: {
      TransactionType: "Payment",
      Destination: destination,
      Amount: "1000000", // 1 XRP in drops
    },
    options: { expiresIn: 300 },
  });
 
  pendingPayloads.set(payload.uuid, {
    chatId: ctx.chat.id,
    messageId: msg.message_id,
  });
 
  const keyboard = new InlineKeyboard().url("✍️ Sign Transaction", payload.signingUrl);
 
  await ctx.api.editMessageText(
    ctx.chat.id,
    msg.message_id,
    `Sign this transaction to send 1 XRP to \`${destination}\`\n\nExpires in 5 minutes.`,
    {
      parse_mode: "Markdown",
      reply_markup: keyboard,
    }
  );
});
 
// ────────────────────────────────────────────────────────────
// /connect command β€” request wallet connection only
// ────────────────────────────────────────────────────────────
bot.command("connect", async (ctx) => {
  const msg = await ctx.reply("⏳ Creating connection request...");
 
  const payload = await client.payloads.create({
    type: "connect",
    options: { expiresIn: 180 },
  });
 
  pendingPayloads.set(payload.uuid, {
    chatId: ctx.chat.id,
    messageId: msg.message_id,
  });
 
  const keyboard = new InlineKeyboard().url("πŸ”— Connect Wallet", payload.signingUrl);
 
  await ctx.api.editMessageText(
    ctx.chat.id,
    msg.message_id,
    "Connect your XRPL wallet to this bot:",
    { reply_markup: keyboard }
  );
});
 
// ────────────────────────────────────────────────────────────
// HTTP server β€” handles both Telegram updates & XRPL webhooks
// ────────────────────────────────────────────────────────────
const handleTelegramUpdate = webhookCallback(bot, "http");
 
const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
  const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
 
  // Telegram webhook endpoint
  if (req.method === "POST" && url.pathname === "/telegram") {
    return handleTelegramUpdate(req, res);
  }
 
  // XRPL Request webhook endpoint
  if (req.method === "POST" && url.pathname === "/xrpl-webhook") {
    const chunks: Buffer[] = [];
    req.on("data", (c) => chunks.push(c));
    req.on("end", async () => {
      const rawBody = Buffer.concat(chunks).toString("utf8");
      const sig = req.headers["x-xrpl-request-signature"] as string;
 
      // Verify HMAC signature (Pro plan)
      const isValid = verifyWebhookSignature(
        process.env.XRPL_REQUEST_WEBHOOK_SECRET!,
        rawBody,
        sig
      );
 
      if (!isValid) {
        res.writeHead(401).end("Invalid signature");
        return;
      }
 
      const event = JSON.parse(rawBody);
      const uuid: string = event.payload?.uuid;
      const status: string = event.payload?.status;
      const ctx = pendingPayloads.get(uuid);
 
      if (ctx) {
        pendingPayloads.delete(uuid);
 
        let text: string;
        if (status === "signed") {
          const txHash = event.payload?.txHash;
          text = `βœ… Transaction signed!\n\nTx hash: \`${txHash}\`\n[View on XRPL Explorer](https://livenet.xrpl.org/transactions/${txHash})`;
        } else if (status === "rejected") {
          text = "❌ Transaction was rejected.";
        } else {
          text = "⏱ Signing request expired.";
        }
 
        await bot.api.editMessageText(ctx.chatId, ctx.messageId, text, {
          parse_mode: "Markdown",
        });
      }
 
      res.writeHead(200).end("ok");
    });
    return;
  }
 
  res.writeHead(404).end();
});
 
const PORT = parseInt(process.env.PORT ?? "3000");
server.listen(PORT, async () => {
  const BASE_URL = process.env.BASE_URL!;
 
  // Register Telegram webhook
  await bot.api.setWebhook(`${BASE_URL}/telegram`);
  console.log(`Bot running β€” Telegram webhook: ${BASE_URL}/telegram`);
  console.log(`XRPL Request webhook: ${BASE_URL}/xrpl-webhook`);
});

Register the XRPL Request webhook

Register /xrpl-webhook in your XRPL Request project once the server is running:

curl -X POST https://xrplre.quest/api/v1/webhooks \
  -H "Authorization: Bearer xrplr_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://your-server.example.com/xrpl-webhook" }'

Store the returned secret in your XRPL_REQUEST_WEBHOOK_SECRET env variable.

Local development with ngrok

# Start your bot server
tsx bot.ts
 
# In another terminal β€” expose port 3000
ngrok http 3000
 
# Set the ngrok URL as BASE_URL and restart the server
BASE_URL=https://abc123.ngrok.io tsx bot.ts

Bot commands to register

Register these with @BotFather using /setcommands:

sign - Sign and submit an XRP payment
connect - Connect your XRPL wallet

Available interactions

CommandPayload typeDescription
/sign <address>signAndSubmitSend 1 XRP, confirmed on-chain
/connectconnectLink wallet address to the bot

Extend with /signmessage (type signMessage) or custom transaction types to fit your use case.