2025-10-03 16:51:44 +02:00
|
|
|
|
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';
|
2026-02-24 23:00:58 +00:00
|
|
|
|
import { weiToNumber } from 'kraiken-lib/format';
|
2025-10-11 15:30:08 +02:00
|
|
|
|
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';
|
2025-10-03 16:51:44 +02:00
|
|
|
|
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 {
|
2025-10-03 16:51:44 +02:00
|
|
|
|
burnNextHourProjected: string;
|
|
|
|
|
|
burnedLastDay: string;
|
|
|
|
|
|
burnedLastWeek: string;
|
|
|
|
|
|
id: string;
|
2025-10-11 15:30:08 +02:00
|
|
|
|
minStake: string;
|
2025-10-03 16:51:44 +02:00
|
|
|
|
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}`);
|
2025-10-03 16:51:44 +02:00
|
|
|
|
const res = await axios.post(
|
|
|
|
|
|
endpoint,
|
|
|
|
|
|
{
|
|
|
|
|
|
query: `query StatsQuery {
|
2025-09-23 19:24:05 +02:00
|
|
|
|
stats(id: "0x01") {
|
2025-10-11 15:30:08 +02:00
|
|
|
|
minStake
|
2025-09-23 19:24:05 +02:00
|
|
|
|
burnNextHourProjected
|
|
|
|
|
|
burnedLastDay
|
|
|
|
|
|
burnedLastWeek
|
|
|
|
|
|
id
|
|
|
|
|
|
mintNextHourProjected
|
|
|
|
|
|
mintedLastDay
|
|
|
|
|
|
mintedLastWeek
|
|
|
|
|
|
outstandingStake
|
|
|
|
|
|
kraikenTotalSupply
|
|
|
|
|
|
stakeTotalSupply
|
|
|
|
|
|
ringBufferPointer
|
|
|
|
|
|
totalBurned
|
|
|
|
|
|
totalMinted
|
|
|
|
|
|
}
|
|
|
|
|
|
}`,
|
2025-10-03 16:51:44 +02:00
|
|
|
|
},
|
|
|
|
|
|
{ timeout: GRAPHQL_TIMEOUT_MS }
|
|
|
|
|
|
);
|
2025-09-24 09:41:28 +02:00
|
|
|
|
|
2025-10-03 16:51:44 +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
|
|
|
|
|
2025-10-03 16:51:44 +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
|
|
|
|
|
2025-10-03 16:51:44 +02:00
|
|
|
|
return [{ ...stats, kraikenTotalSupply: stats.kraikenTotalSupply }];
|
2025-09-23 14:18:04 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const profit7d = computed(() => {
|
2025-10-03 16:51:44 +02:00
|
|
|
|
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(() => {
|
2025-10-03 16:51:44 +02:00
|
|
|
|
if (!statsCollection.value) {
|
|
|
|
|
|
return 0n;
|
|
|
|
|
|
}
|
2025-09-23 14:18:04 +02:00
|
|
|
|
|
2025-10-03 16:51:44 +02:00
|
|
|
|
return BigInt(statsCollection.value.mintedLastWeek) - BigInt(statsCollection.value.burnedLastWeek);
|
2025-09-23 14:18:04 +02:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const statsCollection = computed(() => {
|
2025-10-03 16:51:44 +02:00
|
|
|
|
if (rawStatsCollections.value?.length > 0) {
|
|
|
|
|
|
return rawStatsCollections.value[0];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return undefined;
|
|
|
|
|
|
}
|
2025-09-23 14:18:04 +02:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const outstandingStake = computed(() => {
|
2025-10-03 16:51:44 +02:00
|
|
|
|
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(() => {
|
2025-10-03 16:51:44 +02:00
|
|
|
|
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(() => {
|
2025-10-03 16:51:44 +02:00
|
|
|
|
if (rawStatsCollections.value?.length > 0) {
|
|
|
|
|
|
return BigInt(rawStatsCollections.value[0].stakeTotalSupply);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return 0n;
|
|
|
|
|
|
}
|
2025-09-23 14:18:04 +02:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-10-11 15:30:08 +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=mintedLastWeek−burnedLastWeek
|
|
|
|
|
|
const totalSupplyChange7d = computed(() => {
|
2025-10-03 16:51:44 +02:00
|
|
|
|
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(() => {
|
2025-10-03 16:51:44 +02:00
|
|
|
|
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(() => {
|
2025-10-03 16:51:44 +02:00
|
|
|
|
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(() => {
|
2025-10-03 16:51:44 +02:00
|
|
|
|
if (rawStatsCollections.value?.length > 0 && BigInt(rawStatsCollections.value[0].kraikenTotalSupply) > 0n) {
|
2026-02-24 23:00:58 +00:00
|
|
|
|
return (weiToNumber(stakeTotalSupply.value, 18) * 0.2) / 100;
|
2025-10-03 16:51:44 +02:00
|
|
|
|
} else {
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
2025-09-23 14:18:04 +02:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const claimedSlots = computed(() => {
|
2025-10-03 16:51:44 +02:00
|
|
|
|
if (stakeTotalSupply.value > 0n) {
|
2026-02-24 23:00:58 +00:00
|
|
|
|
const stakeableSupplyNumber = weiToNumber(stakeableSupply.value, 18);
|
|
|
|
|
|
const outstandingStakeNumber = weiToNumber(outstandingStake.value, 18);
|
2025-10-03 16:51:44 +02:00
|
|
|
|
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) {
|
2025-10-03 16:51:44 +02:00
|
|
|
|
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) {
|
2025-10-03 16:51:44 +02:00
|
|
|
|
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();
|
2025-10-03 16:51:44 +02:00
|
|
|
|
loading.value = false;
|
|
|
|
|
|
initialized.value = true;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-09-23 14:18:04 +02:00
|
|
|
|
|
2025-10-03 16:51:44 +02:00
|
|
|
|
try {
|
2025-10-11 10:55:49 +00:00
|
|
|
|
rawStatsCollections.value = await loadStatsCollection(targetChainId, endpoint);
|
2025-10-03 16:51:44 +02:00
|
|
|
|
statsError.value = null;
|
2025-10-11 10:55:49 +00:00
|
|
|
|
retryManager.reset();
|
|
|
|
|
|
retryManager.clear();
|
2025-10-11 15:30:08 +02:00
|
|
|
|
stakeMinStake.value = minStake.value;
|
2025-10-03 16:51:44 +02:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
rawStatsCollections.value = [];
|
|
|
|
|
|
statsError.value = formatGraphqlError(error);
|
2025-10-11 15:30:08 +02:00
|
|
|
|
stakeMinStake.value = 0n;
|
2025-10-11 10:55:49 +00:00
|
|
|
|
retryManager.schedule();
|
2025-10-03 16:51:44 +02:00
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false;
|
|
|
|
|
|
initialized.value = true;
|
|
|
|
|
|
}
|
2025-09-23 14:18:04 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-03 16:51:44 +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;
|
|
|
|
|
|
|
2025-10-03 16:51:44 +02:00
|
|
|
|
onMounted(async () => {
|
|
|
|
|
|
//initial loading stats
|
|
|
|
|
|
if (rawStatsCollections.value?.length === 0 && !loading.value) {
|
2025-10-11 10:55:49 +00:00
|
|
|
|
await loadStats(activeChainId.value);
|
2025-10-03 16:51:44 +02:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
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);
|
2025-10-03 16:51:44 +02:00
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
onUnmounted(() => {
|
2025-10-11 10:55:49 +00:00
|
|
|
|
retryManager.clear();
|
2025-10-03 16:51:44 +02:00
|
|
|
|
if (unwatch) {
|
|
|
|
|
|
unwatch();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2025-09-23 14:18:04 +02:00
|
|
|
|
|
2025-10-03 16:51:44 +02:00
|
|
|
|
return reactive({
|
|
|
|
|
|
profit7d,
|
|
|
|
|
|
nettoToken7d,
|
|
|
|
|
|
inflation7d,
|
|
|
|
|
|
outstandingStake,
|
|
|
|
|
|
kraikenTotalSupply,
|
|
|
|
|
|
stakeTotalSupply,
|
|
|
|
|
|
totalSupplyChange7d,
|
|
|
|
|
|
initialized,
|
|
|
|
|
|
maxSlots,
|
|
|
|
|
|
stakeableSupply,
|
|
|
|
|
|
claimedSlots,
|
|
|
|
|
|
statsError,
|
|
|
|
|
|
loading,
|
2025-10-11 15:30:08 +02:00
|
|
|
|
minStake,
|
2025-10-03 16:51:44 +02:00
|
|
|
|
});
|
2025-09-23 14:18:04 +02:00
|
|
|
|
}
|