2026-02-19 20:18:27 +01:00
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { ref, watch, computed } from 'vue';
|
2026-02-24 12:34:36 +00:00
|
|
|
import { RouterLink } from 'vue-router';
|
2026-02-19 20:18:27 +01:00
|
|
|
import { useAccount } from '@harb/web3';
|
|
|
|
|
|
|
|
|
|
const { address, isConnected } = useAccount();
|
|
|
|
|
|
|
|
|
|
const holder = ref<{
|
|
|
|
|
balance: string;
|
|
|
|
|
totalEthSpent: string;
|
|
|
|
|
totalTokensAcquired: string;
|
|
|
|
|
} | null>(null);
|
|
|
|
|
const stats = ref<{ currentPriceWei: string } | null>(null);
|
|
|
|
|
const loading = ref(false);
|
|
|
|
|
|
|
|
|
|
const endpoint = `${window.location.origin}/api/graphql`;
|
|
|
|
|
|
|
|
|
|
async function fetchWalletData(addr: string) {
|
|
|
|
|
loading.value = true;
|
|
|
|
|
try {
|
2026-02-22 08:09:44 +00:00
|
|
|
const res = await fetch(endpoint, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
body: JSON.stringify({
|
2026-02-23 22:30:55 +00:00
|
|
|
query: `query WalletData($address: String!) {
|
|
|
|
|
holders(address: $address) {
|
2026-02-22 08:09:44 +00:00
|
|
|
balance
|
|
|
|
|
totalEthSpent
|
|
|
|
|
totalTokensAcquired
|
|
|
|
|
}
|
|
|
|
|
protocolStatss(where: { id: "0x01" }) {
|
|
|
|
|
items { currentPriceWei }
|
|
|
|
|
}
|
|
|
|
|
}`,
|
2026-02-23 22:30:55 +00:00
|
|
|
variables: { address: addr.toLowerCase() },
|
2026-02-22 08:09:44 +00:00
|
|
|
}),
|
2026-02-19 20:18:27 +01:00
|
|
|
});
|
2026-02-22 08:09:44 +00:00
|
|
|
const json = await res.json();
|
|
|
|
|
holder.value = json?.data?.holders ?? null;
|
|
|
|
|
stats.value = json?.data?.protocolStatss?.items?.[0] ?? null;
|
2026-02-19 20:18:27 +01:00
|
|
|
} catch {
|
|
|
|
|
holder.value = null;
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
watch([address, isConnected], () => {
|
|
|
|
|
if (isConnected.value && address.value) {
|
|
|
|
|
void fetchWalletData(address.value);
|
|
|
|
|
} else {
|
|
|
|
|
holder.value = null;
|
|
|
|
|
}
|
|
|
|
|
}, { immediate: true });
|
|
|
|
|
|
|
|
|
|
function fmt(wei: string, decimals = 4) {
|
|
|
|
|
const n = Number(wei) / 1e18;
|
|
|
|
|
return n.toLocaleString('en', { maximumFractionDigits: decimals });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const balanceKrk = computed(() => fmt(holder.value?.balance ?? '0', 2));
|
|
|
|
|
const hasPosition = computed(() => holder.value && BigInt(holder.value.balance) > 0n);
|
|
|
|
|
|
|
|
|
|
const avgCost = computed(() => {
|
|
|
|
|
if (!holder.value) return 0;
|
|
|
|
|
const spent = Number(holder.value.totalEthSpent) / 1e18;
|
|
|
|
|
const acquired = Number(holder.value.totalTokensAcquired) / 1e18;
|
|
|
|
|
return acquired > 0 ? spent / acquired : 0;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const currentPrice = computed(() =>
|
|
|
|
|
stats.value ? Number(stats.value.currentPriceWei) / 1e18 : 0,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const pnlPercent = computed(() => {
|
|
|
|
|
if (avgCost.value === 0) return 0;
|
|
|
|
|
return (currentPrice.value / avgCost.value - 1) * 100;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const pnlClass = computed(() => (pnlPercent.value >= 0 ? 'positive' : 'negative'));
|
|
|
|
|
|
2026-02-24 12:34:36 +00:00
|
|
|
const walletRoute = computed(() => ({ name: 'wallet', params: { address: address.value } }));
|
2026-02-19 20:18:27 +01:00
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<div v-if="isConnected && hasPosition" class="wallet-card" :class="pnlClass">
|
|
|
|
|
<div class="wallet-card__balance">{{ balanceKrk }} KRK</div>
|
|
|
|
|
<div v-if="avgCost > 0" class="wallet-card__pnl">
|
|
|
|
|
{{ pnlPercent >= 0 ? '+' : '' }}{{ pnlPercent.toFixed(1) }}%
|
|
|
|
|
</div>
|
2026-02-24 12:34:36 +00:00
|
|
|
<RouterLink :to="walletRoute" class="wallet-card__link">View Dashboard →</RouterLink>
|
2026-02-19 20:18:27 +01:00
|
|
|
</div>
|
|
|
|
|
<div v-else-if="isConnected && !loading && !hasPosition" class="wallet-card wallet-card--empty">
|
|
|
|
|
<span>No KRK yet</span>
|
|
|
|
|
<a href="#get-krk" class="wallet-card__link">Get KRK →</a>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style lang="sass" scoped>
|
|
|
|
|
.wallet-card
|
|
|
|
|
display: flex
|
|
|
|
|
align-items: center
|
|
|
|
|
gap: 1rem
|
|
|
|
|
padding: 0.75rem 1.25rem
|
|
|
|
|
border-radius: 12px
|
|
|
|
|
background: rgba(255, 255, 255, 0.06)
|
|
|
|
|
border: 1px solid rgba(255, 255, 255, 0.15)
|
|
|
|
|
margin-top: 1rem
|
|
|
|
|
|
|
|
|
|
&.positive
|
|
|
|
|
border-color: rgba(16, 185, 129, 0.4)
|
|
|
|
|
background: rgba(16, 185, 129, 0.06)
|
|
|
|
|
|
|
|
|
|
&.negative
|
|
|
|
|
border-color: rgba(239, 68, 68, 0.4)
|
|
|
|
|
background: rgba(239, 68, 68, 0.06)
|
|
|
|
|
|
|
|
|
|
&--empty
|
|
|
|
|
color: #9a9898
|
|
|
|
|
font-size: 0.85rem
|
|
|
|
|
|
|
|
|
|
&__balance
|
|
|
|
|
font-weight: 700
|
|
|
|
|
font-size: 1.1rem
|
|
|
|
|
|
|
|
|
|
&__pnl
|
|
|
|
|
font-weight: 600
|
|
|
|
|
|
|
|
|
|
.positive &
|
|
|
|
|
color: #10b981
|
|
|
|
|
|
|
|
|
|
.negative &
|
|
|
|
|
color: #ef4444
|
|
|
|
|
|
|
|
|
|
&__link
|
|
|
|
|
margin-left: auto
|
|
|
|
|
color: #3b82f6
|
|
|
|
|
text-decoration: none
|
|
|
|
|
font-size: 0.85rem
|
|
|
|
|
|
|
|
|
|
&:hover
|
|
|
|
|
text-decoration: underline
|
|
|
|
|
</style>
|