Skip to content

Commit 3a040a6

Browse files
authored
Merge pull request #17 from Ratio1/develop
Develop
2 parents f16b8d2 + c3a270a commit 3a040a6

File tree

6 files changed

+222
-136
lines changed

6 files changed

+222
-136
lines changed
Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import ErrorComponent from '@/app/server-components/shared/ErrorComponent';
66
import config from '@/config';
77
import { getNodeAvailability } from '@/lib/actions';
88
import { getNodeLicenseDetails } from '@/lib/api/blockchain';
9-
import { isZeroAddress } from '@/lib/utils';
9+
import { internalNodeAddressToEthAddress, isZeroAddress } from '@/lib/utils';
1010
import * as types from '@/typedefs/blockchain';
1111
import { RiCloseLine } from 'react-icons/ri';
1212
import { isAddress } from 'viem';
@@ -18,18 +18,40 @@ const errorMetadata = {
1818
},
1919
};
2020

21+
const resolveNodeEthAddress = (nodeAddress?: string): types.EthAddress | null => {
22+
if (!nodeAddress) {
23+
return null;
24+
}
25+
26+
if (nodeAddress.startsWith('0xai_')) {
27+
try {
28+
const ethAddress = internalNodeAddressToEthAddress(nodeAddress);
29+
return isZeroAddress(ethAddress) ? null : ethAddress;
30+
} catch (error) {
31+
return null;
32+
}
33+
}
34+
35+
if (!isAddress(nodeAddress) || isZeroAddress(nodeAddress)) {
36+
return null;
37+
}
38+
39+
return nodeAddress;
40+
};
41+
2142
export async function generateMetadata({ params }) {
22-
const { nodeEthAddr } = await params;
43+
const { nodeAddr } = await params;
44+
const resolvedNodeEthAddr = resolveNodeEthAddress(nodeAddr);
2345

24-
if (!nodeEthAddr || !isAddress(nodeEthAddr) || isZeroAddress(nodeEthAddr)) {
25-
console.log(`[Node Page] Invalid node address: ${nodeEthAddr}`);
46+
if (!resolvedNodeEthAddr) {
47+
console.log(`[Node Page] Invalid node address: ${nodeAddr}`);
2648
return errorMetadata;
2749
}
2850

2951
let nodeResponse: types.OraclesAvailabilityResult & types.OraclesDefaultResult;
3052

3153
try {
32-
({ nodeResponse } = await fetchLicenseDetailsAndNodeAvailability(nodeEthAddr, config.environment));
54+
({ nodeResponse } = await fetchLicenseDetailsAndNodeAvailability(resolvedNodeEthAddr, config.environment));
3355
} catch (error) {
3456
console.error(error);
3557
return errorMetadata;
@@ -54,11 +76,12 @@ const fetchLicenseDetailsAndNodeAvailability = async (
5476
nodeResponse: types.OraclesAvailabilityResult & types.OraclesDefaultResult;
5577
}> => {
5678
let nodeAddress: types.EthAddress,
57-
totalAssignedAmount: bigint,
58-
totalClaimedAmount: bigint,
59-
lastClaimEpoch: bigint,
60-
assignTimestamp: bigint,
61-
lastClaimOracle: types.EthAddress,
79+
totalAssignedAmount: bigint,
80+
totalClaimedAmount: bigint,
81+
firstMiningEpoch: bigint | undefined,
82+
lastClaimEpoch: bigint,
83+
assignTimestamp: bigint,
84+
lastClaimOracle: types.EthAddress,
6285
isBanned: boolean,
6386
licenseId: bigint,
6487
licenseType: 'ND' | 'MND' | 'GND' | undefined,
@@ -67,12 +90,13 @@ const fetchLicenseDetailsAndNodeAvailability = async (
6790

6891
try {
6992
({
70-
nodeAddress,
71-
totalAssignedAmount,
72-
totalClaimedAmount,
73-
lastClaimEpoch,
74-
assignTimestamp,
75-
lastClaimOracle,
93+
nodeAddress,
94+
totalAssignedAmount,
95+
totalClaimedAmount,
96+
firstMiningEpoch,
97+
lastClaimEpoch,
98+
assignTimestamp,
99+
lastClaimOracle,
76100
isBanned,
77101
licenseId,
78102
licenseType,
@@ -90,12 +114,13 @@ const fetchLicenseDetailsAndNodeAvailability = async (
90114
}
91115

92116
const license: types.License = {
93-
nodeAddress,
94-
totalAssignedAmount,
95-
totalClaimedAmount,
96-
lastClaimEpoch,
97-
assignTimestamp,
98-
lastClaimOracle,
117+
nodeAddress,
118+
totalAssignedAmount,
119+
totalClaimedAmount,
120+
firstMiningEpoch,
121+
lastClaimEpoch,
122+
assignTimestamp,
123+
lastClaimOracle,
99124
isBanned,
100125
owner,
101126
r1PoaiRewards,
@@ -113,9 +138,10 @@ const fetchLicenseDetailsAndNodeAvailability = async (
113138
};
114139

115140
export default async function NodePage({ params }) {
116-
const { nodeEthAddr } = await params;
141+
const { nodeAddr } = await params;
142+
const resolvedNodeEthAddr = resolveNodeEthAddress(nodeAddr);
117143

118-
if (!nodeEthAddr || !isAddress(nodeEthAddr) || isZeroAddress(nodeEthAddr)) {
144+
if (!resolvedNodeEthAddr) {
119145
return <NotFound />;
120146
}
121147

@@ -127,7 +153,7 @@ export default async function NodePage({ params }) {
127153

128154
try {
129155
({ license, licenseId, licenseType, owner, nodeResponse } = await fetchLicenseDetailsAndNodeAvailability(
130-
nodeEthAddr,
156+
resolvedNodeEthAddr,
131157
config.environment,
132158
));
133159
} catch (error: any) {

lib/api/blockchain.ts

Lines changed: 91 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use server';
22

33
import { ERC20Abi } from '@/blockchain/ERC20';
4+
import { MNDContractAbi } from '@/blockchain/MNDContract';
45
import { NDContractAbi } from '@/blockchain/NDContract';
56
import { ReaderAbi } from '@/blockchain/Reader';
67
import config, { getCurrentEpoch, getEpochStartTimestamp, getNextEpochTimestamp } from '@/config';
@@ -66,10 +67,26 @@ export async function getNodeLicenseDetails(nodeAddress: types.EthAddress): Prom
6667
functionName: 'getNodeLicenseDetails',
6768
args: [nodeAddress],
6869
})
69-
.then((result) => ({
70-
...result,
71-
licenseType: [undefined, 'ND', 'MND', 'GND'][result.licenseType] as 'ND' | 'MND' | 'GND' | undefined,
72-
}));
70+
.then(async (result) => {
71+
const licenseType = [undefined, 'ND', 'MND', 'GND'][result.licenseType] as 'ND' | 'MND' | 'GND' | undefined;
72+
let firstMiningEpoch: bigint | undefined;
73+
if (licenseType === 'MND') {
74+
firstMiningEpoch = (
75+
await publicClient.readContract({
76+
address: config.mndContractAddress,
77+
abi: MNDContractAbi,
78+
functionName: 'licenses',
79+
args: [result.licenseId],
80+
})
81+
)[3];
82+
}
83+
84+
return {
85+
...result,
86+
licenseType,
87+
firstMiningEpoch,
88+
};
89+
});
7390
}
7491

7592
export async function getLicense(licenseType: 'ND' | 'MND' | 'GND', licenseId: number | string): Promise<types.License> {
@@ -109,7 +126,7 @@ export async function getLicense(licenseType: 'ND' | 'MND' | 'GND', licenseId: n
109126
functionName: 'getMndLicenseDetails',
110127
args: [BigInt(licenseId)],
111128
})
112-
.then((license) => {
129+
.then(async (license) => {
113130
const isLinked = !isZeroAddress(license.nodeAddress);
114131
const licenseType = [undefined, 'ND', 'MND', 'GND'][license.licenseType] as 'ND' | 'MND' | 'GND' | undefined;
115132
if (licenseType === undefined) {
@@ -118,11 +135,20 @@ export async function getLicense(licenseType: 'ND' | 'MND' | 'GND', licenseId: n
118135
if (licenseType !== 'MND' && licenseType !== 'GND') {
119136
throw new Error('Invalid license type');
120137
}
138+
const firstMiningEpoch = (
139+
await publicClient.readContract({
140+
address: config.mndContractAddress,
141+
abi: MNDContractAbi,
142+
functionName: 'licenses',
143+
args: [BigInt(licenseId)],
144+
})
145+
)[3];
121146

122147
return {
123148
...license,
124149
licenseType,
125150
isLinked,
151+
firstMiningEpoch,
126152
};
127153
});
128154
}
@@ -333,7 +359,7 @@ const getNdLicenseRewards = async (license: types.License, epochs: number[], epo
333359
};
334360

335361
const getMndLicenseRewards = async (license: types.License, epochs: number[], epochs_vals: number[]): Promise<bigint> => {
336-
return calculateLicenseRewards(license, epochs, epochs_vals, config.mndVestingEpochs, config.mndCliffEpochs);
362+
return calculateMndLicenseRewards(license, epochs, epochs_vals);
337363
};
338364

339365
const getGndLicenseRewards = async (license: types.License, epochs: number[], epochs_vals: number[]): Promise<bigint> => {
@@ -387,3 +413,62 @@ const calculateLicenseRewards = async (
387413
const maxRemainingClaimAmount = license.totalAssignedAmount - license.totalClaimedAmount;
388414
return rewards_amount < maxRemainingClaimAmount ? rewards_amount : maxRemainingClaimAmount;
389415
};
416+
417+
const calculateMndLicenseRewards = async (license: types.License, epochs: number[], epochs_vals: number[]): Promise<bigint> => {
418+
const currentEpoch = getCurrentEpoch();
419+
const firstMiningEpoch = license.firstMiningEpoch;
420+
421+
if (firstMiningEpoch === undefined) {
422+
throw new Error('First mining epoch is undefined for MND license');
423+
}
424+
425+
const firstEpochToClaim =
426+
Number(license.lastClaimEpoch) >= Number(firstMiningEpoch) ? Number(license.lastClaimEpoch) : Number(firstMiningEpoch);
427+
const epochsToClaim = currentEpoch - firstEpochToClaim;
428+
429+
if (currentEpoch < Number(firstMiningEpoch) || !epochsToClaim) {
430+
return 0n;
431+
}
432+
433+
if (epochs.length && epochs[0] < firstEpochToClaim) {
434+
const start = firstEpochToClaim - epochs[0];
435+
epochs = epochs.slice(start);
436+
epochs_vals = epochs_vals.slice(start);
437+
}
438+
439+
if (epochsToClaim !== epochs.length || epochsToClaim !== epochs_vals.length) {
440+
console.error(
441+
`Invalid epochs array length. Received ${epochs.length} epochs, but there are ${epochsToClaim} epochs to claim.`,
442+
);
443+
return 0n;
444+
}
445+
446+
let rewards_amount = 0n;
447+
const logisticPlateau = 300_505239501691000000n; // 300.50
448+
const licensePlateau = (license.totalAssignedAmount * BigInt(1e18)) / logisticPlateau;
449+
450+
for (let i = 0; i < epochsToClaim; i++) {
451+
const maxRewardsPerEpoch = calculateMndMaxEpochRelease(epochs[i], firstMiningEpoch, licensePlateau);
452+
rewards_amount += (maxRewardsPerEpoch * BigInt(epochs_vals[i])) / 255n;
453+
}
454+
455+
const maxRemainingClaimAmount = license.totalAssignedAmount - license.totalClaimedAmount;
456+
return rewards_amount > maxRemainingClaimAmount ? maxRemainingClaimAmount : rewards_amount;
457+
};
458+
459+
const calculateMndMaxEpochRelease = (epoch: number, firstMiningEpoch: bigint, licensePlateau: bigint): bigint => {
460+
let x = epoch - Number(firstMiningEpoch);
461+
if (x > config.mndVestingEpochs) {
462+
x = config.mndVestingEpochs;
463+
}
464+
const frac = logisticFraction(x);
465+
return (licensePlateau * BigInt(frac * 1e18)) / BigInt(1e18);
466+
};
467+
468+
const logisticFraction = (x: number): number => {
469+
const length = config.mndVestingEpochs;
470+
const k = 5.0;
471+
const midPrc = 0.7;
472+
const midpoint = length * midPrc;
473+
return 1.0 / (1.0 + Math.exp((-k * (x - midpoint)) / length));
474+
};

0 commit comments

Comments
 (0)