From e392886f1a12f1cd7a5d37661fdf3494dbee7755 Mon Sep 17 00:00:00 2001 From: Arman Dezfuli-Arjomandi Date: Mon, 11 Dec 2023 16:55:11 -0500 Subject: [PATCH 1/3] Supply update WIP --- src/app/api/stats/supply/[token]/route.ts | 47 +++++++++++++++++------ src/app/stats/components/HntInfo.tsx | 13 ++++++- src/app/stats/components/SubDaoInfo.tsx | 8 ++-- src/app/stats/utils/emissions.ts | 3 ++ 4 files changed, 55 insertions(+), 16 deletions(-) create mode 100644 src/app/stats/utils/emissions.ts diff --git a/src/app/api/stats/supply/[token]/route.ts b/src/app/api/stats/supply/[token]/route.ts index 2873618..067b61d 100644 --- a/src/app/api/stats/supply/[token]/route.ts +++ b/src/app/api/stats/supply/[token]/route.ts @@ -6,9 +6,24 @@ import { getDailyEmisisons, MAX_DAILY_NET_EMISSIONS, } from "@/app/stats/utils/remainingEmissions" +import { + HNT_MAX_SUPPLY, + IOT_MAX_SUPPLY, + MOBILE_MAX_SUPPLY, +} from "@/app/stats/utils/emissions" -type SupplyToken = "hnt" | "mobile" | "iot" -type SupplyType = "circulating" | "total" | "max" +enum SupplyToken { + HNT = "hnt", + MOBILE = "mobile", + IOT = "iot", +} + +enum SupplyType { + CIRCULATING = "circulating", + TOTAL = "total", + LIMIT = "limit", + MAX = "max", +} export async function GET( request: NextRequest, @@ -19,31 +34,30 @@ export async function GET( const type = searchParams.get("type") as SupplyType if ( - !["hnt", "mobile", "iot"].includes(token) || - !["circulating", "total", "max"].includes(type) + !Object.values(SupplyToken).includes(token) || + !Object.values(SupplyType).includes(type) ) { return new NextResponse(null, { status: 400 }) } const mintInfo = await fetchMint( { - hnt: HNT_MINT, - mobile: MOBILE_MINT, - iot: IOT_MINT, + [SupplyToken.HNT]: HNT_MINT, + [SupplyToken.MOBILE]: MOBILE_MINT, + [SupplyToken.IOT]: IOT_MINT, }[token] ) - if (type === "circulating") { + if (type === SupplyType.CIRCULATING || type === SupplyType.TOTAL) { const circulatingSupply = mintInfo.info?.info.supply! return NextResponse.json( toNumber(circulatingSupply, mintInfo?.info?.info.decimals || 0) ) - } else if (type === "total" || type === "max") { - // Return the same thing for total and max as they are functionally the same for us + } else if (type === SupplyType.LIMIT) { let remainingEmissions = Math.ceil(getRemainingEmissions(new Date(), token)) - if (token === "hnt") { + if (token === SupplyToken.HNT) { // Due to Net Emissions, assume the max amount will be re-emitted remainingEmissions += Math.ceil(MAX_DAILY_NET_EMISSIONS) } @@ -61,5 +75,16 @@ export async function GET( return NextResponse.json( toNumber(totalSupply, mintInfo?.info?.info.decimals || 0) ) + } else if (type === SupplyType.MAX) { + switch (token) { + case SupplyToken.HNT: + return NextResponse.json(HNT_MAX_SUPPLY) + case SupplyToken.MOBILE: + return NextResponse.json(MOBILE_MAX_SUPPLY) + case SupplyToken.IOT: + return NextResponse.json(IOT_MAX_SUPPLY) + default: + return new NextResponse(null, { status: 400 }) + } } } diff --git a/src/app/stats/components/HntInfo.tsx b/src/app/stats/components/HntInfo.tsx index 2d63ab2..f101aa9 100644 --- a/src/app/stats/components/HntInfo.tsx +++ b/src/app/stats/components/HntInfo.tsx @@ -26,6 +26,7 @@ import { MAX_DAILY_NET_EMISSIONS, getRemainingEmissions, } from "../utils/remainingEmissions" +import { HNT_MAX_SUPPLY } from "../utils/emissions" import { Countdown } from "./Countdown" const COINGECKO_HNT_URL = @@ -127,7 +128,7 @@ export const HntInfo = async () => { }} /> { maxSupplyRecord.recorded_at, DATE_FORMAT )})`, + id: "HNT Supply Limit", + }} + /> + diff --git a/src/app/stats/components/SubDaoInfo.tsx b/src/app/stats/components/SubDaoInfo.tsx index cc47b22..377310f 100644 --- a/src/app/stats/components/SubDaoInfo.tsx +++ b/src/app/stats/components/SubDaoInfo.tsx @@ -86,7 +86,7 @@ export const SubDaoInfo = async ({ subDao }: { subDao: SubDao }) => { const remainingEmissions = Math.round( getRemainingEmissions(new Date(), subDao) ) - const maxSupply = + const supplyLimit = mintInfo.info?.info.supply! + BigInt(remainingEmissions) * BigInt(1000000) const supplyStaked = governanceMetrics.total.hnt @@ -173,16 +173,16 @@ export const SubDaoInfo = async ({ subDao }: { subDao: SubDao }) => { }} /> Date: Tue, 12 Dec 2023 12:37:00 -0500 Subject: [PATCH 2/3] Add IOT and MOBILE max supply to explorer stats page --- src/app/stats/components/SubDaoInfo.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/app/stats/components/SubDaoInfo.tsx b/src/app/stats/components/SubDaoInfo.tsx index 377310f..7089597 100644 --- a/src/app/stats/components/SubDaoInfo.tsx +++ b/src/app/stats/components/SubDaoInfo.tsx @@ -20,6 +20,7 @@ import { getLatestSubNetworkEmissions, getRemainingEmissions, } from "../utils/remainingEmissions" +import { IOT_MAX_SUPPLY, MOBILE_MAX_SUPPLY } from "../utils/emissions" import { SubDao } from "../utils/types" type SubDaoType = { @@ -31,6 +32,7 @@ type SubDaoType = { subDaoMint: PublicKey maxDescription: string activeDetails: string + maxSupply: number } const MOBILE_INFO: SubDaoType = { @@ -43,6 +45,7 @@ const MOBILE_INFO: SubDaoType = { maxDescription: "This is an upper limit that will not be reached and does not consider future MOBILE burn. Reason: Daily emissions are currently only 86% of scheduled emissions, as not all rewardable entities (service providers, and oracles) exist or currently receive rewards.", activeDetails: " This exclusively includes active gateways (not radios).", + maxSupply: MOBILE_MAX_SUPPLY, } const IOT_INFO: SubDaoType = { @@ -55,6 +58,7 @@ const IOT_INFO: SubDaoType = { maxDescription: "This is an upper limit that will not be reached and does not consider future IOT burn. Reason: Daily emissions are currently only 93% of scheduled emissions, as oracles do not currently receive rewards.", activeDetails: "", + maxSupply: IOT_MAX_SUPPLY, } export const SubDaoInfo = async ({ subDao }: { subDao: SubDao }) => { @@ -67,6 +71,7 @@ export const SubDaoInfo = async ({ subDao }: { subDao: SubDao }) => { icon, subDaoMint, maxDescription, + maxSupply, } = subDao === "mobile" ? MOBILE_INFO : IOT_INFO const [activeCount, mintInfo, epochInfo, treasuryInfo, governanceMetrics] = await Promise.all([ @@ -185,6 +190,15 @@ export const SubDaoInfo = async ({ subDao }: { subDao: SubDao }) => { id: `${title} Supply Limit`, }} /> + Date: Tue, 12 Dec 2023 14:39:07 -0500 Subject: [PATCH 3/3] Use existing hnt supply limit logic --- src/app/api/stats/supply/[token]/route.ts | 33 +++++++++++++++-------- src/app/stats/components/HntInfo.tsx | 6 ++--- src/app/stats/components/SubDaoInfo.tsx | 4 +-- src/knex/maxSupply.ts | 2 +- 4 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/app/api/stats/supply/[token]/route.ts b/src/app/api/stats/supply/[token]/route.ts index 067b61d..6b0eab2 100644 --- a/src/app/api/stats/supply/[token]/route.ts +++ b/src/app/api/stats/supply/[token]/route.ts @@ -1,16 +1,18 @@ -import { NextRequest, NextResponse } from "next/server" -import { fetchMint } from "@/app/stats/utils/fetchMint" -import { HNT_MINT, MOBILE_MINT, IOT_MINT, toNumber } from "@helium/spl-utils" -import { - getRemainingEmissions, - getDailyEmisisons, - MAX_DAILY_NET_EMISSIONS, -} from "@/app/stats/utils/remainingEmissions" import { HNT_MAX_SUPPLY, IOT_MAX_SUPPLY, MOBILE_MAX_SUPPLY, } from "@/app/stats/utils/emissions" +import { fetchMint } from "@/app/stats/utils/fetchMint" +import { + MAX_DAILY_NET_EMISSIONS, + getDailyEmisisons, + getRemainingEmissions, +} from "@/app/stats/utils/remainingEmissions" +import { db } from "@/knex/db" +import { MaxSupply } from "@/knex/maxSupply" +import { HNT_MINT, IOT_MINT, MOBILE_MINT, toNumber } from "@helium/spl-utils" +import { NextRequest, NextResponse } from "next/server" enum SupplyToken { HNT = "hnt", @@ -55,11 +57,20 @@ export async function GET( toNumber(circulatingSupply, mintInfo?.info?.info.decimals || 0) ) } else if (type === SupplyType.LIMIT) { - let remainingEmissions = Math.ceil(getRemainingEmissions(new Date(), token)) + let remainingEmissions = 0 + let supply = mintInfo.info?.info.supply! if (token === SupplyToken.HNT) { // Due to Net Emissions, assume the max amount will be re-emitted - remainingEmissions += Math.ceil(MAX_DAILY_NET_EMISSIONS) + remainingEmissions = Math.ceil(MAX_DAILY_NET_EMISSIONS) + + // using existing supply limit logic to avoid repeating edge case logic + const maxSupplyDb = new MaxSupply(db) + const supplyLimit = (await maxSupplyDb.getLatest({ withBurn: false })) + ?.max_supply! + supply = supplyLimit + } else { + remainingEmissions += Math.ceil(getRemainingEmissions(new Date(), token)) } // Add the daily emissions for today to be conservative @@ -68,7 +79,7 @@ export async function GET( remainingEmissions += Math.ceil(dailyEmissions) const totalSupply = - mintInfo.info?.info.supply! + + supply + BigInt(remainingEmissions) * BigInt(Math.pow(10, mintInfo?.info?.info.decimals || 0)) diff --git a/src/app/stats/components/HntInfo.tsx b/src/app/stats/components/HntInfo.tsx index f101aa9..a86a43e 100644 --- a/src/app/stats/components/HntInfo.tsx +++ b/src/app/stats/components/HntInfo.tsx @@ -19,6 +19,7 @@ import { fetchHntBurn, fetchHntEmissions, } from "../utils/dune/fetchHntEmissions" +import { HNT_MAX_SUPPLY } from "../utils/emissions" import { fetchHntGovernanceStats } from "../utils/fetchGovernanceMetrics" import { fetchMint } from "../utils/fetchMint" import { getNextHalvening } from "../utils/getNextHalvening" @@ -26,7 +27,6 @@ import { MAX_DAILY_NET_EMISSIONS, getRemainingEmissions, } from "../utils/remainingEmissions" -import { HNT_MAX_SUPPLY } from "../utils/emissions" import { Countdown } from "./Countdown" const COINGECKO_HNT_URL = @@ -137,7 +137,7 @@ export const HntInfo = async () => { tooltip={{ description: "Maximum supply of HNT derived by summing current supply, remaining emissions, and today's burned HNT (which are re-emitted via net emissions). This is an upper limit that will not be reached and does not consider future HNT burn. Accurate within 1643 HNT.", - cadence: `Every 8h (last run ${format( + cadence: `Daily (last run ${format( maxSupplyRecord.recorded_at, DATE_FORMAT )})`, @@ -149,7 +149,7 @@ export const HntInfo = async () => { value={HNT_MAX_SUPPLY.toLocaleString()} tooltip={{ description: - "Maximum supply of HNT that can ever exist as defined in HIPs. This is an upper limit that will not be reached and does not consider future HNT burn.", + "Maximum supply of HNT that can ever exist as defined in HIPs. This is an upper limit that will not be reached and does not consider past or future HNT burn.", cadence: "Fixed", id: "HNT Max Supply", }} diff --git a/src/app/stats/components/SubDaoInfo.tsx b/src/app/stats/components/SubDaoInfo.tsx index 7089597..92c9a6f 100644 --- a/src/app/stats/components/SubDaoInfo.tsx +++ b/src/app/stats/components/SubDaoInfo.tsx @@ -11,6 +11,7 @@ import { toNumber, } from "@helium/spl-utils" import { PublicKey } from "@solana/web3.js" +import { IOT_MAX_SUPPLY, MOBILE_MAX_SUPPLY } from "../utils/emissions" import { fetchSubDaoGovernanceStats } from "../utils/fetchGovernanceMetrics" import { fetchMint } from "../utils/fetchMint" import { fetchSubDaoEpochInfo } from "../utils/fetchSubDaoEpochInfo" @@ -20,7 +21,6 @@ import { getLatestSubNetworkEmissions, getRemainingEmissions, } from "../utils/remainingEmissions" -import { IOT_MAX_SUPPLY, MOBILE_MAX_SUPPLY } from "../utils/emissions" import { SubDao } from "../utils/types" type SubDaoType = { @@ -194,7 +194,7 @@ export const SubDaoInfo = async ({ subDao }: { subDao: SubDao }) => { label="Max Supply" value={maxSupply.toLocaleString()} tooltip={{ - description: `Maximum supply of ${title} that can ever exist as defined in HIPs. This is an upper limit that will not be reached and does not ${title} future burn.`, + description: `Maximum supply of ${title} that can ever exist as defined in HIPs. This is an upper limit that will not be reached and does not consider ${title} past or future burn.`, cadence: "Fixed", id: `${title} Max Supply`, }} diff --git a/src/knex/maxSupply.ts b/src/knex/maxSupply.ts index e60b302..5e5fa1b 100644 --- a/src/knex/maxSupply.ts +++ b/src/knex/maxSupply.ts @@ -15,7 +15,7 @@ export class MaxSupply { this.knex = knex } - private async getLatest({ + async getLatest({ withBurn, }: { withBurn: boolean