better backend comms
This commit is contained in:
parent
d0e8623cf9
commit
02a057622c
17 changed files with 1054 additions and 134 deletions
|
|
@ -15,6 +15,12 @@ import logger from "@/utils/logger";
|
|||
const rawActivePositions = ref<Array<Position>>([]);
|
||||
const rawClosedPositoins = ref<Array<Position>>([]);
|
||||
const loading = ref(false);
|
||||
const positionsError = ref<string | null>(null);
|
||||
const POSITIONS_RETRY_BASE_DELAY = 1_500;
|
||||
const POSITIONS_RETRY_MAX_DELAY = 60_000;
|
||||
const positionsRetryDelayMs = ref(POSITIONS_RETRY_BASE_DELAY);
|
||||
const GRAPHQL_TIMEOUT_MS = 15_000;
|
||||
let positionsRetryTimer: number | null = null;
|
||||
const chain = useChain();
|
||||
const activePositions = computed(() => {
|
||||
const account = getAccount(config as any);
|
||||
|
|
@ -106,14 +112,16 @@ const tresholdValue = computed(() => {
|
|||
return avg / 2;
|
||||
});
|
||||
|
||||
export async function loadActivePositions() {
|
||||
export async function loadActivePositions(endpoint?: string) {
|
||||
logger.info(`loadActivePositions for chain: ${chainData.value?.path}`);
|
||||
if (!chainData.value?.graphql) {
|
||||
return [];
|
||||
const targetEndpoint = endpoint ?? chainData.value?.graphql?.trim();
|
||||
if (!targetEndpoint) {
|
||||
throw new Error("GraphQL endpoint not configured for this chain.");
|
||||
}
|
||||
console.log("chainData.value?.graphql", chainData.value?.graphql);
|
||||
|
||||
const res = await axios.post(chainData.value?.graphql, {
|
||||
console.log("chainData.value?.graphql", targetEndpoint);
|
||||
|
||||
const res = await axios.post(targetEndpoint, {
|
||||
query: `query ActivePositions {
|
||||
positionss(where: { status: "Active" }, orderBy: "taxRate", orderDirection: "asc", limit: 1000) {
|
||||
items {
|
||||
|
|
@ -132,7 +140,12 @@ export async function loadActivePositions() {
|
|||
}
|
||||
}
|
||||
}`,
|
||||
});
|
||||
}, { timeout: GRAPHQL_TIMEOUT_MS });
|
||||
|
||||
const errors = res.data?.errors;
|
||||
if (Array.isArray(errors) && errors.length > 0) {
|
||||
throw new Error(errors.map((err: any) => err?.message ?? "GraphQL error").join(", "));
|
||||
}
|
||||
|
||||
const items = res.data?.data?.positionss?.items ?? [];
|
||||
return items.map((item: any) => ({
|
||||
|
|
@ -147,12 +160,13 @@ function formatId(id: Hex) {
|
|||
return bigIntId;
|
||||
}
|
||||
|
||||
export async function loadMyClosedPositions(account: GetAccountReturnType) {
|
||||
export async function loadMyClosedPositions(endpoint: string | undefined, account: GetAccountReturnType) {
|
||||
logger.info(`loadMyClosedPositions for chain: ${chainData.value?.path}`);
|
||||
if (!chainData.value?.graphql) {
|
||||
return [];
|
||||
const targetEndpoint = endpoint ?? chainData.value?.graphql?.trim();
|
||||
if (!targetEndpoint) {
|
||||
throw new Error("GraphQL endpoint not configured for this chain.");
|
||||
}
|
||||
const res = await axios.post(chainData.value?.graphql, {
|
||||
const res = await axios.post(targetEndpoint, {
|
||||
query: `query ClosedPositions {
|
||||
positionss(where: { status: "Closed", owner: "${account.address?.toLowerCase()}" }, limit: 1000) {
|
||||
items {
|
||||
|
|
@ -171,10 +185,10 @@ export async function loadMyClosedPositions(account: GetAccountReturnType) {
|
|||
}
|
||||
}
|
||||
}`,
|
||||
});
|
||||
if (res.data.errors?.length > 0) {
|
||||
console.error("todo nur laden, wenn eingeloggt");
|
||||
return [];
|
||||
}, { timeout: GRAPHQL_TIMEOUT_MS });
|
||||
const errors = res.data?.errors;
|
||||
if (Array.isArray(errors) && errors.length > 0) {
|
||||
throw new Error(errors.map((err: any) => err?.message ?? "GraphQL error").join(", "));
|
||||
}
|
||||
const items = res.data?.data?.positionss?.items ?? [];
|
||||
return items.map((item: any) => ({
|
||||
|
|
@ -186,20 +200,84 @@ export async function loadMyClosedPositions(account: GetAccountReturnType) {
|
|||
export async function loadPositions() {
|
||||
loading.value = true;
|
||||
|
||||
rawActivePositions.value = await loadActivePositions();
|
||||
//optimal wäre es: laden, wenn new Position meine Position geclosed hat
|
||||
const account = getAccount(config as any);
|
||||
|
||||
if (account.address) {
|
||||
rawClosedPositoins.value = await loadMyClosedPositions(account);
|
||||
const endpoint = chainData.value?.graphql?.trim();
|
||||
if (!endpoint) {
|
||||
rawActivePositions.value = [];
|
||||
rawClosedPositoins.value = [];
|
||||
positionsError.value = "GraphQL endpoint not configured for this chain.";
|
||||
clearPositionsRetryTimer();
|
||||
positionsRetryDelayMs.value = POSITIONS_RETRY_BASE_DELAY;
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
rawActivePositions.value = await loadActivePositions(endpoint);
|
||||
const account = getAccount(config as any);
|
||||
if (account.address) {
|
||||
rawClosedPositoins.value = await loadMyClosedPositions(endpoint, account);
|
||||
} else {
|
||||
rawClosedPositoins.value = [];
|
||||
}
|
||||
positionsError.value = null;
|
||||
positionsRetryDelayMs.value = POSITIONS_RETRY_BASE_DELAY;
|
||||
clearPositionsRetryTimer();
|
||||
} catch (error) {
|
||||
console.warn("[positions] loadPositions() failed", error);
|
||||
rawActivePositions.value = [];
|
||||
rawClosedPositoins.value = [];
|
||||
positionsError.value = formatGraphqlError(error);
|
||||
schedulePositionsRetry();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
let unwatch: WatchEventReturnType;
|
||||
let unwatchPositionRemovedEvent: WatchEventReturnType;
|
||||
let unwatchChainSwitch: WatchChainIdReturnType;
|
||||
let unwatchAccountChanged: WatchAccountReturnType;
|
||||
let unwatch: WatchEventReturnType | null;
|
||||
let unwatchPositionRemovedEvent: WatchEventReturnType | null;
|
||||
let unwatchChainSwitch: WatchChainIdReturnType | null;
|
||||
let unwatchAccountChanged: WatchAccountReturnType | null;
|
||||
|
||||
function formatGraphqlError(error: unknown): string {
|
||||
if (axios.isAxiosError(error)) {
|
||||
const responseErrors = (error.response?.data as any)?.errors;
|
||||
if (Array.isArray(responseErrors) && responseErrors.length > 0) {
|
||||
return responseErrors.map((err: any) => err?.message ?? "GraphQL error").join(", ");
|
||||
}
|
||||
if (error.response?.status) {
|
||||
return `GraphQL request failed with status ${error.response.status}`;
|
||||
}
|
||||
if (error.message) {
|
||||
return error.message;
|
||||
}
|
||||
}
|
||||
if (error instanceof Error && error.message) {
|
||||
return error.message;
|
||||
}
|
||||
return "Unknown GraphQL error";
|
||||
}
|
||||
|
||||
function clearPositionsRetryTimer() {
|
||||
if (positionsRetryTimer !== null) {
|
||||
clearTimeout(positionsRetryTimer);
|
||||
positionsRetryTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function schedulePositionsRetry() {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
if (positionsRetryTimer !== null) {
|
||||
return;
|
||||
}
|
||||
const delay = positionsRetryDelayMs.value;
|
||||
positionsRetryTimer = window.setTimeout(async () => {
|
||||
positionsRetryTimer = null;
|
||||
await loadPositions();
|
||||
}, delay);
|
||||
positionsRetryDelayMs.value = Math.min(positionsRetryDelayMs.value * 2, POSITIONS_RETRY_MAX_DELAY);
|
||||
}
|
||||
export function usePositions() {
|
||||
function watchEvent() {
|
||||
unwatch = watchContractEvent(config as any, {
|
||||
|
|
@ -231,7 +309,7 @@ export function usePositions() {
|
|||
//initial loading positions
|
||||
|
||||
if (activePositions.value.length < 1 && loading.value === false) {
|
||||
loadPositions();
|
||||
await loadPositions();
|
||||
// await getMinStake();
|
||||
}
|
||||
|
||||
|
|
@ -260,12 +338,23 @@ export function usePositions() {
|
|||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// if (unwatch) {
|
||||
// unwatch();
|
||||
// }
|
||||
// if (unwatchPositionRemovedEvent) {
|
||||
// unwatchPositionRemovedEvent();
|
||||
// }
|
||||
if (unwatch) {
|
||||
unwatch();
|
||||
unwatch = null;
|
||||
}
|
||||
if (unwatchPositionRemovedEvent) {
|
||||
unwatchPositionRemovedEvent();
|
||||
unwatchPositionRemovedEvent = null;
|
||||
}
|
||||
if (unwatchChainSwitch) {
|
||||
unwatchChainSwitch();
|
||||
unwatchChainSwitch = null;
|
||||
}
|
||||
if (unwatchAccountChanged) {
|
||||
unwatchAccountChanged();
|
||||
unwatchAccountChanged = null;
|
||||
}
|
||||
clearPositionsRetryTimer();
|
||||
});
|
||||
|
||||
function createRandomPosition(amount: number = 1) {
|
||||
|
|
@ -317,5 +406,7 @@ export function usePositions() {
|
|||
watchEvent,
|
||||
watchPositionRemoved,
|
||||
createRandomPosition,
|
||||
positionsError,
|
||||
loading,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ import type { WatchBlocksReturnType } from "viem";
|
|||
import { bigInt2Number } from "@/utils/helper";
|
||||
const demo = sessionStorage.getItem("demo") === "true";
|
||||
|
||||
const GRAPHQL_TIMEOUT_MS = 15_000;
|
||||
const RETRY_BASE_DELAY_MS = 1_500;
|
||||
const RETRY_MAX_DELAY_MS = 60_000;
|
||||
|
||||
interface StatsRecord {
|
||||
burnNextHourProjected: string;
|
||||
burnedLastDay: string;
|
||||
|
|
@ -27,14 +31,54 @@ interface StatsRecord {
|
|||
const rawStatsCollections = ref<Array<StatsRecord>>([]);
|
||||
const loading = ref(false);
|
||||
const initialized = ref(false);
|
||||
const statsError = ref<string | null>(null);
|
||||
const statsRetryDelayMs = ref(RETRY_BASE_DELAY_MS);
|
||||
let statsRetryTimer: number | null = null;
|
||||
|
||||
export async function loadStatsCollection() {
|
||||
logger.info(`loadStatsCollection for chain: ${chainData.value?.path}`);
|
||||
if (!chainData.value?.graphql) {
|
||||
return [];
|
||||
function formatGraphqlError(error: unknown): string {
|
||||
if (axios.isAxiosError(error)) {
|
||||
const responseErrors = (error.response?.data as any)?.errors;
|
||||
if (Array.isArray(responseErrors) && responseErrors.length > 0) {
|
||||
return responseErrors.map((err: any) => err?.message ?? "GraphQL error").join(", ");
|
||||
}
|
||||
if (error.response?.status) {
|
||||
return `GraphQL request failed with status ${error.response.status}`;
|
||||
}
|
||||
if (error.message) {
|
||||
return error.message;
|
||||
}
|
||||
}
|
||||
if (error instanceof Error && error.message) {
|
||||
return error.message;
|
||||
}
|
||||
return "Unknown GraphQL error";
|
||||
}
|
||||
|
||||
const res = await axios.post(chainData.value?.graphql, {
|
||||
function clearStatsRetryTimer() {
|
||||
if (statsRetryTimer !== null) {
|
||||
clearTimeout(statsRetryTimer);
|
||||
statsRetryTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleStatsRetry() {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
if (statsRetryTimer !== null) {
|
||||
return;
|
||||
}
|
||||
const delay = statsRetryDelayMs.value;
|
||||
statsRetryTimer = window.setTimeout(async () => {
|
||||
statsRetryTimer = null;
|
||||
await loadStats();
|
||||
}, delay);
|
||||
statsRetryDelayMs.value = Math.min(statsRetryDelayMs.value * 2, RETRY_MAX_DELAY_MS);
|
||||
}
|
||||
|
||||
export async function loadStatsCollection(endpoint: string) {
|
||||
logger.info(`loadStatsCollection for chain: ${chainData.value?.path}`);
|
||||
const res = await axios.post(endpoint, {
|
||||
query: `query StatsQuery {
|
||||
stats(id: "0x01") {
|
||||
burnNextHourProjected
|
||||
|
|
@ -52,11 +96,16 @@ export async function loadStatsCollection() {
|
|||
totalMinted
|
||||
}
|
||||
}`,
|
||||
});
|
||||
}, { timeout: GRAPHQL_TIMEOUT_MS });
|
||||
|
||||
const errors = res.data?.errors;
|
||||
if (Array.isArray(errors) && errors.length > 0) {
|
||||
throw new Error(errors.map((err: any) => err?.message ?? "GraphQL error").join(", "));
|
||||
}
|
||||
|
||||
const stats = res.data?.data?.stats as StatsRecord | undefined;
|
||||
if (!stats) {
|
||||
return [];
|
||||
throw new Error("Stats entity not found in GraphQL response");
|
||||
}
|
||||
|
||||
return [{ ...stats, kraikenTotalSupply: stats.kraikenTotalSupply }];
|
||||
|
|
@ -178,10 +227,31 @@ const claimedSlots = computed(() => {
|
|||
export async function loadStats() {
|
||||
loading.value = true;
|
||||
|
||||
rawStatsCollections.value = await loadStatsCollection();
|
||||
const endpoint = chainData.value?.graphql?.trim();
|
||||
if (!endpoint) {
|
||||
rawStatsCollections.value = [];
|
||||
statsError.value = "GraphQL endpoint not configured for this chain.";
|
||||
clearStatsRetryTimer();
|
||||
statsRetryDelayMs.value = RETRY_BASE_DELAY_MS;
|
||||
loading.value = false;
|
||||
initialized.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = false;
|
||||
initialized.value = true;
|
||||
try {
|
||||
rawStatsCollections.value = await loadStatsCollection(endpoint);
|
||||
statsError.value = null;
|
||||
statsRetryDelayMs.value = RETRY_BASE_DELAY_MS;
|
||||
clearStatsRetryTimer();
|
||||
} catch (error) {
|
||||
console.warn("[stats] loadStats() failed", error);
|
||||
rawStatsCollections.value = [];
|
||||
statsError.value = formatGraphqlError(error);
|
||||
scheduleStatsRetry();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
initialized.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
let unwatch: any = null;
|
||||
|
|
@ -212,6 +282,7 @@ export function useStatCollection() {
|
|||
// })
|
||||
}
|
||||
onUnmounted(() => {
|
||||
clearStatsRetryTimer();
|
||||
unwatch();
|
||||
});
|
||||
|
||||
|
|
@ -227,5 +298,7 @@ export function useStatCollection() {
|
|||
maxSlots,
|
||||
stakeableSupply,
|
||||
claimedSlots,
|
||||
statsError,
|
||||
loading,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ref, onMounted, onUnmounted, reactive, computed } from "vue";
|
||||
import { type Ref } from "vue";
|
||||
import { getBalance, watchAccount, watchChainId } from "@wagmi/core";
|
||||
import { getAccount, getBalance, watchAccount, watchChainId } from "@wagmi/core";
|
||||
import { type WatchAccountReturnType, type GetAccountReturnType, type GetBalanceReturnType } from "@wagmi/core";
|
||||
import { config } from "@/wagmi";
|
||||
import { getAllowance, HarbContract, getNonce } from "@/contracts/harb";
|
||||
|
|
@ -15,18 +15,10 @@ const balance = ref<GetBalanceReturnType>({
|
|||
symbol: "",
|
||||
formatted: ""
|
||||
});
|
||||
const account = ref<GetAccountReturnType>({
|
||||
address: undefined,
|
||||
addresses: undefined,
|
||||
chain: undefined,
|
||||
chainId: undefined,
|
||||
connector: undefined,
|
||||
isConnected: false,
|
||||
isConnecting: false,
|
||||
isDisconnected: true,
|
||||
isReconnecting: false,
|
||||
status: "disconnected",
|
||||
});
|
||||
const account = ref<GetAccountReturnType>(getAccount(config as any));
|
||||
if (!account.value.chainId) {
|
||||
(account.value as any).chainId = DEFAULT_CHAIN_ID;
|
||||
}
|
||||
|
||||
const selectedChainId = computed(() => account.value.chainId ?? DEFAULT_CHAIN_ID);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue