Skip to content

Commit ea8ea74

Browse files
Merge pull request #100 from blockful/refactor/OP-Icon-+-Number-Formatting-for-Notification-System
Refactor/op icon + number formatting for notification system
2 parents 4b3db49 + 62e2373 commit ea8ea74

4 files changed

Lines changed: 127 additions & 6 deletions

File tree

apps/consumers/src/services/dao.service.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ export class DAOService {
1616
// DAO emojis mapping
1717
private daoEmojis = new Map<string, string>([
1818
['UNI', '🦄'],
19-
['ENS', '🔷']
19+
['ENS', '🔷'],
20+
['OP', '🔴'],
2021
]);
2122

2223
constructor(
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { formatTokenAmount } from './number-formatter';
2+
3+
describe('formatTokenAmount', () => {
4+
describe('with 18 decimals (default)', () => {
5+
it('should format zero correctly', () => {
6+
expect(formatTokenAmount('0')).toBe('0');
7+
expect(formatTokenAmount(0)).toBe('0');
8+
});
9+
10+
it('should format very small amounts as <0.1', () => {
11+
expect(formatTokenAmount('1')).toBe('<0.1'); // 1 wei
12+
expect(formatTokenAmount('50000000000000000')).toBe('<0.1'); // 0.05 token
13+
expect(formatTokenAmount('99999999999999999')).toBe('0.1'); // 0.099... token (rounds to 0.1)
14+
});
15+
16+
it('should format small amounts with 1 decimal', () => {
17+
expect(formatTokenAmount('100000000000000000')).toBe('0.1'); // 0.1 token
18+
expect(formatTokenAmount('100000000000000001')).toBe('0.1'); // 0.1+ token (rounded)
19+
expect(formatTokenAmount('500000000000000000')).toBe('0.5'); // 0.5 token
20+
expect(formatTokenAmount('1000000000000000000')).toBe('1.0'); // 1 token
21+
expect(formatTokenAmount('1500000000000000000')).toBe('1.5'); // 1.5 tokens
22+
expect(formatTokenAmount('999000000000000000000')).toBe('999.0'); // 999 tokens
23+
});
24+
25+
it('should format thousands with K suffix', () => {
26+
expect(formatTokenAmount('1000000000000000000000')).toBe('1.0K'); // 1,000 tokens
27+
expect(formatTokenAmount('1234000000000000000000')).toBe('1.2K'); // 1,234 tokens
28+
expect(formatTokenAmount('12345000000000000000000')).toBe('12.3K'); // 12,345 tokens
29+
expect(formatTokenAmount('999999000000000000000000')).toBe('1.0M'); // 999,999 tokens (rounded up to 1M)
30+
});
31+
32+
it('should format millions with M suffix', () => {
33+
expect(formatTokenAmount('1000000000000000000000000')).toBe('1.0M'); // 1 million tokens
34+
expect(formatTokenAmount('1234567000000000000000000')).toBe('1.2M'); // 1.234567 million tokens
35+
expect(formatTokenAmount('999999999000000000000000000')).toBe('1.0B'); // ~1 billion tokens (rounded up to 1B)
36+
});
37+
38+
it('should format billions with B suffix', () => {
39+
expect(formatTokenAmount('1000000000000000000000000000')).toBe('1.0B'); // 1 billion tokens
40+
expect(formatTokenAmount('1234567890000000000000000000')).toBe('1.2B'); // 1.23... billion tokens
41+
});
42+
43+
it('should use exponential notation for very large numbers', () => {
44+
expect(formatTokenAmount('1000000000000000000000000000000')).toBe('1.0e+12'); // trillion (uses exponential)
45+
});
46+
47+
it('should promote units when rounding results in 1000+', () => {
48+
// Test promotion from K to M (999.95K rounds to 1000.0K -> 1.0M)
49+
expect(formatTokenAmount('999950000000000000000000')).toBe('1.0M');
50+
51+
// Test promotion from M to B (999.95M rounds to 1000.0M -> 1.0B)
52+
expect(formatTokenAmount('999950000000000000000000000')).toBe('1.0B');
53+
});
54+
});
55+
56+
describe('with custom decimals', () => {
57+
it('should handle 6 decimals (like USDC)', () => {
58+
expect(formatTokenAmount('1000000', 6)).toBe('1.0'); // 1 USDC
59+
expect(formatTokenAmount('1500000', 6)).toBe('1.5'); // 1.5 USDC
60+
expect(formatTokenAmount('1000000000', 6)).toBe('1.0K'); // 1,000 USDC
61+
});
62+
63+
it('should handle 8 decimals (like WBTC)', () => {
64+
expect(formatTokenAmount('100000000', 8)).toBe('1.0'); // 1 WBTC
65+
expect(formatTokenAmount('50000000', 8)).toBe('0.5'); // 0.5 WBTC
66+
expect(formatTokenAmount('100000000000', 8)).toBe('1.0K'); // 1,000 WBTC
67+
});
68+
});
69+
});
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* Utility functions for formatting numbers in human-readable notation
3+
*/
4+
5+
/**
6+
* Formats token amounts from wei to human-readable format
7+
* @param amount The amount in wei (as string or number)
8+
* @param decimals The number of decimals for the token (default: 18)
9+
* @returns Formatted string with appropriate suffix (K, M, B, etc)
10+
*/
11+
export function formatTokenAmount(amount: string | number, decimals: number = 18): string {
12+
// Convert wei string to number, handling 18 decimal places
13+
const divisor = Math.pow(10, decimals);
14+
const actualAmount = Number(amount) / divisor;
15+
16+
// Handle zero
17+
if (actualAmount === 0) return '0';
18+
19+
// Handle very small amounts (less than 0.1)
20+
if (actualAmount < 0.1) return '<0.1';
21+
22+
// For amounts less than 1000, show with 1 decimal place
23+
if (actualAmount < 1000) {
24+
return actualAmount.toFixed(1);
25+
}
26+
27+
// For larger amounts, use K, M, B notation
28+
const units = ['', 'K', 'M', 'B'];
29+
let unitIndex = Math.floor(Math.log10(actualAmount) / 3);
30+
31+
if (unitIndex >= units.length) {
32+
return actualAmount.toExponential(1);
33+
}
34+
35+
let scaledAmount = actualAmount / Math.pow(1000, unitIndex);
36+
let formatted = scaledAmount.toFixed(1);
37+
38+
// If rounded result is >= 1000, promote to next unit
39+
if (parseFloat(formatted) >= 1000) {
40+
unitIndex++;
41+
if (unitIndex >= units.length) {
42+
return actualAmount.toExponential(1);
43+
}
44+
scaledAmount = actualAmount / Math.pow(1000, unitIndex);
45+
formatted = scaledAmount.toFixed(1);
46+
}
47+
48+
return formatted + units[unitIndex];
49+
}

apps/dispatcher/src/services/triggers/voting-power-trigger.service.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { DispatcherMessage, MessageProcessingResult } from "../../interfaces/dis
22
import { ISubscriptionClient, User } from "../../interfaces/subscription-client.interface";
33
import { NotificationClientFactory } from "../notification/notification-factory.service";
44
import { BaseTriggerHandler } from "./base-trigger.service";
5+
import { formatTokenAmount } from "../../lib/number-formatter";
56
import crypto from 'crypto';
67

78
/**
@@ -62,23 +63,24 @@ export class VotingPowerTriggerHandler extends BaseTriggerHandler {
6263
let notificationMessage = '';
6364

6465
const deltaValue = delta ? parseInt(delta) : 0;
66+
const formattedDelta = formatTokenAmount(Math.abs(deltaValue));
6567

6668
if (changeType === 'delegation') {
6769
if (deltaValue >= 0) {
68-
notificationMessage = `🥳 You've received a new delegation in ${daoId}!\n${sourceAccountId} delegated to you, increasing your voting power by ${deltaValue}.`;
70+
notificationMessage = `🥳 You've received a new delegation in ${daoId}!\n${sourceAccountId} delegated to you, increasing your voting power by ${formattedDelta}.`;
6971
} else if (deltaValue < 0) {
70-
notificationMessage = `🥺 A delegator just undelegated in ${daoId}!\n${sourceAccountId} removed their delegation, reducing your voting power by ${deltaValue}.`;
72+
notificationMessage = `🥺 A delegator just undelegated in ${daoId}!\n${sourceAccountId} removed their delegation, reducing your voting power by ${formattedDelta}.`;
7173
}
7274
} else if (changeType === 'transfer') {
7375
if (deltaValue >= 0) {
74-
notificationMessage = `📈 Your voting power increased in ${daoId}!\nYou gained ${deltaValue} voting power from token transfer activity.`;
76+
notificationMessage = `📈 Your voting power increased in ${daoId}!\nYou gained ${formattedDelta} voting power from token transfer activity.`;
7577
} else if (deltaValue < 0) {
76-
notificationMessage = `📉 Your voting power decreased in ${daoId}!\nYou lost ${Math.abs(deltaValue)} voting power from token transfer activity.`;
78+
notificationMessage = `📉 Your voting power decreased in ${daoId}!\nYou lost ${formattedDelta} voting power from token transfer activity.`;
7779
}
7880
} else {
7981
// Generic voting power change
8082
if (deltaValue !== 0) {
81-
notificationMessage = `⚡ Your voting power has changed in ${daoId}!\nVoting power updated by ${deltaValue}.`;
83+
notificationMessage = `⚡ Your voting power has changed in ${daoId}!\nVoting power updated by ${formattedDelta}.`;
8284
} else {
8385
notificationMessage = `⚡ Your voting power has changed in ${daoId}!\nVoting power activity detected.`;
8486
}

0 commit comments

Comments
 (0)