better backend comms
This commit is contained in:
parent
d0e8623cf9
commit
02a057622c
17 changed files with 1054 additions and 134 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -25,3 +25,4 @@ onchain/node_modules/
|
|||
ponder-repo
|
||||
tmp
|
||||
foundry.lock
|
||||
services/ponder/.env.local
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
- `BASE_SEPOLIA`: Public Base Sepolia testnet
|
||||
- `BASE`: Base mainnet
|
||||
|
||||
> **Local dev**: Always start the stack via `nohup ./scripts/local_env.sh start &` (never run services individually); stop with `./scripts/local_env.sh stop`.
|
||||
|
||||
## Execution Workflow
|
||||
1. **Contracts**
|
||||
- Build/test via `forge build` and `forge test` inside `onchain/`.
|
||||
|
|
@ -67,7 +69,7 @@
|
|||
- `PONDER_NETWORK=BASE_SEPOLIA_LOCAL_FORK npm run dev`
|
||||
- `curl -X POST http://localhost:42069/graphql -d '{"query":"{ stats(id:\"0x01\"){kraikenTotalSupply}}"}'`
|
||||
- `curl http://127.0.0.1:43069/status`
|
||||
- `./scripts/local_env.sh start` boots Anvil+contracts+ponder+frontend+txnBot; stop with Ctrl+C or `./scripts/local_env.sh stop`.
|
||||
- `nohup ./scripts/local_env.sh start &` boots Anvil+contracts+ponder+frontend+txnBot; stop with Ctrl+C or `./scripts/local_env.sh stop` when you want to tear it down.
|
||||
|
||||
## Refactor Backlog
|
||||
- Replace the temporary `any` shims in `services/ponder/src/kraiken.ts` and `services/ponder/src/stake.ts` by importing the official 0.13 handler types instead of stubbing `ponder-env.d.ts`.
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ ANVIL_RPC="http://127.0.0.1:8545"
|
|||
GRAPHQL_HEALTH="http://127.0.0.1:42069/health"
|
||||
GRAPHQL_ENDPOINT="http://127.0.0.1:42069/graphql"
|
||||
FRONTEND_URL="http://127.0.0.1:5173"
|
||||
LOCAL_TXNBOT_URL="http://127.0.0.1:43069"
|
||||
|
||||
FOUNDRY_BIN=${FOUNDRY_BIN:-"$HOME/.foundry/bin"}
|
||||
FORGE="$FOUNDRY_BIN/forge"
|
||||
|
|
@ -172,10 +173,17 @@ ensure_dependencies() {
|
|||
|
||||
start_anvil() {
|
||||
if [[ -f "$ANVIL_PID_FILE" ]]; then
|
||||
log "Anvil already running (pid $(cat "$ANVIL_PID_FILE"))"
|
||||
local existing_pid
|
||||
existing_pid="$(cat "$ANVIL_PID_FILE")"
|
||||
if kill -0 "$existing_pid" 2>/dev/null; then
|
||||
log "Anvil already running (pid $existing_pid)"
|
||||
return
|
||||
fi
|
||||
|
||||
log "Found stale Anvil pid file (pid $existing_pid); restarting Anvil"
|
||||
rm -f "$ANVIL_PID_FILE"
|
||||
fi
|
||||
|
||||
log "Starting Anvil (forking $FORK_URL)"
|
||||
local anvil_args=("--fork-url" "$FORK_URL" "--chain-id" 31337 "--block-time" 1 \
|
||||
"--host" 127.0.0.1 "--port" 8545)
|
||||
|
|
@ -266,6 +274,16 @@ bootstrap_liquidity_manager() {
|
|||
log "Calling recenter()"
|
||||
"$CAST" send --rpc-url "$ANVIL_RPC" --private-key "$DEPLOYER_PK" \
|
||||
"$LIQUIDITY_MANAGER" "recenter()" >>"$SETUP_LOG" 2>&1
|
||||
|
||||
if [[ -n "$TXNBOT_ADDRESS" ]]; then
|
||||
log "Transferring recenter access to txnBot $TXNBOT_ADDRESS"
|
||||
"$CAST" rpc --rpc-url "$ANVIL_RPC" anvil_impersonateAccount "$FEE_DEST" >/dev/null
|
||||
"$CAST" send --rpc-url "$ANVIL_RPC" --from "$FEE_DEST" --unlocked \
|
||||
"$LIQUIDITY_MANAGER" "setRecenterAccess(address)" "$TXNBOT_ADDRESS" >>"$SETUP_LOG" 2>&1
|
||||
"$CAST" rpc --rpc-url "$ANVIL_RPC" anvil_stopImpersonatingAccount "$FEE_DEST" >/dev/null
|
||||
else
|
||||
log "TXNBOT_ADDRESS not set; recenter access left with deployer"
|
||||
fi
|
||||
}
|
||||
|
||||
prepare_application_state() {
|
||||
|
|
@ -320,6 +338,14 @@ fund_txnbot_wallet() {
|
|||
"$CAST" send --rpc-url "$ANVIL_RPC" --private-key "$DEPLOYER_PK" \
|
||||
"$TXNBOT_ADDRESS" --value "$TXNBOT_FUND_VALUE" >>"$SETUP_LOG" 2>&1 || \
|
||||
log "Funding txnBot wallet failed (see setup log)"
|
||||
|
||||
# Ensure local fork balance reflects the funding even if the upstream state is zero
|
||||
local fund_value_wei
|
||||
fund_value_wei="$("$CAST" --to-unit "$TXNBOT_FUND_VALUE" wei)"
|
||||
local fund_value_hex
|
||||
fund_value_hex="$("$CAST" --to-hex "$fund_value_wei")"
|
||||
"$CAST" rpc --rpc-url "$ANVIL_RPC" anvil_setBalance "$TXNBOT_ADDRESS" "$fund_value_hex" \
|
||||
>>"$SETUP_LOG" 2>&1
|
||||
}
|
||||
|
||||
start_txnbot() {
|
||||
|
|
@ -355,13 +381,26 @@ start_ponder() {
|
|||
|
||||
start_frontend() {
|
||||
if [[ -f "$WEBAPP_PID_FILE" ]]; then
|
||||
log "Frontend already running (pid $(cat "$WEBAPP_PID_FILE"))"
|
||||
log "Frontend already running (pid $(cat \"$WEBAPP_PID_FILE\"))"
|
||||
return
|
||||
fi
|
||||
|
||||
source_addresses || { log "Contract addresses not found"; exit 1; }
|
||||
|
||||
local vite_env=(
|
||||
"VITE_DEFAULT_CHAIN_ID=31337"
|
||||
"VITE_LOCAL_RPC_URL=/rpc/anvil"
|
||||
"VITE_LOCAL_RPC_PROXY_TARGET=$ANVIL_RPC"
|
||||
"VITE_KRAIKEN_ADDRESS=$KRAIKEN"
|
||||
"VITE_STAKE_ADDRESS=$STAKE"
|
||||
"VITE_SWAP_ROUTER=$SWAP_ROUTER"
|
||||
"VITE_PONDER_BASE_SEPOLIA_LOCAL_FORK=$GRAPHQL_ENDPOINT"
|
||||
"VITE_TXNBOT_BASE_SEPOLIA_LOCAL_FORK=$LOCAL_TXNBOT_URL"
|
||||
)
|
||||
|
||||
log "Starting frontend (Vite dev server)"
|
||||
pushd "$ROOT_DIR/web-app" >/dev/null
|
||||
npm run dev -- --host 0.0.0.0 --port 5173 >"$WEBAPP_LOG" 2>&1 &
|
||||
env "${vite_env[@]}" npm run dev -- --host 0.0.0.0 --port 5173 >"$WEBAPP_LOG" 2>&1 &
|
||||
popd >/dev/null
|
||||
echo $! >"$WEBAPP_PID_FILE"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
# Auto-generated by local_env.sh
|
||||
PONDER_NETWORK=BASE_SEPOLIA_LOCAL_FORK
|
||||
KRAIKEN_ADDRESS=0x56186c1e64ca8043def78d06aff222212ea5df71
|
||||
STAKE_ADDRESS=0x056e4a859558a3975761abd7385506bc4d8a8e60
|
||||
START_BLOCK=31443298
|
||||
# Use PostgreSQL connection
|
||||
DATABASE_URL=postgresql://ponder:ponder_local@localhost/ponder_local
|
||||
DATABASE_SCHEMA=ponder_local_31443298
|
||||
|
|
@ -1,10 +1,19 @@
|
|||
import { Hono } from "hono";
|
||||
import { cors } from "hono/cors";
|
||||
import { client, graphql } from "ponder";
|
||||
import { db } from "ponder:api";
|
||||
import schema from "ponder:schema";
|
||||
import { Hono } from "hono";
|
||||
import { client, graphql } from "ponder";
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
const allowedOrigins = process.env.PONDER_CORS_ORIGINS?.split(",").map((origin) => origin.trim()).filter(Boolean);
|
||||
|
||||
app.use("/*", cors({
|
||||
origin: allowedOrigins?.length ? allowedOrigins : "*",
|
||||
allowMethods: ["GET", "POST", "OPTIONS"],
|
||||
allowHeaders: ["Content-Type", "Apollo-Require-Preflight"],
|
||||
}));
|
||||
|
||||
// SQL endpoint
|
||||
app.use("/sql/*", client({ db, schema }));
|
||||
|
||||
|
|
|
|||
|
|
@ -99,22 +99,46 @@ export async function ensureStatsExists(context: any, timestamp?: bigint) {
|
|||
let statsData = await context.db.find(stats, { id: STATS_ID });
|
||||
if (!statsData) {
|
||||
const { client, contracts } = context;
|
||||
const readWithFallback = async <T>(fn: () => Promise<T>, fallback: T, label: string): Promise<T> => {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (error) {
|
||||
console.warn(`[stats.ensureStatsExists] Falling back for ${label}`, error);
|
||||
return fallback;
|
||||
}
|
||||
};
|
||||
|
||||
const [kraikenTotalSupply, stakeTotalSupply, outstandingStake] = await Promise.all([
|
||||
readWithFallback(
|
||||
() =>
|
||||
client.readContract({
|
||||
abi: contracts.Kraiken.abi,
|
||||
address: contracts.Kraiken.address,
|
||||
functionName: "totalSupply",
|
||||
}),
|
||||
0n,
|
||||
"Kraiken.totalSupply",
|
||||
),
|
||||
readWithFallback(
|
||||
() =>
|
||||
client.readContract({
|
||||
abi: contracts.Stake.abi,
|
||||
address: contracts.Stake.address,
|
||||
functionName: "totalSupply",
|
||||
}),
|
||||
0n,
|
||||
"Stake.totalSupply",
|
||||
),
|
||||
readWithFallback(
|
||||
() =>
|
||||
client.readContract({
|
||||
abi: contracts.Stake.abi,
|
||||
address: contracts.Stake.address,
|
||||
functionName: "outstandingStake",
|
||||
}),
|
||||
0n,
|
||||
"Stake.outstandingStake",
|
||||
),
|
||||
]);
|
||||
|
||||
cachedStakeTotalSupply = stakeTotalSupply;
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ import { formatBigIntDivision, InsertCommaNumber, formatBigNumber, bigInt2Number
|
|||
// import { bytesToUint256, uint256ToBytes } from "harb-lib";
|
||||
// import { getSnatchList } from "harb-lib/dist/";
|
||||
import { formatUnits } from "viem";
|
||||
import { loadActivePositions, usePositions, type Position } from "@/composables/usePositions";
|
||||
import { loadPositions, usePositions, type Position } from "@/composables/usePositions";
|
||||
import {
|
||||
calculateSnatchShortfall,
|
||||
selectSnatchPositions,
|
||||
|
|
@ -238,7 +238,7 @@ async function stakeSnatch() {
|
|||
}
|
||||
stakeSnatchLoading.value = true;
|
||||
await new Promise((resolve) => setTimeout(resolve, 10000));
|
||||
await loadActivePositions();
|
||||
await loadPositions();
|
||||
await loadStats();
|
||||
stakeSnatchLoading.value = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {bigInt2Number, formatBigIntDivision} from "@/utils/helper";
|
|||
import { computed, ref } from "vue";
|
||||
import { useStatCollection } from "@/composables/useStatCollection";
|
||||
import { useStake } from "@/composables/useStake";
|
||||
import { loadActivePositions, usePositions, type Position } from "@/composables/usePositions";
|
||||
import { usePositions, type Position } from "@/composables/usePositions";
|
||||
import { useDark } from "@/composables/useDark";
|
||||
const { darkTheme } = useDark();
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,55 @@
|
|||
const LOCAL_PONDER_URL = "http://127.0.0.1:42069/graphql";
|
||||
const LOCAL_TXNBOT_URL = "http://127.0.0.1:43069";
|
||||
import deploymentsLocal from "../../onchain/deployments-local.json";
|
||||
|
||||
export const DEFAULT_CHAIN_ID = Number(import.meta.env.VITE_DEFAULT_CHAIN_ID ?? 84532);
|
||||
const LOCAL_PONDER_URL = import.meta.env.VITE_PONDER_BASE_SEPOLIA_LOCAL_FORK ?? "http://127.0.0.1:42069/graphql";
|
||||
const LOCAL_TXNBOT_URL = import.meta.env.VITE_TXNBOT_BASE_SEPOLIA_LOCAL_FORK ?? "http://127.0.0.1:43069";
|
||||
const LOCAL_RPC_URL = import.meta.env.VITE_LOCAL_RPC_URL ?? "/rpc/anvil";
|
||||
|
||||
const localContracts = (deploymentsLocal as any)?.contracts ?? {};
|
||||
const localInfra = (deploymentsLocal as any)?.infrastructure ?? {};
|
||||
const LOCAL_KRAIKEN = (localContracts.Kraiken ?? "").trim();
|
||||
const LOCAL_STAKE = (localContracts.Stake ?? "").trim();
|
||||
const LOCAL_LM = (localContracts.LiquidityManager ?? "").trim();
|
||||
const LOCAL_WETH = (localInfra.weth ?? "0x4200000000000000000000000000000000000006").trim();
|
||||
const LOCAL_ROUTER = "0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4";
|
||||
|
||||
function detectDefaultChainId(): number {
|
||||
const envValue = import.meta.env.VITE_DEFAULT_CHAIN_ID;
|
||||
if (envValue) {
|
||||
const parsed = Number(envValue);
|
||||
if (Number.isFinite(parsed)) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
const host = window.location.hostname.toLowerCase();
|
||||
if (host === "localhost" || host === "127.0.0.1" || host === "::1") {
|
||||
return 31337;
|
||||
}
|
||||
}
|
||||
|
||||
return 84532;
|
||||
}
|
||||
|
||||
export const DEFAULT_CHAIN_ID = detectDefaultChainId();
|
||||
|
||||
export const chainsData = [
|
||||
{
|
||||
// local base sepolia fork
|
||||
id: 31337,
|
||||
graphql: LOCAL_PONDER_URL,
|
||||
path: "local",
|
||||
stake: LOCAL_STAKE,
|
||||
harb: LOCAL_KRAIKEN,
|
||||
uniswap: "",
|
||||
cheats: {
|
||||
weth: LOCAL_WETH,
|
||||
swapRouter: LOCAL_ROUTER,
|
||||
liquidityManager: LOCAL_LM,
|
||||
txnBot: LOCAL_TXNBOT_URL,
|
||||
rpc: LOCAL_RPC_URL,
|
||||
}
|
||||
},
|
||||
{
|
||||
// sepolia
|
||||
id: 11155111,
|
||||
|
|
|
|||
|
|
@ -21,10 +21,66 @@
|
|||
@click="addToken"
|
||||
variant="secondary"
|
||||
>{{ addingToken ? "Adding…" : "Add KRK token" }}</f-button>
|
||||
<f-button
|
||||
:disabled="!canAddWeth || addingWeth"
|
||||
@click="addWethToken"
|
||||
variant="secondary"
|
||||
>{{ addingWeth ? "Adding…" : "Add WETH token" }}</f-button>
|
||||
</div>
|
||||
</div>
|
||||
</f-card>
|
||||
|
||||
<f-card title="Liquidity Snapshot">
|
||||
<div class="cheats-form">
|
||||
<template v-if="!cheatConfig">
|
||||
<p class="cheats-warning">Cheat helpers are disabled for this network.</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-if="statsError">
|
||||
<p class="cheats-warning">{{ statsError }}</p>
|
||||
</template>
|
||||
<template v-else-if="statsLoading">
|
||||
<p class="cheats-hint">Loading liquidity metrics…</p>
|
||||
</template>
|
||||
<template v-else-if="liquidityStats">
|
||||
<div class="liquidity-meta">
|
||||
<span>Current tick: {{ liquidityStats.currentTick }}</span>
|
||||
<span>Token0 is WETH: {{ liquidityStats.token0isWeth ? "Yes" : "No" }}</span>
|
||||
</div>
|
||||
<div class="liquidity-meta">
|
||||
<span>Buy depth to discovery edge: {{ liquidityStats.buyDepthFormatted }} ETH</span>
|
||||
</div>
|
||||
<table class="liquidity-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Stage</th>
|
||||
<th>Tick Lower</th>
|
||||
<th>Tick Upper</th>
|
||||
<th>Liquidity</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="position in liquidityStats.positions" :key="position.label">
|
||||
<td>{{ position.label }}</td>
|
||||
<td>{{ position.tickLower }}</td>
|
||||
<td>{{ position.tickUpper }}</td>
|
||||
<td>{{ position.liquidityFormatted }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p class="cheats-hint">Liquidity stats unavailable for this network.</p>
|
||||
</template>
|
||||
</template>
|
||||
<f-button
|
||||
variant="secondary"
|
||||
:disabled="statsLoading"
|
||||
@click="refreshLiquidityStats"
|
||||
>{{ statsLoading ? "Refreshing…" : "Refresh stats" }}</f-button>
|
||||
</div>
|
||||
</f-card>
|
||||
|
||||
<div class="cheats-grid">
|
||||
<f-card title="Mint ETH">
|
||||
<div class="cheats-form">
|
||||
|
|
@ -65,7 +121,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import FButton from "@/components/fcomponents/FButton.vue";
|
||||
import FCard from "@/components/fcomponents/FCard.vue";
|
||||
import FInput from "@/components/fcomponents/FInput.vue";
|
||||
|
|
@ -74,19 +130,27 @@ import { useToast } from "vue-toastification";
|
|||
import { useAccount } from "@wagmi/vue";
|
||||
import { getChain, DEFAULT_CHAIN_ID } from "@/config";
|
||||
import { readContract, writeContract } from "@wagmi/core";
|
||||
import HarbJson from "@/assets/contracts/harb.json";
|
||||
import {
|
||||
createPublicClient,
|
||||
erc20Abi,
|
||||
formatEther,
|
||||
getAddress,
|
||||
http,
|
||||
isAddress,
|
||||
maxUint256,
|
||||
parseEther,
|
||||
toHex,
|
||||
type Address
|
||||
zeroAddress,
|
||||
type Address,
|
||||
type Abi
|
||||
} from "viem";
|
||||
|
||||
const toast = useToast();
|
||||
const { address, chainId } = useAccount();
|
||||
|
||||
const rpcUrl = ref("http://127.0.0.1:8545");
|
||||
const rpcUrl = ref("");
|
||||
let lastAutoRpcUrl = "";
|
||||
|
||||
const mintTarget = ref("");
|
||||
const mintAmount = ref("10");
|
||||
|
|
@ -99,12 +163,32 @@ const forcing = ref(false);
|
|||
|
||||
const addingNetwork = ref(false);
|
||||
const addingToken = ref(false);
|
||||
const addingWeth = ref(false);
|
||||
|
||||
watch(address, (value) => {
|
||||
if (value) {
|
||||
mintTarget.value = value;
|
||||
interface PositionState {
|
||||
liquidity: bigint;
|
||||
tickLower: number;
|
||||
tickUpper: number;
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
interface PositionView extends PositionState {
|
||||
label: (typeof POSITION_STAGE_LABELS)[number];
|
||||
liquidityFormatted: string;
|
||||
}
|
||||
|
||||
interface LiquidityStats {
|
||||
positions: PositionView[];
|
||||
currentTick: number;
|
||||
poolAddress: Address;
|
||||
token0isWeth: boolean;
|
||||
buyDepthWei: bigint;
|
||||
buyDepthFormatted: string;
|
||||
}
|
||||
|
||||
const liquidityStats = ref<LiquidityStats | null>(null);
|
||||
const statsLoading = ref(false);
|
||||
const statsError = ref<string | null>(null);
|
||||
let statsRequestId = 0;
|
||||
|
||||
const resolvedChainId = computed(() => chainId.value ?? DEFAULT_CHAIN_ID);
|
||||
const chainConfig = computed(() => getChain(resolvedChainId.value));
|
||||
|
|
@ -113,6 +197,7 @@ const cheatConfig = computed(() => chainConfig.value?.cheats ?? null);
|
|||
const canSwap = computed(() => Boolean(address.value && cheatConfig.value?.weth && cheatConfig.value?.swapRouter && chainConfig.value?.harb));
|
||||
const hasWalletProvider = computed(() => typeof window !== "undefined" && Boolean((window as any)?.ethereum?.request));
|
||||
const canAddToken = computed(() => Boolean(hasWalletProvider.value && chainConfig.value?.harb));
|
||||
const canAddWeth = computed(() => Boolean(hasWalletProvider.value && cheatConfig.value?.weth));
|
||||
const txnBotRecenterUrl = computed(() => {
|
||||
const baseUrl = cheatConfig.value?.txnBot?.trim();
|
||||
if (!baseUrl) return null;
|
||||
|
|
@ -120,6 +205,38 @@ const txnBotRecenterUrl = computed(() => {
|
|||
});
|
||||
const canTriggerRecenter = computed(() => Boolean(txnBotRecenterUrl.value));
|
||||
|
||||
watch(address, (value) => {
|
||||
if (value) {
|
||||
mintTarget.value = value;
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
watch(
|
||||
() => (cheatConfig.value?.rpc ?? "").trim(),
|
||||
(value) => {
|
||||
const trimmed = value;
|
||||
if (!rpcUrl.value || rpcUrl.value === lastAutoRpcUrl) {
|
||||
rpcUrl.value = trimmed;
|
||||
}
|
||||
lastAutoRpcUrl = trimmed;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => [rpcUrl.value, resolvedChainId.value, chainConfig.value?.harb, cheatConfig.value?.weth, cheatConfig.value?.swapRouter],
|
||||
() => {
|
||||
void loadLiquidityStats();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
if (!liquidityStats.value && !statsLoading.value) {
|
||||
void loadLiquidityStats();
|
||||
}
|
||||
});
|
||||
|
||||
function resolveChainName(chainId: number): string {
|
||||
switch (chainId) {
|
||||
case 31337:
|
||||
|
|
@ -135,6 +252,17 @@ function resolveChainName(chainId: number): string {
|
|||
}
|
||||
}
|
||||
|
||||
function resolveRpcUrl(): string {
|
||||
const value = rpcUrl.value.trim();
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
if (value.startsWith("/") && typeof window !== "undefined" && window.location?.origin) {
|
||||
return `${window.location.origin}${value}`;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
const WETH_ABI = [
|
||||
{
|
||||
inputs: [],
|
||||
|
|
@ -146,6 +274,13 @@ const WETH_ABI = [
|
|||
];
|
||||
|
||||
const SWAP_ROUTER_ABI = [
|
||||
{
|
||||
inputs: [],
|
||||
name: "factory",
|
||||
outputs: [{ internalType: "address", name: "", type: "address" }],
|
||||
stateMutability: "view",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
|
|
@ -170,11 +305,361 @@ const SWAP_ROUTER_ABI = [
|
|||
},
|
||||
];
|
||||
|
||||
const UNISWAP_FACTORY_ABI = [
|
||||
{
|
||||
inputs: [
|
||||
{ internalType: "address", name: "tokenA", type: "address" },
|
||||
{ internalType: "address", name: "tokenB", type: "address" },
|
||||
{ internalType: "uint24", name: "fee", type: "uint24" },
|
||||
],
|
||||
name: "getPool",
|
||||
outputs: [{ internalType: "address", name: "pool", type: "address" }],
|
||||
stateMutability: "view",
|
||||
type: "function",
|
||||
},
|
||||
];
|
||||
|
||||
const UNISWAP_POOL_ABI = [
|
||||
{
|
||||
inputs: [],
|
||||
name: "slot0",
|
||||
outputs: [
|
||||
{ internalType: "uint160", name: "sqrtPriceX96", type: "uint160" },
|
||||
{ internalType: "int24", name: "tick", type: "int24" },
|
||||
{ internalType: "uint16", name: "observationIndex", type: "uint16" },
|
||||
{ internalType: "uint16", name: "observationCardinality", type: "uint16" },
|
||||
{ internalType: "uint16", name: "observationCardinalityNext", type: "uint16" },
|
||||
{ internalType: "uint8", name: "feeProtocol", type: "uint8" },
|
||||
{ internalType: "bool", name: "unlocked", type: "bool" },
|
||||
],
|
||||
stateMutability: "view",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "liquidity",
|
||||
outputs: [{ internalType: "uint128", name: "", type: "uint128" }],
|
||||
stateMutability: "view",
|
||||
type: "function",
|
||||
},
|
||||
];
|
||||
|
||||
const LIQUIDITY_MANAGER_POSITIONS_ABI = [
|
||||
{
|
||||
inputs: [{ internalType: "uint8", name: "", type: "uint8" }],
|
||||
name: "positions",
|
||||
outputs: [
|
||||
{ internalType: "uint128", name: "liquidity", type: "uint128" },
|
||||
{ internalType: "int24", name: "tickLower", type: "int24" },
|
||||
{ internalType: "int24", name: "tickUpper", type: "int24" },
|
||||
],
|
||||
stateMutability: "view",
|
||||
type: "function",
|
||||
},
|
||||
];
|
||||
|
||||
const POSITION_STAGE_LABELS = ["Floor", "Anchor", "Discovery"] as const;
|
||||
const POOL_FEE = 10_000;
|
||||
const MIN_TICK = -887272;
|
||||
const MAX_TICK = 887272;
|
||||
const Q96 = 2n ** 96n;
|
||||
const MAX_UINT256_BIGINT = (1n << 256n) - 1n;
|
||||
|
||||
function formatBigInt(value: bigint): string {
|
||||
const sign = value < 0n ? "-" : "";
|
||||
const digits = (value < 0n ? -value : value).toString();
|
||||
return sign + digits.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
}
|
||||
|
||||
function formatEth(value: bigint): string {
|
||||
if (value === 0n) return "0";
|
||||
const formatted = formatEther(value);
|
||||
const [whole, frac = ""] = formatted.split(".");
|
||||
const trimmedFrac = frac.replace(/0+$/, "").slice(0, 6);
|
||||
return trimmedFrac ? `${whole}.${trimmedFrac}` : whole;
|
||||
}
|
||||
|
||||
function getSqrtRatioAtTick(tick: number): bigint {
|
||||
if (tick < MIN_TICK || tick > MAX_TICK) {
|
||||
throw new Error("Tick out of range");
|
||||
}
|
||||
const absTick = BigInt(tick < 0 ? -tick : tick);
|
||||
let ratio = (absTick & 0x1n) !== 0n
|
||||
? 0xfffcb933bd6fad37aa2d162d1a594001n
|
||||
: 0x100000000000000000000000000000000n;
|
||||
if ((absTick & 0x2n) !== 0n) ratio = (ratio * 0xfff97272373d413259a46990580e213an) >> 128n;
|
||||
if ((absTick & 0x4n) !== 0n) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdccn) >> 128n;
|
||||
if ((absTick & 0x8n) !== 0n) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0n) >> 128n;
|
||||
if ((absTick & 0x10n) !== 0n) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644n) >> 128n;
|
||||
if ((absTick & 0x20n) !== 0n) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0n) >> 128n;
|
||||
if ((absTick & 0x40n) !== 0n) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861n) >> 128n;
|
||||
if ((absTick & 0x80n) !== 0n) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053n) >> 128n;
|
||||
if ((absTick & 0x100n) !== 0n) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4n) >> 128n;
|
||||
if ((absTick & 0x200n) !== 0n) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54n) >> 128n;
|
||||
if ((absTick & 0x400n) !== 0n) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3n) >> 128n;
|
||||
if ((absTick & 0x800n) !== 0n) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9n) >> 128n;
|
||||
if ((absTick & 0x1000n) !== 0n) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825n) >> 128n;
|
||||
if ((absTick & 0x2000n) !== 0n) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5n) >> 128n;
|
||||
if ((absTick & 0x4000n) !== 0n) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7n) >> 128n;
|
||||
if ((absTick & 0x8000n) !== 0n) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6n) >> 128n;
|
||||
if ((absTick & 0x10000n) !== 0n) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9n) >> 128n;
|
||||
if ((absTick & 0x20000n) !== 0n) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604n) >> 128n;
|
||||
if ((absTick & 0x40000n) !== 0n) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98n) >> 128n;
|
||||
if ((absTick & 0x80000n) !== 0n) ratio = (ratio * 0x48a170391f7dc42444e8fa2n) >> 128n;
|
||||
if (tick > 0) {
|
||||
ratio = MAX_UINT256_BIGINT / ratio;
|
||||
}
|
||||
const result = ratio >> 32n;
|
||||
const shouldRoundUp = (ratio & 0xffffffffn) !== 0n;
|
||||
return shouldRoundUp ? result + 1n : result;
|
||||
}
|
||||
|
||||
function getAmount0ForLiquidity(sqrtRatioAX96: bigint, sqrtRatioBX96: bigint, liquidity: bigint): bigint {
|
||||
if (sqrtRatioAX96 > sqrtRatioBX96) {
|
||||
[sqrtRatioAX96, sqrtRatioBX96] = [sqrtRatioBX96, sqrtRatioAX96];
|
||||
}
|
||||
if (liquidity === 0n || sqrtRatioAX96 === 0n) {
|
||||
return 0n;
|
||||
}
|
||||
const numerator = liquidity << 96n;
|
||||
const intermediate = (sqrtRatioBX96 - sqrtRatioAX96) * numerator;
|
||||
return intermediate / sqrtRatioBX96 / sqrtRatioAX96;
|
||||
}
|
||||
|
||||
function getAmount1ForLiquidity(sqrtRatioAX96: bigint, sqrtRatioBX96: bigint, liquidity: bigint): bigint {
|
||||
if (sqrtRatioAX96 > sqrtRatioBX96) {
|
||||
[sqrtRatioAX96, sqrtRatioBX96] = [sqrtRatioBX96, sqrtRatioAX96];
|
||||
}
|
||||
if (liquidity === 0n) {
|
||||
return 0n;
|
||||
}
|
||||
return (liquidity * (sqrtRatioBX96 - sqrtRatioAX96)) / Q96;
|
||||
}
|
||||
|
||||
function calculateEthToMoveBetweenTicks(fromTick: number, toTick: number, liquidity: bigint): bigint {
|
||||
if (fromTick >= toTick || liquidity === 0n) {
|
||||
return 0n;
|
||||
}
|
||||
const sqrtFrom = getSqrtRatioAtTick(fromTick);
|
||||
const sqrtTo = getSqrtRatioAtTick(toTick);
|
||||
return getAmount0ForLiquidity(sqrtFrom, sqrtTo, liquidity);
|
||||
}
|
||||
|
||||
function calculateEthToMoveBetweenTicksDown(fromTick: number, toTick: number, liquidity: bigint): bigint {
|
||||
if (fromTick <= toTick || liquidity === 0n) {
|
||||
return 0n;
|
||||
}
|
||||
const sqrtFrom = getSqrtRatioAtTick(fromTick);
|
||||
const sqrtTo = getSqrtRatioAtTick(toTick);
|
||||
return getAmount1ForLiquidity(sqrtTo, sqrtFrom, liquidity);
|
||||
}
|
||||
|
||||
function calculateBuyDepth(
|
||||
token0isWeth: boolean,
|
||||
currentTick: number,
|
||||
anchor: PositionState,
|
||||
discovery: PositionState
|
||||
): bigint {
|
||||
if (anchor.liquidity === 0n && discovery.liquidity === 0n) {
|
||||
return 0n;
|
||||
}
|
||||
|
||||
if (token0isWeth) {
|
||||
const targetTick = Math.max(discovery.tickUpper, anchor.tickUpper);
|
||||
if (currentTick >= targetTick) {
|
||||
return 0n;
|
||||
}
|
||||
|
||||
let total = 0n;
|
||||
if (currentTick >= anchor.tickLower && currentTick < anchor.tickUpper && anchor.liquidity > 0n) {
|
||||
const anchorEnd = Math.min(targetTick, anchor.tickUpper);
|
||||
total += calculateEthToMoveBetweenTicks(currentTick, anchorEnd, anchor.liquidity);
|
||||
}
|
||||
if (targetTick > anchor.tickUpper && discovery.liquidity > 0n) {
|
||||
const discoveryStart = Math.max(currentTick, discovery.tickLower);
|
||||
if (discoveryStart < discovery.tickUpper) {
|
||||
total += calculateEthToMoveBetweenTicks(discoveryStart, targetTick, discovery.liquidity);
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
const targetTick = Math.min(discovery.tickLower, anchor.tickLower);
|
||||
if (currentTick <= targetTick) {
|
||||
return 0n;
|
||||
}
|
||||
|
||||
let total = 0n;
|
||||
if (currentTick <= anchor.tickUpper && currentTick > anchor.tickLower && anchor.liquidity > 0n) {
|
||||
const anchorEnd = Math.max(targetTick, anchor.tickLower);
|
||||
total += calculateEthToMoveBetweenTicksDown(currentTick, anchorEnd, anchor.liquidity);
|
||||
}
|
||||
if (targetTick < anchor.tickLower && discovery.liquidity > 0n) {
|
||||
const discoveryStart = Math.min(currentTick, discovery.tickUpper);
|
||||
if (discoveryStart > discovery.tickLower) {
|
||||
total += calculateEthToMoveBetweenTicksDown(discoveryStart, targetTick, discovery.liquidity);
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
async function loadLiquidityStats({ notify = false }: { notify?: boolean } = {}) {
|
||||
const requestId = ++statsRequestId;
|
||||
const cheat = cheatConfig.value;
|
||||
const chain = chainConfig.value;
|
||||
|
||||
if (!cheat || !chain || !cheat.weth || !chain.harb) {
|
||||
liquidityStats.value = null;
|
||||
statsError.value = null;
|
||||
statsLoading.value = false;
|
||||
if (notify) {
|
||||
toast.error("Liquidity helpers not configured for this network");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!rpcUrl.value) {
|
||||
liquidityStats.value = null;
|
||||
statsError.value = "Enter an RPC URL to load liquidity stats";
|
||||
statsLoading.value = false;
|
||||
if (notify) {
|
||||
toast.error(statsError.value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
statsLoading.value = true;
|
||||
statsError.value = null;
|
||||
|
||||
try {
|
||||
const targetRpcUrl = resolveRpcUrl();
|
||||
if (!targetRpcUrl) {
|
||||
throw new Error("RPC URL is empty");
|
||||
}
|
||||
|
||||
const client = createPublicClient({ transport: http(targetRpcUrl) });
|
||||
const harbAddress = ensureAddress(chain.harb, "KRAIKEN token");
|
||||
let peripheryManager: Address | null = null;
|
||||
try {
|
||||
const [managerFromContract] = await client.readContract({
|
||||
address: harbAddress,
|
||||
abi: HarbJson as Abi,
|
||||
functionName: "peripheryContracts",
|
||||
}) as [Address, Address];
|
||||
peripheryManager = managerFromContract;
|
||||
} catch (error) {
|
||||
console.warn("[cheats] Unable to read peripheryContracts", error);
|
||||
}
|
||||
|
||||
const overrideManager = (cheat as Record<string, unknown>)?.liquidityManager;
|
||||
const managerAddressCandidate = typeof overrideManager === "string" && overrideManager.trim().length > 0
|
||||
? overrideManager
|
||||
: peripheryManager;
|
||||
|
||||
if (!managerAddressCandidate) {
|
||||
throw new Error("LiquidityManager address not configured for this chain");
|
||||
}
|
||||
|
||||
const liquidityManagerAddress = ensureAddress(managerAddressCandidate, "LiquidityManager");
|
||||
|
||||
const wethAddress = ensureAddress(cheat.weth, "WETH token");
|
||||
const token0isWeth = BigInt(wethAddress) < BigInt(harbAddress);
|
||||
|
||||
const factoryAddress = await client.readContract({
|
||||
address: ensureAddress(cheat.swapRouter, "Uniswap router"),
|
||||
abi: SWAP_ROUTER_ABI as Abi,
|
||||
functionName: "factory",
|
||||
}) as Address;
|
||||
|
||||
const poolAddressRaw = await client.readContract({
|
||||
address: ensureAddress(factoryAddress, "Uniswap factory"),
|
||||
abi: UNISWAP_FACTORY_ABI as Abi,
|
||||
functionName: "getPool",
|
||||
args: token0isWeth ? [wethAddress, harbAddress, POOL_FEE] : [harbAddress, wethAddress, POOL_FEE],
|
||||
}) as Address;
|
||||
|
||||
if (!poolAddressRaw || poolAddressRaw === zeroAddress) {
|
||||
throw new Error("KRK/WETH pool not found at 1% fee tier");
|
||||
}
|
||||
|
||||
const poolAddress = ensureAddress(poolAddressRaw, "Uniswap pool");
|
||||
const slot0Response = await client.readContract({
|
||||
address: poolAddress,
|
||||
abi: UNISWAP_POOL_ABI as Abi,
|
||||
functionName: "slot0",
|
||||
}) as { tick: number } | readonly unknown[];
|
||||
|
||||
const currentTick = Array.isArray(slot0Response)
|
||||
? Number(slot0Response[1])
|
||||
: Number((slot0Response as { tick: number }).tick);
|
||||
|
||||
const [floorRaw, anchorRaw, discoveryRaw] = await Promise.all(
|
||||
[0, 1, 2].map((stage) =>
|
||||
client.readContract({
|
||||
address: liquidityManagerAddress,
|
||||
abi: LIQUIDITY_MANAGER_POSITIONS_ABI as Abi,
|
||||
functionName: "positions",
|
||||
args: [stage],
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
const toPosition = (raw: any): PositionState => ({
|
||||
liquidity: BigInt(raw?.liquidity ?? raw?.[0] ?? 0n),
|
||||
tickLower: Number(raw?.tickLower ?? raw?.[1] ?? 0),
|
||||
tickUpper: Number(raw?.tickUpper ?? raw?.[2] ?? 0),
|
||||
});
|
||||
|
||||
const floor = toPosition(floorRaw);
|
||||
const anchor = toPosition(anchorRaw);
|
||||
const discovery = toPosition(discoveryRaw);
|
||||
|
||||
const buyDepthWei = calculateBuyDepth(token0isWeth, currentTick, anchor, discovery);
|
||||
|
||||
if (statsRequestId === requestId) {
|
||||
const positions: PositionView[] = [
|
||||
{ label: POSITION_STAGE_LABELS[0], ...floor, liquidityFormatted: formatBigInt(floor.liquidity) },
|
||||
{ label: POSITION_STAGE_LABELS[1], ...anchor, liquidityFormatted: formatBigInt(anchor.liquidity) },
|
||||
{ label: POSITION_STAGE_LABELS[2], ...discovery, liquidityFormatted: formatBigInt(discovery.liquidity) },
|
||||
];
|
||||
|
||||
liquidityStats.value = {
|
||||
positions,
|
||||
currentTick,
|
||||
poolAddress,
|
||||
token0isWeth,
|
||||
buyDepthWei,
|
||||
buyDepthFormatted: formatEth(buyDepthWei),
|
||||
};
|
||||
statsError.value = null;
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (statsRequestId === requestId) {
|
||||
liquidityStats.value = null;
|
||||
const message = error?.shortMessage ?? error?.message ?? "Failed to load liquidity stats";
|
||||
statsError.value = message;
|
||||
if (notify) {
|
||||
toast.error(message);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (statsRequestId === requestId) {
|
||||
statsLoading.value = false;
|
||||
if (!statsError.value && notify) {
|
||||
toast.success("Liquidity stats refreshed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshLiquidityStats() {
|
||||
await loadLiquidityStats({ notify: true });
|
||||
}
|
||||
|
||||
async function rpcRequest<T>(method: string, params: unknown[]): Promise<T> {
|
||||
if (!rpcUrl.value) {
|
||||
throw new Error("RPC URL required");
|
||||
}
|
||||
const response = await fetch(rpcUrl.value, {
|
||||
const response = await fetch(resolveRpcUrl(), {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ jsonrpc: "2.0", id: Date.now(), method, params }),
|
||||
|
|
@ -266,17 +751,20 @@ async function addToken() {
|
|||
throw new Error("KRAIKEN token address not configured for this chain");
|
||||
}
|
||||
const harbAddress = ensureAddress(chain.harb, "KRAIKEN token");
|
||||
const chainId = resolvedChainId.value;
|
||||
const provider = (window as any).ethereum;
|
||||
const [symbol, decimals] = await Promise.all([
|
||||
readContract(config as any, {
|
||||
abi: erc20Abi,
|
||||
address: harbAddress,
|
||||
functionName: "symbol",
|
||||
chainId,
|
||||
}) as Promise<string>,
|
||||
readContract(config as any, {
|
||||
abi: erc20Abi,
|
||||
address: harbAddress,
|
||||
functionName: "decimals",
|
||||
chainId,
|
||||
}) as Promise<number>,
|
||||
]);
|
||||
await provider.request({
|
||||
|
|
@ -298,6 +786,50 @@ async function addToken() {
|
|||
}
|
||||
}
|
||||
|
||||
async function addWethToken() {
|
||||
if (addingWeth.value || !canAddWeth.value) return;
|
||||
try {
|
||||
addingWeth.value = true;
|
||||
const cheat = cheatConfig.value;
|
||||
if (!cheat?.weth) {
|
||||
throw new Error("WETH address not configured for this chain");
|
||||
}
|
||||
const wethAddress = ensureAddress(cheat.weth, "WETH token");
|
||||
const chainId = resolvedChainId.value;
|
||||
const provider = (window as any).ethereum;
|
||||
const [symbol, decimals] = await Promise.all([
|
||||
readContract(config as any, {
|
||||
abi: erc20Abi,
|
||||
address: wethAddress,
|
||||
functionName: "symbol",
|
||||
chainId,
|
||||
}) as Promise<string>,
|
||||
readContract(config as any, {
|
||||
abi: erc20Abi,
|
||||
address: wethAddress,
|
||||
functionName: "decimals",
|
||||
chainId,
|
||||
}) as Promise<number>,
|
||||
]);
|
||||
await provider.request({
|
||||
method: "wallet_watchAsset",
|
||||
params: {
|
||||
type: "ERC20",
|
||||
options: {
|
||||
address: wethAddress,
|
||||
symbol: symbol?.slice(0, 11) || "WETH",
|
||||
decimals: Number.isFinite(decimals) ? decimals : 18,
|
||||
},
|
||||
},
|
||||
});
|
||||
toast.success(`Token request sent (${symbol || "WETH"})`);
|
||||
} catch (error: any) {
|
||||
toast.error(error?.message ?? "Failed to add token");
|
||||
} finally {
|
||||
addingWeth.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function buyKrk() {
|
||||
if (!canSwap.value || swapping.value) return;
|
||||
try {
|
||||
|
|
@ -312,6 +844,7 @@ async function buyKrk() {
|
|||
const router = ensureAddress(cheatConfig.value!.swapRouter, "Swap router");
|
||||
const harb = ensureAddress(chainConfig.value!.harb, "KRAIKEN token");
|
||||
const caller = ensureAddress(address.value!, "Wallet address");
|
||||
const chainId = resolvedChainId.value;
|
||||
let amount: bigint;
|
||||
try {
|
||||
amount = parseEther(swapAmount.value || "0");
|
||||
|
|
@ -321,18 +854,68 @@ async function buyKrk() {
|
|||
if (amount <= 0n) {
|
||||
throw new Error("Amount must be greater than zero");
|
||||
}
|
||||
const factoryAddress = await readContract(config as any, {
|
||||
abi: SWAP_ROUTER_ABI,
|
||||
address: router,
|
||||
functionName: "factory",
|
||||
chainId,
|
||||
}) as Address;
|
||||
const factory = ensureAddress(factoryAddress, "Uniswap factory");
|
||||
const poolAddress = await readContract(config as any, {
|
||||
abi: UNISWAP_FACTORY_ABI,
|
||||
address: factory,
|
||||
functionName: "getPool",
|
||||
args: [weth, harb, 10_000],
|
||||
chainId,
|
||||
}) as Address;
|
||||
if (!poolAddress || poolAddress === zeroAddress) {
|
||||
throw new Error("No KRK/WETH pool found at 1% fee; deploy and recenter first");
|
||||
}
|
||||
const poolLiquidity = await readContract(config as any, {
|
||||
abi: UNISWAP_POOL_ABI,
|
||||
address: poolAddress,
|
||||
functionName: "liquidity",
|
||||
chainId,
|
||||
}) as bigint;
|
||||
if (poolLiquidity === 0n) {
|
||||
throw new Error("KRK/WETH pool has zero liquidity; run recenter before swapping");
|
||||
}
|
||||
const wethBalance = await readContract(config as any, {
|
||||
abi: erc20Abi,
|
||||
address: weth,
|
||||
functionName: "balanceOf",
|
||||
args: [caller],
|
||||
chainId,
|
||||
}) as bigint;
|
||||
|
||||
const wrapAmount = amount > wethBalance ? amount - wethBalance : 0n;
|
||||
|
||||
if (wrapAmount > 0n) {
|
||||
await writeContract(config as any, {
|
||||
abi: WETH_ABI,
|
||||
address: weth,
|
||||
functionName: "deposit",
|
||||
value: amount,
|
||||
value: wrapAmount,
|
||||
chainId,
|
||||
});
|
||||
}
|
||||
const allowance = await readContract(config as any, {
|
||||
abi: erc20Abi,
|
||||
address: weth,
|
||||
functionName: "allowance",
|
||||
args: [caller, router],
|
||||
chainId,
|
||||
}) as bigint;
|
||||
|
||||
if (allowance < amount) {
|
||||
await writeContract(config as any, {
|
||||
abi: erc20Abi,
|
||||
address: weth,
|
||||
functionName: "approve",
|
||||
args: [router, amount],
|
||||
args: [router, maxUint256],
|
||||
chainId,
|
||||
});
|
||||
}
|
||||
await writeContract(config as any, {
|
||||
abi: SWAP_ROUTER_ABI,
|
||||
address: router,
|
||||
|
|
@ -348,6 +931,7 @@ async function buyKrk() {
|
|||
sqrtPriceLimitX96: 0n,
|
||||
},
|
||||
],
|
||||
chainId,
|
||||
});
|
||||
toast.success("Swap submitted. Watch your wallet for KRK.");
|
||||
} catch (error: any) {
|
||||
|
|
@ -415,6 +999,23 @@ async function forceRecenter() {
|
|||
flex-wrap: wrap
|
||||
gap: 12px
|
||||
|
||||
.liquidity-meta
|
||||
display: flex
|
||||
flex-wrap: wrap
|
||||
gap: 12px
|
||||
font-size: 14px
|
||||
|
||||
.liquidity-table
|
||||
width: 100%
|
||||
border-collapse: collapse
|
||||
font-size: 14px
|
||||
|
||||
.liquidity-table th,
|
||||
.liquidity-table td
|
||||
padding: 6px 8px
|
||||
text-align: left
|
||||
border-bottom: 1px solid #2a2a2a
|
||||
|
||||
.cheats-hint
|
||||
font-size: 12px
|
||||
color: #A3A3A3
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {bigInt2Number, formatBigIntDivision} from "@/utils/helper";
|
|||
import { computed, ref } from "vue";
|
||||
import { useStatCollection } from "@/composables/useStatCollection";
|
||||
import { useStake } from "@/composables/useStake";
|
||||
import { loadActivePositions, usePositions, type Position } from "@/composables/usePositions";
|
||||
import { usePositions, type Position } from "@/composables/usePositions";
|
||||
import { useDark } from "@/composables/useDark";
|
||||
const { darkTheme } = useDark();
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
<chart-complete></chart-complete>
|
||||
<div class="hold-stake-wrapper">
|
||||
<f-card class="inner-border">
|
||||
<template v-if="wallet.account.chainId && !chainsArray.includes(wallet.account.chainId)">
|
||||
<template v-if="!isChainSupported">
|
||||
Chain not supported
|
||||
</template>
|
||||
<template v-else-if="status !== 'connected'">
|
||||
|
|
@ -80,6 +80,7 @@ import { useWallet } from "@/composables/useWallet";
|
|||
import FCard from "@/components/fcomponents/FCard.vue";
|
||||
import IconInfo from "@/components/icons/IconInfo.vue";
|
||||
import FButton from "@/components/fcomponents/FButton.vue";
|
||||
import { DEFAULT_CHAIN_ID } from "@/config";
|
||||
|
||||
// todo interface positions
|
||||
import { usePositions } from "@/composables/usePositions";
|
||||
|
|
@ -90,8 +91,8 @@ import { compactNumber, InsertCommaNumber } from "@/utils/helper";
|
|||
const { myActivePositions, tresholdValue, activePositions } = usePositions();
|
||||
|
||||
const stats = useStatCollection();
|
||||
const chains = useChains();
|
||||
const wallet = useWallet();
|
||||
const chains = useChains();
|
||||
|
||||
function calculateAverageTaxRate(data: any): number {
|
||||
console.log("data", data);
|
||||
|
|
@ -104,10 +105,10 @@ function calculateAverageTaxRate(data: any): number {
|
|||
return averageTaxRate * 100;
|
||||
}
|
||||
|
||||
const chainsArray = computed(() => chains.value.map((chain) => chain.id));
|
||||
|
||||
|
||||
const averageTaxRate = computed(() => calculateAverageTaxRate(activePositions.value));
|
||||
const supportedChainIds = computed(() => chains.value.map((chain) => chain.id));
|
||||
const currentChainId = computed(() => wallet.account.chainId ?? DEFAULT_CHAIN_ID);
|
||||
const isChainSupported = computed(() => supportedChainIds.value.includes(currentChainId.value));
|
||||
onMounted(async () => {});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,54 @@
|
|||
import { http, createConfig, createStorage } from "@wagmi/vue";
|
||||
import { baseSepolia } from "@wagmi/vue/chains";
|
||||
import { coinbaseWallet, walletConnect } from "@wagmi/vue/connectors";
|
||||
import { defineChain } from "viem";
|
||||
|
||||
const LOCAL_RPC_URL = import.meta.env.VITE_LOCAL_RPC_URL ?? "/rpc/anvil";
|
||||
|
||||
const kraikenLocalFork = defineChain({
|
||||
id: 31337,
|
||||
name: "Kraiken Local Fork",
|
||||
network: "kraiken-local",
|
||||
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
||||
rpcUrls: {
|
||||
default: { http: [LOCAL_RPC_URL] },
|
||||
public: { http: [LOCAL_RPC_URL] },
|
||||
},
|
||||
blockExplorers: {
|
||||
default: { name: "Local Explorer", url: "" },
|
||||
},
|
||||
testnet: true,
|
||||
});
|
||||
|
||||
export const config = createConfig({
|
||||
chains: [baseSepolia],
|
||||
chains: [kraikenLocalFork, baseSepolia],
|
||||
storage: createStorage({ storage: window.localStorage }),
|
||||
|
||||
connectors: [
|
||||
walletConnect({
|
||||
projectId: "d8e5ecb0353c02e21d4c0867d4473ac5",
|
||||
metadata: {
|
||||
name: "Harberg",
|
||||
description: "Connect your wallet with Harberg",
|
||||
url: "https://harberg.eth.limo",
|
||||
name: "Kraiken",
|
||||
description: "Connect your wallet with Kraiken",
|
||||
url: "https://kraiken.eth.limo",
|
||||
icons: [""],
|
||||
},
|
||||
}),
|
||||
coinbaseWallet({ appName: "Harberg", darkMode: true }),
|
||||
coinbaseWallet({
|
||||
appName: "Kraiken",
|
||||
darkMode: true,
|
||||
preference: {
|
||||
options: "all",
|
||||
telemetry: false,
|
||||
},
|
||||
}),
|
||||
],
|
||||
transports: {
|
||||
[kraikenLocalFork.id]: http(LOCAL_RPC_URL),
|
||||
[baseSepolia.id]: http(),
|
||||
},
|
||||
});
|
||||
|
||||
if (typeof window !== "undefined" && config.state.chainId !== kraikenLocalFork.id) {
|
||||
config.setState((state) => ({ ...state, chainId: kraikenLocalFork.id }));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@ import vue from '@vitejs/plugin-vue'
|
|||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
export default defineConfig(() => {
|
||||
const localRpcProxyTarget = process.env.VITE_LOCAL_RPC_PROXY_TARGET
|
||||
|
||||
return {
|
||||
// base: "/HarbergPublic/",
|
||||
plugins: [
|
||||
vue(),
|
||||
|
|
@ -17,4 +20,20 @@ export default defineConfig({
|
|||
'kraiken-lib': fileURLToPath(new URL('../kraiken-lib/src', import.meta.url)),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
proxy: localRpcProxyTarget
|
||||
? {
|
||||
'/rpc/anvil': {
|
||||
target: localRpcProxyTarget,
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
rewrite: (path) => {
|
||||
const rewritten = path.replace(/^\/rpc\/anvil/, '')
|
||||
return rewritten.length === 0 ? '/' : rewritten
|
||||
},
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue