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:
@@ -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}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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}`;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user