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
- Node.js 18+
- A Telegram bot token from @BotFather
- An XRPL Request API key from xrplre.quest/dashboard
- A public HTTPS URL (use ngrok for local dev)
Install dependencies
npm install grammy @xrplrequest/sdk
npm install -D typescript @types/node tsxEnvironment 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=3000Full 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.tsBot commands to register
Register these with @BotFather using /setcommands:
sign - Sign and submit an XRP payment
connect - Connect your XRPL walletAvailable interactions
| Command | Payload type | Description |
|---|---|---|
/sign <address> | signAndSubmit | Send 1 XRP, confirmed on-chain |
/connect | connect | Link wallet address to the bot |
Extend with /signmessage (type signMessage) or custom transaction types to fit your use case.