Not assigned
diff --git a/app/server-components/Licenses/LicenseRewardsPoA.tsx b/app/server-components/Licenses/LicenseRewardsPoA.tsx
index 30ff666..34f4abe 100644
--- a/app/server-components/Licenses/LicenseRewardsPoA.tsx
+++ b/app/server-components/Licenses/LicenseRewardsPoA.tsx
@@ -8,10 +8,12 @@ import { SmallTag } from '../shared/SmallTag';
export default async function LicenseRewardsPoA({
license,
licenseType,
+ licenseId,
getNodeAvailability,
}: {
license: types.License;
licenseType: 'ND' | 'MND' | 'GND';
+ licenseId: string;
getNodeAvailability: () => Promise<(types.OraclesAvailabilityResult & types.OraclesDefaultResult) | undefined>;
}) {
try {
@@ -21,9 +23,7 @@ export default async function LicenseRewardsPoA({
await getNodeAvailability();
if (!nodeResponse) {
- return (
-
Error loading rewards} isSmall />
- );
+ return null;
}
const firstCheckEpoch: number = getLicenseFirstCheckEpoch(license.assignTimestamp);
@@ -32,6 +32,7 @@ export default async function LicenseRewardsPoA({
rewards = await getLicenseRewards(
license,
licenseType,
+ BigInt(licenseId),
nodeResponse.epochs.slice(lastClaimEpoch - firstCheckEpoch),
nodeResponse.epochs_vals.slice(lastClaimEpoch - firstCheckEpoch),
);
diff --git a/app/server-components/main-cards/LicenseCard.tsx b/app/server-components/main-cards/LicenseCard.tsx
index 9f03dbd..f828975 100644
--- a/app/server-components/main-cards/LicenseCard.tsx
+++ b/app/server-components/main-cards/LicenseCard.tsx
@@ -115,11 +115,12 @@ export default async function LicenseCard({ license, licenseType, licenseId, own
}>
-
+
{licenseType === 'ND' && (
diff --git a/blockchain/MNDContract.ts b/blockchain/MNDContract.ts
index 75651df..9570c06 100644
--- a/blockchain/MNDContract.ts
+++ b/blockchain/MNDContract.ts
@@ -1,4 +1,19 @@
export const MNDContractAbi = [
+ {
+ inputs: [],
+ name: 'AssignedAmountExceedsLimit',
+ type: 'error',
+ },
+ {
+ inputs: [],
+ name: 'CannotReassignWithin24Hours',
+ type: 'error',
+ },
+ {
+ inputs: [],
+ name: 'CannotUnlinkBeforeClaimingRewards',
+ type: 'error',
+ },
{
inputs: [],
name: 'ERC721EnumerableForbiddenBatchMint',
@@ -133,16 +148,71 @@ export const MNDContractAbi = [
name: 'ExpectedPause',
type: 'error',
},
+ {
+ inputs: [],
+ name: 'IncorrectNumberOfParams',
+ type: 'error',
+ },
+ {
+ inputs: [],
+ name: 'InvalidEpochs',
+ type: 'error',
+ },
{
inputs: [],
name: 'InvalidInitialization',
type: 'error',
},
+ {
+ inputs: [],
+ name: 'InvalidLicensePower',
+ type: 'error',
+ },
+ {
+ inputs: [],
+ name: 'InvalidNodeAddress',
+ type: 'error',
+ },
+ {
+ inputs: [],
+ name: 'InvalidNodeAddressForRewards',
+ type: 'error',
+ },
+ {
+ inputs: [],
+ name: 'MaxTokenSupplyReached',
+ type: 'error',
+ },
+ {
+ inputs: [],
+ name: 'MaxTotalAssignedTokensReached',
+ type: 'error',
+ },
+ {
+ inputs: [],
+ name: 'MismatchedInputArraysLength',
+ type: 'error',
+ },
+ {
+ inputs: [],
+ name: 'NodeAddressAlreadyRegistered',
+ type: 'error',
+ },
+ {
+ inputs: [],
+ name: 'NonexistentTokenURI',
+ type: 'error',
+ },
{
inputs: [],
name: 'NotInitializing',
type: 'error',
},
+ {
+ inputs: [],
+ name: 'NotLicenseOwner',
+ type: 'error',
+ },
{
inputs: [
{
@@ -271,6 +341,16 @@ export const MNDContractAbi = [
name: 'ReentrancyGuardReentrantCall',
type: 'error',
},
+ {
+ inputs: [],
+ name: 'SoulboundNonTransferableToken',
+ type: 'error',
+ },
+ {
+ inputs: [],
+ name: 'TimestampBeforeStartEpoch',
+ type: 'error',
+ },
{
anonymous: false,
inputs: [
@@ -469,6 +549,18 @@ export const MNDContractAbi = [
name: 'rewardsAmount',
type: 'uint256',
},
+ {
+ indexed: false,
+ internalType: 'uint256',
+ name: 'carryoverAmount',
+ type: 'uint256',
+ },
+ {
+ indexed: false,
+ internalType: 'uint256',
+ name: 'withheldAmount',
+ type: 'uint256',
+ },
{
indexed: false,
internalType: 'uint256',
@@ -476,7 +568,7 @@ export const MNDContractAbi = [
type: 'uint256',
},
],
- name: 'RewardsClaimed',
+ name: 'RewardsClaimedV2',
type: 'event',
},
{
@@ -599,6 +691,19 @@ export const MNDContractAbi = [
stateMutability: 'nonpayable',
type: 'function',
},
+ {
+ inputs: [],
+ name: 'adoptionOracle',
+ outputs: [
+ {
+ internalType: 'contract IAdoptionOracle',
+ name: '',
+ type: 'address',
+ },
+ ],
+ stateMutability: 'view',
+ type: 'function',
+ },
{
inputs: [
{
@@ -617,6 +722,25 @@ export const MNDContractAbi = [
stateMutability: 'nonpayable',
type: 'function',
},
+ {
+ inputs: [
+ {
+ internalType: 'uint256',
+ name: '',
+ type: 'uint256',
+ },
+ ],
+ name: 'awbBalances',
+ outputs: [
+ {
+ internalType: 'uint256',
+ name: '',
+ type: 'uint256',
+ },
+ ],
+ stateMutability: 'view',
+ type: 'function',
+ },
{
inputs: [
{
@@ -693,6 +817,16 @@ export const MNDContractAbi = [
name: 'rewardsAmount',
type: 'uint256',
},
+ {
+ internalType: 'uint256',
+ name: 'carryoverAmount',
+ type: 'uint256',
+ },
+ {
+ internalType: 'uint256',
+ name: 'withheldAmount',
+ type: 'uint256',
+ },
],
internalType: 'struct ComputeRewardsResult[]',
name: '',
@@ -1067,6 +1201,29 @@ export const MNDContractAbi = [
stateMutability: 'view',
type: 'function',
},
+ {
+ inputs: [
+ {
+ internalType: 'uint256[]',
+ name: 'licenseIds',
+ type: 'uint256[]',
+ },
+ {
+ internalType: 'address[]',
+ name: 'newNodeAddresses',
+ type: 'address[]',
+ },
+ {
+ internalType: 'bytes',
+ name: 'signature',
+ type: 'bytes',
+ },
+ ],
+ name: 'linkMultiNode',
+ outputs: [],
+ stateMutability: 'nonpayable',
+ type: 'function',
+ },
{
inputs: [
{
@@ -1090,6 +1247,19 @@ export const MNDContractAbi = [
stateMutability: 'nonpayable',
type: 'function',
},
+ {
+ inputs: [],
+ name: 'maxCarryoverReleaseFactor',
+ outputs: [
+ {
+ internalType: 'uint8',
+ name: '',
+ type: 'uint8',
+ },
+ ],
+ stateMutability: 'view',
+ type: 'function',
+ },
{
inputs: [],
name: 'name',
@@ -1251,6 +1421,19 @@ export const MNDContractAbi = [
stateMutability: 'nonpayable',
type: 'function',
},
+ {
+ inputs: [
+ {
+ internalType: 'address',
+ name: 'adoptionOracle_',
+ type: 'address',
+ },
+ ],
+ name: 'setAdoptionOracle',
+ outputs: [],
+ stateMutability: 'nonpayable',
+ type: 'function',
+ },
{
inputs: [
{
@@ -1338,6 +1521,19 @@ export const MNDContractAbi = [
stateMutability: 'nonpayable',
type: 'function',
},
+ {
+ inputs: [
+ {
+ internalType: 'uint8',
+ name: 'newFactor',
+ type: 'uint8',
+ },
+ ],
+ name: 'setMaxCarryoverReleaseFactor',
+ outputs: [],
+ stateMutability: 'nonpayable',
+ type: 'function',
+ },
{
inputs: [
{
diff --git a/lib/api/blockchain.ts b/lib/api/blockchain.ts
index a50c12d..5fda8fb 100644
--- a/lib/api/blockchain.ts
+++ b/lib/api/blockchain.ts
@@ -10,6 +10,7 @@ import console from 'console';
import { differenceInSeconds } from 'date-fns';
import Moralis from 'moralis';
import { EvmAddress, EvmChain } from 'moralis/common-evm-utils';
+import type { ReadContractReturnType } from 'viem';
import { isZeroAddress } from '../utils';
import { getPublicClient } from './client';
@@ -70,7 +71,7 @@ export async function getNodeLicenseDetails(nodeAddress: types.EthAddress): Prom
.then(async (result) => {
const licenseType = [undefined, 'ND', 'MND', 'GND'][result.licenseType] as 'ND' | 'MND' | 'GND' | undefined;
let firstMiningEpoch: bigint | undefined;
- if (licenseType === 'MND') {
+ if (licenseType === 'MND' || licenseType === 'GND') {
firstMiningEpoch = (
await publicClient.readContract({
address: config.mndContractAddress,
@@ -245,16 +246,17 @@ export const getBlockByTimestamp = async (targetTimestamp: number) => {
export const getLicenseRewards = async (
license: types.License,
licenseType: 'ND' | 'MND' | 'GND',
+ licenseId: bigint,
epochs: number[],
epochs_vals: number[],
-): Promise => {
+): Promise => {
switch (licenseType) {
case 'ND':
return getNdLicenseRewards(license, epochs, epochs_vals);
+
case 'MND':
- return getMndLicenseRewards(license, epochs, epochs_vals);
case 'GND':
- return getGndLicenseRewards(license, epochs, epochs_vals);
+ return getMndOrGndLicenseRewards(license, licenseId, epochs, epochs_vals);
}
};
@@ -353,72 +355,44 @@ export async function fetchR1MintedLastEpoch() {
return value;
}
-const getNdLicenseRewards = async (license: types.License, epochs: number[], epochs_vals: number[]): Promise => {
- return calculateLicenseRewards(license, epochs, epochs_vals, config.ndVestingEpochs);
-};
-
-const getMndLicenseRewards = async (license: types.License, epochs: number[], epochs_vals: number[]): Promise => {
- return calculateMndLicenseRewards(license, epochs, epochs_vals);
-};
-
-const getGndLicenseRewards = async (license: types.License, epochs: number[], epochs_vals: number[]): Promise => {
- return calculateLicenseRewards(license, epochs, epochs_vals, config.gndVestingEpochs);
-};
-
-const calculateLicenseRewards = async (
+const getNdLicenseRewards = async (
license: types.License,
epochs: number[],
epochs_vals: number[],
- vestingEpochs: number,
- cliffEpochs: number = 0,
-): Promise => {
+): Promise => {
const currentEpoch = getCurrentEpoch();
+ const epochsToClaim = currentEpoch - Number(license.lastClaimEpoch);
- const firstEpochToClaim =
- cliffEpochs > 0
- ? license.lastClaimEpoch >= cliffEpochs
- ? Number(license.lastClaimEpoch)
- : cliffEpochs
- : Number(license.lastClaimEpoch);
-
- const epochsToClaim = currentEpoch - firstEpochToClaim;
-
- if ((cliffEpochs > 0 && currentEpoch < cliffEpochs) || epochsToClaim <= 0) {
+ if (!epochsToClaim) {
return 0n;
}
- // Disregard epochs before the cliff epoch for MNDs
- if (cliffEpochs > 0 && epochs[0] < cliffEpochs) {
- const start = cliffEpochs - epochs[0];
- epochs = epochs.slice(start);
- epochs_vals = epochs_vals.slice(start);
- }
-
if (epochsToClaim !== epochs.length || epochsToClaim !== epochs_vals.length) {
- console.error(
- `Invalid epochs array length. Received ${epochs.length} epochs, but there are ${epochsToClaim} epochs to claim.`,
- );
-
- return 0n;
+ return undefined;
}
- const maxRewardsPerEpoch = license.totalAssignedAmount / BigInt(vestingEpochs);
let rewards_amount = 0n;
+ const maxRewardsPerEpoch = license.totalAssignedAmount / BigInt(config.ndVestingEpochs);
for (let i = 0; i < epochsToClaim; i++) {
rewards_amount += (maxRewardsPerEpoch * BigInt(epochs_vals[i])) / 255n;
}
const maxRemainingClaimAmount = license.totalAssignedAmount - license.totalClaimedAmount;
- return rewards_amount < maxRemainingClaimAmount ? rewards_amount : maxRemainingClaimAmount;
+ return rewards_amount > maxRemainingClaimAmount ? maxRemainingClaimAmount : rewards_amount;
};
-const calculateMndLicenseRewards = async (license: types.License, epochs: number[], epochs_vals: number[]): Promise => {
+const getMndOrGndLicenseRewards = async (
+ license: types.License,
+ licenseId: bigint,
+ epochs: number[],
+ epochs_vals: number[],
+): Promise => {
const currentEpoch = getCurrentEpoch();
const firstMiningEpoch = license.firstMiningEpoch;
if (firstMiningEpoch === undefined) {
- throw new Error('First mining epoch is undefined for MND license');
+ throw new Error('First mining epoch is undefined for MND/GND license');
}
const firstEpochToClaim =
@@ -436,38 +410,36 @@ const calculateMndLicenseRewards = async (license: types.License, epochs: number
}
if (epochsToClaim !== epochs.length || epochsToClaim !== epochs_vals.length) {
- console.error(
- `Invalid epochs array length. Received ${epochs.length} epochs, but there are ${epochsToClaim} epochs to claim.`,
- );
- return 0n;
+ return undefined;
}
- let rewards_amount = 0n;
- const logisticPlateau = 300_505239501691000000n; // 300.50
- const licensePlateau = (license.totalAssignedAmount * BigInt(1e18)) / logisticPlateau;
+ const publicClient = await getPublicClient();
- for (let i = 0; i < epochsToClaim; i++) {
- const maxRewardsPerEpoch = calculateMndMaxEpochRelease(epochs[i], firstMiningEpoch, licensePlateau);
- rewards_amount += (maxRewardsPerEpoch * BigInt(epochs_vals[i])) / 255n;
+ let result: ReadContractReturnType | undefined;
+
+ try {
+ result = await publicClient.readContract({
+ address: config.mndContractAddress,
+ abi: MNDContractAbi,
+ functionName: 'calculateRewards',
+ args: [
+ [
+ {
+ licenseId,
+ nodeAddress: license.nodeAddress,
+ epochs: epochs.map((epoch) => BigInt(epoch)),
+ availabilies: epochs_vals,
+ },
+ ],
+ ],
+ });
+ } catch {
+ return undefined;
}
- const maxRemainingClaimAmount = license.totalAssignedAmount - license.totalClaimedAmount;
- return rewards_amount > maxRemainingClaimAmount ? maxRemainingClaimAmount : rewards_amount;
-};
-
-const calculateMndMaxEpochRelease = (epoch: number, firstMiningEpoch: bigint, licensePlateau: bigint): bigint => {
- let x = epoch - Number(firstMiningEpoch);
- if (x > config.mndVestingEpochs) {
- x = config.mndVestingEpochs;
+ if (!result || result.length !== 1) {
+ throw new Error('Invalid rewards calculation result');
}
- const frac = logisticFraction(x);
- return (licensePlateau * BigInt(frac * 1e18)) / BigInt(1e18);
-};
-const logisticFraction = (x: number): number => {
- const length = config.mndVestingEpochs;
- const k = 5.0;
- const midPrc = 0.7;
- const midpoint = length * midPrc;
- return 1.0 / (1.0 + Math.exp((-k * (x - midpoint)) / length));
+ return result[0].rewardsAmount + result[0].carryoverAmount;
};
diff --git a/package-lock.json b/package-lock.json
index f45b256..a2e9360 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25,13 +25,14 @@
"date-fns": "^4.1.0",
"ethereum-blockies": "^0.1.1",
"framer-motion": "^12.0.11",
+ "lodash": "4.17.23",
"mapbox-gl": "^3.15.0",
"maplibre-gl": "^5.7.1",
"moralis": "^2.27.2",
- "next": "^15.5.9",
+ "next": "15.5.10",
"qs": "^6.14.1",
- "react": "^19.2.1",
- "react-dom": "^19.2.1",
+ "react": "19.2.4",
+ "react-dom": "19.2.4",
"react-icons": "^5.4.0",
"react-map-gl": "^8.0.4",
"recharts": "^2.15.4",
@@ -3183,9 +3184,9 @@
}
},
"node_modules/@next/env": {
- "version": "15.5.9",
- "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.9.tgz",
- "integrity": "sha512-4GlTZ+EJM7WaW2HEZcyU317tIQDjkQIyENDLxYJfSWlfqguN+dHkZgyQTV/7ykvobU7yEH5gKvreNrH4B6QgIg==",
+ "version": "15.5.10",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.10.tgz",
+ "integrity": "sha512-plg+9A/KoZcTS26fe15LHg+QxReTazrIOoKKUC3Uz4leGGeNPgLHdevVraAAOX0snnUs3WkRx3eUQpj9mreG6A==",
"license": "MIT"
},
"node_modules/@next/eslint-plugin-next": {
@@ -8863,9 +8864,9 @@
}
},
"node_modules/lodash": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "version": "4.17.23",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
+ "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"license": "MIT"
},
"node_modules/lodash.merge": {
@@ -9196,12 +9197,12 @@
"license": "MIT"
},
"node_modules/next": {
- "version": "15.5.9",
- "resolved": "https://registry.npmjs.org/next/-/next-15.5.9.tgz",
- "integrity": "sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==",
+ "version": "15.5.10",
+ "resolved": "https://registry.npmjs.org/next/-/next-15.5.10.tgz",
+ "integrity": "sha512-r0X65PNwyDDyOrWNKpQoZvOatw7BcsTPRKdwEqtc9cj3wv7mbBIk9tKed4klRaFXJdX0rugpuMTHslDrAU1bBg==",
"license": "MIT",
"dependencies": {
- "@next/env": "15.5.9",
+ "@next/env": "15.5.10",
"@swc/helpers": "0.5.15",
"caniuse-lite": "^1.0.30001579",
"postcss": "8.4.31",
@@ -10051,24 +10052,24 @@
}
},
"node_modules/react": {
- "version": "19.2.3",
- "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
- "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
+ "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
- "version": "19.2.3",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
- "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
+ "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
"license": "MIT",
"dependencies": {
"scheduler": "^0.27.0"
},
"peerDependencies": {
- "react": "^19.2.3"
+ "react": "^19.2.4"
}
},
"node_modules/react-icons": {
diff --git a/package.json b/package.json
index 311c0a4..dd560b8 100644
--- a/package.json
+++ b/package.json
@@ -33,13 +33,14 @@
"date-fns": "^4.1.0",
"ethereum-blockies": "^0.1.1",
"framer-motion": "^12.0.11",
+ "lodash": "4.17.23",
"mapbox-gl": "^3.15.0",
"maplibre-gl": "^5.7.1",
"moralis": "^2.27.2",
- "next": "^15.5.9",
+ "next": "15.5.10",
"qs": "^6.14.1",
- "react": "^19.2.1",
- "react-dom": "^19.2.1",
+ "react": "19.2.4",
+ "react-dom": "19.2.4",
"react-icons": "^5.4.0",
"react-map-gl": "^8.0.4",
"recharts": "^2.15.4",