Skip to content

Commit 309dd30

Browse files
authored
feat: new MND adoption stats (#44)
1 parent 2459a46 commit 309dd30

File tree

5 files changed

+121
-44
lines changed

5 files changed

+121
-44
lines changed

src/components/Licenses/LicensesPageHeader.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,27 @@ function LicensesPageHeader({
7070
);
7171
const [rewardsPoA, isLoadingRewardsPoA] = useAwait(rewardsPoAPromise);
7272

73-
const earnedAmountPoA = useMemo(() => licenses.reduce((acc, license) => acc + license.totalClaimedAmount, 0n), [licenses]);
73+
const releasedAmountPoA = useMemo(
74+
() => licenses.reduce((acc, license) => acc + license.totalClaimedAmount, 0n),
75+
[licenses],
76+
);
77+
const awbAmountPoA = useMemo(
78+
() => licenses.reduce((acc, license) => acc + (license.type === 'ND' ? 0n : license.awbBalance), 0n),
79+
[licenses],
80+
);
81+
const earnedAmountPoA = useMemo(
82+
// Actual amount claimed in wallet
83+
() => (releasedAmountPoA > awbAmountPoA ? releasedAmountPoA - awbAmountPoA : 0n),
84+
[releasedAmountPoA, awbAmountPoA],
85+
);
7486
const futureClaimableR1AmountPoA: bigint = useMemo(
7587
() => licenses.reduce((acc, license) => acc + license.remainingAmount, 0n),
7688
[licenses],
7789
);
90+
const hasMndOrGndLicense = useMemo(
91+
() => licenses.some((license) => license.type === 'MND' || license.type === 'GND'),
92+
[licenses],
93+
);
7894

7995
const futureClaimableUsdPoA: number = useMemo(() => {
8096
if (!r1PriceUsd) return 0;
@@ -350,6 +366,15 @@ function LicensesPageHeader({
350366
: fBI(earnedAmountPoA, 18),
351367
)}
352368

369+
{hasMndOrGndLicense &&
370+
getValueWithLabel(
371+
'In AWB ($R1)',
372+
awbAmountPoA < 1000000000000000000000n
373+
? parseFloat(Number(formatUnits(awbAmountPoA ?? 0n, 18)).toFixed(2))
374+
: fBI(awbAmountPoA, 18),
375+
awbAmountPoA > 0n ? 'text-orange-500' : undefined,
376+
)}
377+
353378
{getValueWithLabel('Future Max Claimable ($R1)', fBI(futureClaimableR1AmountPoA, 18))}
354379

355380
{getValueWithLabel('Max Potential Value ($)', fN(futureClaimableUsdPoA))}

src/lib/licenses.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,21 @@ export const getLicensesWithNodesAndRewards = (
6464
const licensesWithNodesWithRewards: types.License[] = licenses.map((license) => {
6565
const availability: Promise<types.OraclesAvailabilityResult> = result.then((result) => result[license.nodeAddress]);
6666
let rewards: Promise<bigint | undefined>;
67+
let rewardsBreakdown: Promise<types.MndRewardsBreakdown | undefined> | undefined;
6768

6869
switch (license.type) {
6970
case 'ND':
7071
rewards = getNdRewards(license, availability, config.ndVestingEpochs);
7172
break;
7273

7374
case 'GND':
74-
case 'MND':
75-
rewards = getMndOrGndRewards(license, availability, publicClient);
75+
case 'MND': {
76+
rewardsBreakdown = getMndOrGndRewardsBreakdown(license, availability, publicClient);
77+
rewards = rewardsBreakdown.then((breakdown) =>
78+
breakdown ? breakdown.rewardsAmount + breakdown.carryoverAmount : undefined,
79+
);
7680
break;
81+
}
7782

7883
default:
7984
rewards = Promise.resolve(undefined);
@@ -83,6 +88,7 @@ export const getLicensesWithNodesAndRewards = (
8388
...license,
8489
isLinked: true as const,
8590
rewards,
91+
rewardsBreakdown,
8692
alias: availability.then(({ node_alias }) => node_alias),
8793
isOnline: availability.then(({ node_is_online }) => node_is_online),
8894
epochs: availability.then(({ epochs }) => epochs),
@@ -155,11 +161,11 @@ const getNdRewards = async (
155161
return rewards_amount;
156162
};
157163

158-
const getMndOrGndRewards = async (
164+
const getMndOrGndRewardsBreakdown = async (
159165
license: BaseMNDLicense | BaseGNDLicense,
160166
availability: Promise<types.OraclesAvailabilityResult>,
161167
publicClient: PublicClient,
162-
): Promise<bigint | undefined> => {
168+
): Promise<types.MndRewardsBreakdown | undefined> => {
163169
const currentEpoch = getCurrentEpoch();
164170
const firstEpochToClaim =
165171
license.lastClaimEpoch >= license.firstMiningEpoch ? Number(license.lastClaimEpoch) : Number(license.firstMiningEpoch);
@@ -168,7 +174,11 @@ const getMndOrGndRewards = async (
168174
const { epochs, epochs_vals } = await availability;
169175

170176
if (currentEpoch < license.firstMiningEpoch || !epochsToClaim) {
171-
return 0n;
177+
return {
178+
rewardsAmount: 0n,
179+
carryoverAmount: 0n,
180+
withheldAmount: 0n,
181+
};
172182
}
173183

174184
if (epochsToClaim !== epochs?.length || epochsToClaim !== epochs_vals?.length) {
@@ -195,5 +205,9 @@ const getMndOrGndRewards = async (
195205
if (result.length !== 1) {
196206
throw new Error('Invalid rewards calculation result');
197207
}
198-
return result[0].rewardsAmount + result[0].carryoverAmount;
208+
return {
209+
rewardsAmount: result[0].rewardsAmount,
210+
carryoverAmount: result[0].carryoverAmount,
211+
withheldAmount: result[0].withheldAmount,
212+
};
199213
};

src/shared/Licenses/LicenseCardDetails.tsx

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { arrayAverage, getValueWithLabel, throttledToastOracleError } from '@lib
66
import { CardHorizontal } from '@shared/cards/CardHorizontal';
77
import SyncingOraclesTag from '@shared/SyncingOraclesTag';
88
import { cloneElement, useMemo } from 'react';
9-
import { License } from 'typedefs/blockchain';
9+
import { License, MndRewardsBreakdown } from 'typedefs/blockchain';
1010
import { formatUnits } from 'viem';
1111

1212
const nodePerformanceItems = [
@@ -37,6 +37,12 @@ export const LicenseCardDetails = ({
3737
}) => {
3838
const [rewardsPoA, isLoadingRewardsPoA] = useAwait(license.isLinked ? license.rewards : 0n);
3939
const rewardsPoAI = license.type === 'ND' ? license.r1PoaiRewards : 0n;
40+
const [rewardsBreakdown] = useAwait<MndRewardsBreakdown | undefined>(
41+
license.isLinked && license.type !== 'ND' ? license.rewardsBreakdown : undefined,
42+
);
43+
44+
const hasMndBreakdown = !!rewardsBreakdown && rewardsBreakdown.carryoverAmount > 0n;
45+
const formatR1 = (value: bigint) => parseFloat(Number(formatUnits(value, 18)).toFixed(4)).toLocaleString();
4046

4147
const nodePerformancePromise: Promise<{
4248
epochs: number[];
@@ -166,10 +172,15 @@ export const LicenseCardDetails = ({
166172
<SyncingOraclesTag />
167173
) : (
168174
<div className="flex items-end gap-1.5">
169-
<div className="text-primary text-lg leading-none font-semibold">
170-
{parseFloat(Number(formatUnits(rewardsPoA ?? 0n, 18)).toFixed(4)).toLocaleString()}
171-
172-
<span className="text-slate-400"> $R1</span>
175+
<div className="col gap-1.5">
176+
<div className="text-primary text-lg leading-none font-semibold">
177+
{formatR1(rewardsPoA ?? 0n)} <span className="text-slate-400"> $R1</span>
178+
</div>
179+
{license.type !== 'ND' && hasMndBreakdown && (
180+
<div className="text-right text-[11px] leading-none text-slate-500">
181+
includes {formatR1(rewardsBreakdown.carryoverAmount)} carryover
182+
</div>
183+
)}
173184
</div>
174185
</div>
175186
)}
@@ -234,12 +245,15 @@ export const LicenseCardDetails = ({
234245
<div className="col gap-2.5">
235246
<div className="text-sm font-medium text-slate-500">Adoption Withheld Buffer</div>
236247

237-
<div className="text-lg leading-none font-semibold text-orange-500">
238-
{parseFloat(
239-
Number(formatUnits(license.awbBalance ?? 0n, 18)).toFixed(4),
240-
).toLocaleString()}
241-
242-
<span className="text-slate-400"> $R1</span>
248+
<div className="col gap-1.5">
249+
<div className="text-lg leading-none font-semibold text-orange-500">
250+
{formatR1(license.awbBalance ?? 0n)} <span className="text-slate-400"> $R1</span>
251+
</div>
252+
{rewardsBreakdown && (
253+
<div className="text-[11px] leading-none text-slate-500">
254+
+ {formatR1(rewardsBreakdown.withheldAmount)} with current claim
255+
</div>
256+
)}
243257
</div>
244258
</div>
245259
</div>

src/shared/Licenses/LicenseCardHeader.tsx

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { FunctionComponent, PropsWithChildren, useEffect, useMemo, useState } fr
1414
import toast from 'react-hot-toast';
1515
import { RiCpuLine, RiExchange2Line, RiFireLine, RiLink, RiLinkUnlink, RiMoreFill, RiTimeLine } from 'react-icons/ri';
1616
import { Link } from 'react-router-dom';
17-
import { License } from 'typedefs/blockchain';
17+
import { License, MndRewardsBreakdown } from 'typedefs/blockchain';
1818
import { formatUnits } from 'viem';
1919
import { useAccount, usePublicClient } from 'wagmi';
2020
import { LicenseCardNode } from './LicenseCardNode';
@@ -42,6 +42,7 @@ export const LicenseCardHeader = ({
4242
// Rewards
4343
const [rewardsTotal, setRewardsTotal] = useState<bigint | undefined>();
4444
const [licenseRewardsPoA, setLicenseRewardsPoA] = useState<bigint | undefined>();
45+
const [licenseRewardsBreakdown, setLicenseRewardsBreakdown] = useState<MndRewardsBreakdown | undefined>();
4546
const licenseRewardsPoAI: bigint | undefined = license.type === 'ND' ? license.r1PoaiRewards || undefined : undefined;
4647

4748
// Used to restrict actions until all data is loaded
@@ -74,8 +75,12 @@ export const LicenseCardHeader = ({
7475
if (license.isLinked) {
7576
(async () => {
7677
try {
77-
const rewardsPoA = await license.rewards;
78+
const [rewardsPoA, rewardsBreakdown] = await Promise.all([
79+
license.rewards,
80+
license.type !== 'ND' ? license.rewardsBreakdown : Promise.resolve(undefined),
81+
]);
7882
setLicenseRewardsPoA(rewardsPoA);
83+
setLicenseRewardsBreakdown(rewardsBreakdown);
7984

8085
setRewardsTotal(
8186
rewardsPoA !== undefined || licenseRewardsPoAI !== undefined
@@ -109,37 +114,41 @@ export const LicenseCardHeader = ({
109114
</Link>
110115
);
111116

112-
const getLicenseUsageStats = () => (
113-
<div className="row gap-2.5 text-sm leading-none font-medium">
114-
<div>
115-
{fBI(license.totalClaimedAmount, 18)}/{fBI(license.totalAssignedAmount, 18)}
116-
</div>
117+
const getLicenseUsageStats = () => {
118+
const walletClaimedAmount =
119+
license.type === 'ND'
120+
? license.totalClaimedAmount
121+
: license.totalClaimedAmount > license.awbBalance
122+
? license.totalClaimedAmount - license.awbBalance
123+
: 0n;
124+
125+
return (
126+
<div className="row gap-2.5 text-sm leading-none font-medium">
127+
<div>
128+
{fBI(walletClaimedAmount, 18)}/{fBI(license.totalAssignedAmount, 18)}
129+
</div>
117130

118-
<div className="flex h-1 w-full overflow-hidden rounded-full bg-gray-300">
119-
<div
120-
className="bg-primary rounded-full transition-all"
121-
style={{
122-
width: `${Number(
123-
((license.totalClaimedAmount - (license.type !== 'ND' ? license.awbBalance : 0n)) * 100n) /
124-
license.totalAssignedAmount,
125-
)}%`,
126-
}}
127-
></div>
128-
{license.type !== 'ND' && (
131+
<div className="flex h-1 w-full overflow-hidden rounded-full bg-gray-300">
129132
<div
130-
className="rounded-end bg-orange-500 transition-all"
133+
className="bg-primary rounded-full transition-all"
131134
style={{
132-
width: `${Number((license.awbBalance * 100n) / license.totalAssignedAmount)}%`,
135+
width: `${Number((walletClaimedAmount * 100n) / license.totalAssignedAmount)}%`,
133136
}}
134137
></div>
135-
)}
136-
</div>
138+
{license.type !== 'ND' && (
139+
<div
140+
className="rounded-end bg-orange-500 transition-all"
141+
style={{
142+
width: `${Number((license.awbBalance * 100n) / license.totalAssignedAmount)}%`,
143+
}}
144+
></div>
145+
)}
146+
</div>
137147

138-
<div>
139-
{parseFloat(((Number(license.totalClaimedAmount) / Number(license.totalAssignedAmount)) * 100).toFixed(2))}%
148+
<div>{parseFloat(((Number(walletClaimedAmount) / Number(license.totalAssignedAmount)) * 100).toFixed(2))}%</div>
140149
</div>
141-
</div>
142-
);
150+
);
151+
};
143152

144153
const getLicenseCard = () => (
145154
<LicenseSmallCard>
@@ -163,8 +172,10 @@ export const LicenseCardHeader = ({
163172

164173
const nRewardsPoA: number = Number(formatUnits(licenseRewardsPoA ?? 0n, 18));
165174
const nRewardsPoAI: number = Number(formatUnits(licenseRewardsPoAI ?? 0n, 18));
175+
const nCarryover: number = Number(formatUnits(licenseRewardsBreakdown?.carryoverAmount ?? 0n, 18));
166176

167177
const hasRewards = nRewardsPoA > 0 || nRewardsPoAI > 0;
178+
const showMndBreakdown = license.type !== 'ND' && nCarryover > 0;
168179

169180
if (!isLoadingRewards && !hasRewards) {
170181
return undefined;
@@ -187,6 +198,11 @@ export const LicenseCardHeader = ({
187198
<div className="col gap-1.5 text-sm">
188199
<div className="leading-none font-medium text-slate-500">PoA</div>
189200
<div className="text-primary leading-none font-semibold">{fN(nRewardsPoA)}</div>
201+
{showMndBreakdown && (
202+
<div className="text-[11px] leading-none text-slate-500">
203+
includes {fN(nCarryover)} carryover
204+
</div>
205+
)}
190206
</div>
191207
)}
192208

src/typedefs/blockchain.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ import { ApplicationStatus } from './profile';
33
type R1Address = `0xai${string}`;
44
type EthAddress = `0x${string}`;
55

6+
type MndRewardsBreakdown = {
7+
rewardsAmount: bigint;
8+
carryoverAmount: bigint;
9+
withheldAmount: bigint;
10+
};
11+
612
type BaseLicense = {
713
readonly licenseId: bigint;
814
nodeAddress: EthAddress;
@@ -23,6 +29,7 @@ type LicenseWithNodeInfo = BaseLicense & {
2329
isLinked: true;
2430
alias: Promise<string | undefined>;
2531
rewards: Promise<bigint | undefined>;
32+
rewardsBreakdown?: Promise<MndRewardsBreakdown | undefined>;
2633
isOnline: Promise<boolean>;
2734
epochs: Promise<number[]>;
2835
epochsAvailabilities: Promise<number[]>;
@@ -137,6 +144,7 @@ export type {
137144
GNDLicense,
138145
License,
139146
MNDLicense,
147+
MndRewardsBreakdown,
140148
NDLicense,
141149
OraclesAvailabilityResult,
142150
OraclesDefaultResult,

0 commit comments

Comments
 (0)