diff --git a/package.json b/package.json index ce560e2..3ed0021 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "start:dev:debug": "dotenv -e .env.dev -e .env.service -- nest start --debug --watch", "start:local-dev": "dotenv -e .env.local-dev -e .env.service -- nest start --watch", "start:production": "dotenv -e .env.production -e .env.service -- nest start --watch", + "start:production:debug": "dotenv -e .env.production -e .env.service -- nest start --debug --watch", "start:local-dev:debug": "dotenv -e .env.local-dev -e .env.service -- nest start --debug --watch", "client:start:dev": "dotenv -e .env.dev -e .env.client -- ts-node test/client.ts", "client:start:local-dev": "dotenv -e .env.local-dev -e .env.client -- ts-node test/client.ts" @@ -32,6 +33,7 @@ "@nestjs/terminus": "^9.1.0", "@solana/spl-governance": "^0.3.28", "@solana/spl-token": "0.2.0", + "@solana/web3.js": "^1.91.7", "bn.js": "^5.1.3", "borsh": "^0.3.1", "bs58": "^4.0.1", diff --git a/src/formatting-utilts.ts b/src/formatting-utilts.ts new file mode 100644 index 0000000..0727adf --- /dev/null +++ b/src/formatting-utilts.ts @@ -0,0 +1,28 @@ +const toPrecise3 = new Intl.NumberFormat('en-US', { + maximumSignificantDigits: 3, +}); +function removeTrailingZeros(value: string): string { + return value.replace(/\.?0+$/, ''); +} + +export function amountToShortString(num: number): string { + if (num < 1) { + return toPrecise3.format(num); + } + if (num >= 1 && num < 1e3) { + return removeTrailingZeros(num.toFixed(2)); + } + if (num >= 1e3 && num < 1e6) { + return removeTrailingZeros((num / 1e3).toFixed(1)) + 'K'; + } + if (num >= 1e6 && num < 1e9) { + return removeTrailingZeros((num / 1e6).toFixed(1)) + 'M'; + } + if (num >= 1e9 && num < 1e12) { + return removeTrailingZeros((num / 1e9).toFixed(1)) + 'B'; + } + if (num >= 1e12) { + return removeTrailingZeros((num / 1e12).toFixed(1)) + 'T'; + } + return num.toFixed(2); +} diff --git a/src/proposal-state-monitoring.service.ts b/src/proposal-state-monitoring.service.ts index fc248bc..50d7255 100644 --- a/src/proposal-state-monitoring.service.ts +++ b/src/proposal-state-monitoring.service.ts @@ -19,8 +19,9 @@ import { } from '@solana/spl-governance'; import { CachingEventType, fmtTokenAmount, RealmMints } from './realms-cache'; import { OnEvent } from '@nestjs/event-emitter'; -import { DialectSdk } from '@dialectlabs/sdk'; +import { DappMessageActionType, DialectSdk } from '@dialectlabs/sdk'; import { Solana } from '@dialectlabs/blockchain-sdk-solana'; +import { amountToShortString } from './formatting-utilts'; interface ProposalVotingStats { yesCount: number; @@ -162,6 +163,8 @@ export class ProposalStateChangeMonitoringService { const { yesCount, noCount, relativeYesCount, relativeNoCount } = getVotingStats(account, realm); + const yesVotesFormatted = amountToShortString(yesCount); + const noVotesFormatted = amountToShortString(noCount); if (account.state === ProposalState.Succeeded) { return { title: `Proposal for ${realmName} is succeeded`, @@ -169,7 +172,16 @@ export class ProposalStateChangeMonitoringService { account.name } for ${realmName} is succeeded with ${relativeYesCount.toFixed( 1, - )}% of 👍 votes (${yesCount} 👍 / ${noCount} 👎): ${proposalLink}`, + )}% of 👍 votes (${yesVotesFormatted} 👍 / ${noVotesFormatted} 👎): ${proposalLink}`, + actions: { + type: DappMessageActionType.LINK, + links: [ + { + label: 'View Proposal', + url: proposalLink, + }, + ], + }, }; } if (account.state === ProposalState.Defeated) { @@ -179,7 +191,16 @@ export class ProposalStateChangeMonitoringService { account.name } for ${realmName} is defeated with ${relativeNoCount.toFixed( 1, - )}% of 👎 votes (${yesCount} 👍 / ${noCount} 👎): ${proposalLink}`, + )}% of 👎 votes (${yesVotesFormatted} 👍 / ${noVotesFormatted} 👎): ${proposalLink}`, + actions: { + type: DappMessageActionType.LINK, + links: [ + { + label: 'View Proposal', + url: proposalLink, + }, + ], + }, }; } return { diff --git a/src/realms-sdk.ts b/src/realms-sdk.ts index cee0e8b..b647bd8 100644 --- a/src/realms-sdk.ts +++ b/src/realms-sdk.ts @@ -15,7 +15,7 @@ import { import { RealmsRestService } from './realms-rest-service'; import { HttpService } from '@nestjs/axios'; import { chunk, compact, keyBy, uniqBy, zip } from 'lodash'; -import { parseMintAccountData } from './realms-cache'; +import { parseMintAccountData, RealmMints } from './realms-cache'; import { sleepSecs } from 'twitter-api-v2/dist/v1/media-helpers.v1'; const connection = new Connection(process.env.DIALECT_SDK_SOLANA_RPC_URL!); @@ -111,15 +111,18 @@ export async function fetchRealmsWithMints(realms: ProgramAccount[]) { (it) => it.address.toBase58(), ); - const realmsWithMints = realms.map((it) => ({ - ...it, - mints: { - mint: parsedRawMints[it.account.communityMint.toBase58()]?.parsed, - councilMint: - it.account.config.councilMint && - parsedRawMints[it.account.config.councilMint.toBase58()]?.parsed, - }, - })); + const realmsWithMints: ProgramAccount[] = realms.map( + (it) => ({ + ...it, + account: { + ...it.account, + mint: parsedRawMints[it.account.communityMint.toBase58()]?.parsed, + councilMint: + it.account.config.councilMint && + parsedRawMints[it.account.config.councilMint.toBase58()]?.parsed, + }, + }), + ); return realmsWithMints; }