harb/web-app/src/composables/useStatCollection.ts

275 lines
7.6 KiB
TypeScript
Raw Normal View History

import { ref, reactive, computed } from 'vue';
import axios from 'axios';
import { watchChainId } from '@wagmi/core';
import type { Config } from '@wagmi/core';
import { config } from '@/wagmi';
import logger from '@/utils/logger';
import type { WatchBlocksReturnType } from 'viem';
import { weiToNumber } from 'kraiken-lib/format';
import { minStake as stakeMinStake } from '@/contracts/stake';
2025-10-11 10:55:49 +00:00
import { DEFAULT_CHAIN_ID } from '@/config';
import { createRetryManager, formatGraphqlError, resolveGraphqlEndpoint } from '@/utils/graphqlRetry';
const demo = sessionStorage.getItem('demo') === 'true';
2025-09-23 14:18:04 +02:00
2025-09-24 09:41:28 +02:00
const GRAPHQL_TIMEOUT_MS = 15_000;
2025-09-23 19:24:05 +02:00
interface StatsRecord {
burnNextHourProjected: string;
burnedLastDay: string;
burnedLastWeek: string;
id: string;
minStake: string;
mintNextHourProjected: string;
mintedLastDay: string;
mintedLastWeek: string;
outstandingStake: string;
kraikenTotalSupply: string;
stakeTotalSupply: string;
ringBufferPointer: number;
totalBurned: string;
totalMinted: string;
2025-09-23 14:18:04 +02:00
}
2025-09-23 19:24:05 +02:00
const rawStatsCollections = ref<Array<StatsRecord>>([]);
2025-09-23 14:18:04 +02:00
const loading = ref(false);
const initialized = ref(false);
2025-09-24 09:41:28 +02:00
const statsError = ref<string | null>(null);
2025-10-11 10:55:49 +00:00
const activeChainId = ref<number>(DEFAULT_CHAIN_ID);
2025-09-23 14:18:04 +02:00
2025-10-11 10:55:49 +00:00
const retryManager = createRetryManager(loadStats, activeChainId);
2025-09-24 09:41:28 +02:00
2025-10-11 10:55:49 +00:00
export async function loadStatsCollection(chainId: number, endpointOverride?: string) {
const endpoint = resolveGraphqlEndpoint(chainId, endpointOverride);
logger.info(`loadStatsCollection for chainId: ${chainId}`);
const res = await axios.post(
endpoint,
{
query: `query StatsQuery {
2025-09-23 19:24:05 +02:00
stats(id: "0x01") {
minStake
2025-09-23 19:24:05 +02:00
burnNextHourProjected
burnedLastDay
burnedLastWeek
id
mintNextHourProjected
mintedLastDay
mintedLastWeek
outstandingStake
kraikenTotalSupply
stakeTotalSupply
ringBufferPointer
totalBurned
totalMinted
}
}`,
},
{ timeout: GRAPHQL_TIMEOUT_MS }
);
2025-09-24 09:41:28 +02:00
const errors = res.data?.errors;
if (Array.isArray(errors) && errors.length > 0) {
throw new Error(errors.map((err: unknown) => (err as { message?: string })?.message ?? 'GraphQL error').join(', '));
}
2025-09-23 19:24:05 +02:00
const stats = res.data?.data?.stats as StatsRecord | undefined;
if (!stats) {
throw new Error('Stats entity not found in GraphQL response');
}
2025-09-23 19:24:05 +02:00
return [{ ...stats, kraikenTotalSupply: stats.kraikenTotalSupply }];
2025-09-23 14:18:04 +02:00
}
const profit7d = computed(() => {
if (!statsCollection.value) {
return 0n;
}
const mintedLastWeek = BigInt(statsCollection.value.mintedLastWeek);
const burnedLastWeek = BigInt(statsCollection.value.burnedLastWeek);
const totalMinted = BigInt(statsCollection.value.totalMinted);
const totalBurned = BigInt(statsCollection.value.totalBurned);
const denominator = totalMinted - totalBurned;
if (denominator === 0n) {
return 0n;
}
return (mintedLastWeek - burnedLastWeek) / denominator;
2025-09-23 14:18:04 +02:00
});
const nettoToken7d = computed(() => {
if (!statsCollection.value) {
return 0n;
}
2025-09-23 14:18:04 +02:00
return BigInt(statsCollection.value.mintedLastWeek) - BigInt(statsCollection.value.burnedLastWeek);
2025-09-23 14:18:04 +02:00
});
const statsCollection = computed(() => {
if (rawStatsCollections.value?.length > 0) {
return rawStatsCollections.value[0];
} else {
return undefined;
}
2025-09-23 14:18:04 +02:00
});
const outstandingStake = computed(() => {
if (demo) {
// outStandingStake = 1990000000000000000000000n;
return 2000000000000000000000000n;
}
if (rawStatsCollections.value?.length > 0) {
return BigInt(rawStatsCollections.value[0].outstandingStake);
} else {
return 0n;
}
2025-09-23 14:18:04 +02:00
});
2025-09-23 19:24:05 +02:00
const kraikenTotalSupply = computed(() => {
if (rawStatsCollections.value?.length > 0) {
return BigInt(rawStatsCollections.value[0].kraikenTotalSupply);
} else {
return 0n;
}
2025-09-23 14:18:04 +02:00
});
const stakeTotalSupply = computed(() => {
if (rawStatsCollections.value?.length > 0) {
return BigInt(rawStatsCollections.value[0].stakeTotalSupply);
} else {
return 0n;
}
2025-09-23 14:18:04 +02:00
});
const minStake = computed(() => {
if (rawStatsCollections.value?.length > 0) {
return BigInt(rawStatsCollections.value[0].minStake);
} else {
return 0n;
}
});
2025-09-23 14:18:04 +02:00
//Total Supply Change / 7d=mintedLastWeekburnedLastWeek
const totalSupplyChange7d = computed(() => {
if (rawStatsCollections.value?.length > 0) {
return BigInt(rawStatsCollections.value[0].mintedLastWeek) - BigInt(rawStatsCollections.value[0].burnedLastWeek);
} else {
return 0n;
}
2025-09-23 14:18:04 +02:00
});
//totalsupply Change7d / harbtotalsupply
const inflation7d = computed(() => {
if (rawStatsCollections.value?.length > 0 && BigInt(rawStatsCollections.value[0].kraikenTotalSupply) > 0n) {
return BigInt(rawStatsCollections.value[0].mintedLastWeek) - BigInt(rawStatsCollections.value[0].burnedLastWeek);
} else {
return 0n;
}
2025-09-23 14:18:04 +02:00
});
const stakeableSupply = computed(() => {
if (rawStatsCollections.value?.length > 0 && BigInt(rawStatsCollections.value[0].kraikenTotalSupply) > 0n) {
return stakeTotalSupply.value / 5n;
} else {
return 0n;
}
2025-09-23 14:18:04 +02:00
});
//maxSlots
const maxSlots = computed(() => {
if (rawStatsCollections.value?.length > 0 && BigInt(rawStatsCollections.value[0].kraikenTotalSupply) > 0n) {
return (weiToNumber(stakeTotalSupply.value, 18) * 0.2) / 100;
} else {
return 0;
}
2025-09-23 14:18:04 +02:00
});
const claimedSlots = computed(() => {
if (stakeTotalSupply.value > 0n) {
const stakeableSupplyNumber = weiToNumber(stakeableSupply.value, 18);
const outstandingStakeNumber = weiToNumber(outstandingStake.value, 18);
return (outstandingStakeNumber / stakeableSupplyNumber) * maxSlots.value;
} else {
return 0;
}
2025-09-23 14:18:04 +02:00
});
2025-10-11 10:55:49 +00:00
export async function loadStats(chainId?: number) {
loading.value = true;
2025-09-23 14:18:04 +02:00
2025-10-11 10:55:49 +00:00
const targetChainId = typeof chainId === 'number' ? chainId : (activeChainId.value ?? DEFAULT_CHAIN_ID);
activeChainId.value = targetChainId;
let endpoint: string;
try {
endpoint = resolveGraphqlEndpoint(targetChainId);
} catch (error) {
rawStatsCollections.value = [];
2025-10-11 10:55:49 +00:00
statsError.value = error instanceof Error ? error.message : 'GraphQL endpoint not configured for this chain.';
retryManager.clear();
retryManager.reset();
loading.value = false;
initialized.value = true;
return;
}
2025-09-23 14:18:04 +02:00
try {
2025-10-11 10:55:49 +00:00
rawStatsCollections.value = await loadStatsCollection(targetChainId, endpoint);
statsError.value = null;
2025-10-11 10:55:49 +00:00
retryManager.reset();
retryManager.clear();
stakeMinStake.value = minStake.value;
} catch (error) {
rawStatsCollections.value = [];
statsError.value = formatGraphqlError(error);
stakeMinStake.value = 0n;
2025-10-11 10:55:49 +00:00
retryManager.schedule();
} finally {
loading.value = false;
initialized.value = true;
}
2025-09-23 14:18:04 +02:00
}
import { onMounted, onUnmounted } from 'vue';
let unwatch: WatchBlocksReturnType | null = null;
2025-10-11 10:55:49 +00:00
export function useStatCollection(chainId: number = DEFAULT_CHAIN_ID) {
activeChainId.value = chainId;
onMounted(async () => {
//initial loading stats
if (rawStatsCollections.value?.length === 0 && !loading.value) {
2025-10-11 10:55:49 +00:00
await loadStats(activeChainId.value);
}
});
if (!unwatch) {
//chain Switch reload stats for other chain
unwatch = watchChainId(config as Config, {
2025-10-11 10:55:49 +00:00
async onChange(nextChainId) {
const resolvedChainId = nextChainId ?? DEFAULT_CHAIN_ID;
activeChainId.value = resolvedChainId;
await loadStats(resolvedChainId);
},
});
}
onUnmounted(() => {
2025-10-11 10:55:49 +00:00
retryManager.clear();
if (unwatch) {
unwatch();
}
});
2025-09-23 14:18:04 +02:00
return reactive({
profit7d,
nettoToken7d,
inflation7d,
outstandingStake,
kraikenTotalSupply,
stakeTotalSupply,
totalSupplyChange7d,
initialized,
maxSlots,
stakeableSupply,
claimedSlots,
statsError,
loading,
minStake,
});
2025-09-23 14:18:04 +02:00
}