feat: display truncated lightning address for winners

- Add truncateLightningAddress utility (shows first 2 chars + ******)
- Backend: Include winner_address in past-wins API response
- Frontend: Display truncated address in past winners list
- Telegram: Add truncated address to draw announcements for transparency

Example: username@blink.sv -> us******@blink.sv
This commit is contained in:
Michilis
2025-12-12 16:20:18 +00:00
parent 00f09236a3
commit 3bc067f691
6 changed files with 65 additions and 8 deletions

View File

@@ -464,11 +464,13 @@ Good luck next round! 🍀`,
winnerName: string,
winningTicket: string,
prizeSats: string,
totalTickets: number
totalTickets: number,
winnerAddress?: string
) =>
`🎰 *JACKPOT DRAW COMPLETE!* 🎰
🏆 *Winner:* ${winnerName}
⚡ *Address:* \`${winnerAddress || 'N/A'}\`
🎟 *Winning Ticket:* #${winningTicket}
💰 *Prize:* ${prizeSats} sats
📊 *Total Tickets:* ${totalTickets}

View File

@@ -6,6 +6,7 @@ import { logger } from './logger';
import { messages } from '../messages';
import { GroupSettings, reminderTimeToMinutes, formatReminderTime, ReminderTime, DEFAULT_GROUP_REMINDER_SLOTS } from '../types/groups';
import { TelegramUser } from '../types';
import { truncateLightningAddress } from '../utils/format';
interface CycleInfo {
id: string;
@@ -158,6 +159,7 @@ class NotificationScheduler {
let winnerDisplayName = 'Anon';
let winnerTicketNumber = '0000';
let winnerTelegramId: number | null = null;
let winnerLightningAddress = '';
let potSats = 0;
let prizeSats = 0;
let payoutStatus = 'processing';
@@ -174,6 +176,7 @@ class NotificationScheduler {
const user = await stateManager.getUser(telegramId);
winnerTelegramId = telegramId;
winnerDisplayName = user ? stateManager.getDisplayName(user) : 'Anon';
winnerLightningAddress = status.purchase.lightning_address || '';
const winningTicket = status.tickets.find(t => t.is_winning_ticket);
if (winningTicket) {
@@ -240,7 +243,8 @@ class NotificationScheduler {
winnerDisplayName,
winnerTicketNumber,
potSats,
uniqueUserCount
uniqueUserCount,
winnerLightningAddress
);
}
@@ -252,11 +256,13 @@ class NotificationScheduler {
winnerDisplayName: string,
winnerTicketNumber: string,
potSats: number,
totalParticipants: number
totalParticipants: number,
winnerLightningAddress: string
): Promise<void> {
if (!this.bot) return;
const groups = await groupStateManager.getGroupsWithFeature('drawAnnouncements');
const truncatedAddress = truncateLightningAddress(winnerLightningAddress);
for (const group of groups) {
try {
@@ -264,7 +270,8 @@ class NotificationScheduler {
winnerDisplayName,
`#${winnerTicketNumber}`,
potSats.toLocaleString(),
totalParticipants
totalParticipants,
truncatedAddress
);
await this.bot.sendMessage(group.groupId, message, { parse_mode: 'Markdown' });
@@ -384,6 +391,7 @@ class NotificationScheduler {
let winnerDisplayName = 'Anon';
let winnerTicketNumber = '0000';
let winnerTelegramId: number | null = null;
let winnerLightningAddress = '';
let prizeSats = 0;
let payoutStatus = 'processing';
@@ -397,6 +405,7 @@ class NotificationScheduler {
const user = await stateManager.getUser(telegramId);
winnerTelegramId = telegramId;
winnerDisplayName = user ? stateManager.getDisplayName(user) : 'Anon';
winnerLightningAddress = status.purchase.lightning_address || '';
const winningTicket = status.tickets.find(t => t.is_winning_ticket);
if (winningTicket) {
@@ -458,7 +467,7 @@ class NotificationScheduler {
// Send group announcements
const uniqueUserCount = userPurchases.size;
await this.sendGroupDrawAnnouncements(cycle, winnerDisplayName, winnerTicketNumber, uniqueUserCount);
await this.sendGroupDrawAnnouncements(cycle, winnerDisplayName, winnerTicketNumber, uniqueUserCount, winnerLightningAddress);
}
/**
@@ -468,11 +477,13 @@ class NotificationScheduler {
cycle: CycleInfo,
winnerDisplayName: string,
winnerTicketNumber: string,
totalParticipants: number
totalParticipants: number,
winnerLightningAddress: string
): Promise<void> {
if (!this.bot) return;
const groups = await groupStateManager.getGroupsWithFeature('drawAnnouncements');
const truncatedAddress = truncateLightningAddress(winnerLightningAddress);
for (const group of groups) {
const delay = (group.announcementDelaySeconds || 0) * 1000;
@@ -483,7 +494,8 @@ class NotificationScheduler {
winnerDisplayName,
`#${winnerTicketNumber}`,
cycle.pot_total_sats.toLocaleString(),
totalParticipants
totalParticipants,
truncatedAddress
);
if (this.bot) {

View File

@@ -125,4 +125,20 @@ export function truncate(str: string, maxLength: number): string {
return str.substring(0, maxLength - 3) + '...';
}
/**
* Truncate lightning address for privacy
* "username@blink.sv" -> "us******@blink.sv"
*/
export function truncateLightningAddress(address: string): string {
if (!address || !address.includes('@')) return address;
const [username, domain] = address.split('@');
// Show first 2 chars of username, then asterisks
const visibleChars = Math.min(2, username.length);
const truncatedUsername = username.substring(0, visibleChars) + '******';
return `${truncatedUsername}@${domain}`;
}