webapp - ESLint + Prettier with pre-commit hooks (#54)

resolves #47

Co-authored-by: johba <johba@harb.eth>
Reviewed-on: https://codeberg.org/johba/harb/pulls/54
This commit is contained in:
johba 2025-10-03 16:51:44 +02:00
parent 2acb619a11
commit f8927b426e
83 changed files with 7137 additions and 5113 deletions

View file

@ -1,3 +1,10 @@
#!/usr/bin/env sh
cd kraiken-lib || exit 1
npx lint-staged
set -e
if [ -d "kraiken-lib" ]; then
(cd kraiken-lib && npx lint-staged)
fi
if [ -d "web-app" ]; then
(cd web-app && npx lint-staged)
fi

View file

@ -2,5 +2,8 @@
"devDependencies": {
"@playwright/test": "^1.55.1",
"playwright-mcp": "^0.0.12"
},
"scripts": {
"prepare": "husky"
}
}

View file

@ -0,0 +1,9 @@
{
"src/**/*.{vue,ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"src/**/*.scss": [
"prettier --write"
]
}

9
web-app/.prettierrc Normal file
View file

@ -0,0 +1,9 @@
{
"semi": true,
"singleQuote": true,
"printWidth": 140,
"tabWidth": 2,
"trailingComma": "es5",
"arrowParens": "avoid",
"vueIndentScriptAndStyle": false
}

10
web-app/env.d.ts vendored
View file

@ -1 +1,11 @@
/// <reference types="vite/client" />
import type { EIP1193Provider } from 'viem';
declare global {
interface Window {
ethereum?: EIP1193Provider;
}
}
export {};

95
web-app/eslint.config.js Normal file
View file

@ -0,0 +1,95 @@
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import pluginVue from 'eslint-plugin-vue';
import vueTsEslintConfig from '@vue/eslint-config-typescript';
import tsParser from '@typescript-eslint/parser';
import stylistic from '@stylistic/eslint-plugin';
import prettier from 'eslint-config-prettier';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
export default [
{
name: 'app/files-to-lint',
files: ['**/*.{ts,mts,tsx,vue}'],
languageOptions: {
parser: tsParser,
parserOptions: {
projectService: true,
project: [resolve(__dirname, 'tsconfig.app.json')],
tsconfigRootDir: __dirname,
sourceType: 'module',
ecmaVersion: 'latest',
allowDefaultProject: true,
},
},
plugins: {
'@stylistic': stylistic,
},
},
{
name: 'app/files-to-ignore',
ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**', '**/node_modules/**', '**/.ponder/**', '**/__tests__/**'],
},
...pluginVue.configs['flat/essential'],
...vueTsEslintConfig(),
{
name: 'app/custom-rules',
rules: {
// TypeScript rules
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
},
],
// General code quality
'no-console': 'error',
'@stylistic/indent': ['error', 2, { SwitchCase: 1 }],
'max-len': ['error', { code: 140 }],
// Vue specific rules
'vue/multi-word-component-names': 'error',
'vue/component-name-in-template-casing': ['error', 'PascalCase'],
'vue/require-prop-types': 'error',
'vue/require-default-prop': 'error',
'vue/html-indent': ['error', 2],
'vue/no-unused-vars': 'error',
// Disable rules that conflict with Prettier
'vue/max-attributes-per-line': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/html-self-closing': 'off',
// Naming conventions
camelcase: ['error', { properties: 'never', ignoreDestructuring: true, allow: ['^UNSAFE_'] }],
// Complexity rules (disabled as per requirements)
complexity: 'off',
'max-depth': 'off',
'max-lines': 'off',
'max-params': 'off',
},
},
{
name: 'app/tests-override',
files: ['src/**/__tests__/**/*.ts', 'src/**/__tests__/**/*.tsx'],
languageOptions: {
parserOptions: {
projectService: false,
project: undefined,
},
},
},
prettier,
];

2094
web-app/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -10,7 +10,12 @@
"build-only": "vite build",
"type-check": "vue-tsc --build",
"subtree": "git subtree push --prefix dist origin gh-pages",
"test": "vitest"
"test": "vitest",
"lint": "eslint . --ext .vue,.ts,.tsx",
"lint:fix": "eslint . --ext .vue,.ts,.tsx --fix",
"format": "prettier --write \"src/**/*.{vue,ts,tsx,scss}\"",
"format:check": "prettier --check \"src/**/*.{vue,ts,tsx,scss}\"",
"prepare": "husky install"
},
"dependencies": {
"@tanstack/vue-query": "^5.64.2",
@ -31,13 +36,23 @@
},
"devDependencies": {
"@iconify/vue": "^4.3.0",
"@stylistic/eslint-plugin": "^2.8.0",
"@tsconfig/node22": "^22.0.0",
"@types/node": "^22.10.7",
"@typescript-eslint/eslint-plugin": "^8.45.0",
"@typescript-eslint/parser": "^8.45.0",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/eslint-config-typescript": "^14.6.0",
"@vue/tsconfig": "^0.7.0",
"eslint": "^9.36.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-vue": "^10.5.0",
"gh-pages": "^6.1.1",
"husky": "^9.1.7",
"jsdom": "^27.0.0",
"lint-staged": "^16.2.3",
"npm-run-all2": "^7.0.2",
"prettier": "^3.6.2",
"typescript": "~5.7.3",
"vite": "^6.0.11",
"vite-plugin-vue-devtools": "^7.7.0",

View file

@ -1,37 +1,31 @@
<template>
<component :is="layoutComponent">
<router-view />
<RouterView />
</component>
</template>
<script setup lang="ts">
import { RouterView, useRoute, useRouter } from "vue-router";
import { RouterView, useRoute } from 'vue-router';
import type { LayoutName } from '@/types/router';
import DefaultLayout from '@/layouts/DefaultLayout.vue';
import NavbarLayout from '@/layouts/NavbarLayout.vue';
const layouts = {
DefaultLayout,
NavbarLayout
NavbarLayout,
};
import { computed, defineAsyncComponent, provide, ref } from "vue";
import { computed } from 'vue';
const route = useRoute();
const router = useRouter();
const layoutComponent = computed(() => {
const layoutName: LayoutName = route.meta.layout ?? 'DefaultLayout';
return layouts[layoutName]; // Jetzt kennt TypeScript den Typ!
});
</script>
<style lang="sass">
footer
margin-top: auto

View file

@ -1,7 +1,7 @@
<script setup lang="ts">
defineProps<{
msg: string
}>()
msg: string;
}>();
</script>
<template>

View file

@ -0,0 +1,55 @@
<template>
<a :href="props.href" target="_blank">
<div class="social-badge" :style="{ color: color, 'border-color': color }" :class="{ 'social-badge--dark': props.dark }">
<div class="social-badge-icon">
<component :color="color" :is="img" />
</div>
</div>
</a>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import IconDiscord from '@/components/icons/IconDiscord.vue';
import IconTwitter from '@/components/icons/IconTwitter.vue';
import IconTelegram from '@/components/icons/IconTelegram.vue';
interface Props {
type?: string;
dark?: boolean;
href?: string;
}
const props = withDefaults(defineProps<Props>(), {
type: 'discord',
dark: false,
href: '',
});
const color = computed(() => (props.dark ? 'white' : 'black'));
const icons = {
discord: IconDiscord,
twitter: IconTwitter,
telegram: IconTelegram,
} as const;
const img = computed(() => icons[props.type as keyof typeof icons] ?? null);
</script>
<style lang="sass">
.social-badge
border-radius: 14px
display: flex
border: 1px solid var(--color-social-border)
padding: 6px 20px
align-items: center
flex: 0 1 0
color: black
&:hover,&:active,&:focus
background-color: var(--color-white-hovered)
cursor: pointer
&.social-badge--dark
&:hover, &:active, &:focus
background-color: var(--color-black-hovered)
// font-size: 0
</style>

View file

@ -3,7 +3,7 @@
<div class="stake-inner">
<template v-if="!statCollection.initialized">
<div>
<f-loader></f-loader>
<FLoader></FLoader>
</div>
</template>
@ -12,131 +12,114 @@
<FSlider :min="minStakeAmount" :max="maxStakeAmount" v-model="stake.stakingAmountNumber"></FSlider>
<div class="formular">
<div class="row row-1">
<f-input label="Staking Amount" class="staking-amount" v-model="stake.stakingAmountNumber">
<FInput label="Staking Amount" class="staking-amount" v-model="stake.stakingAmountNumber">
<template v-slot:details>
<div class="balance">Balance: {{ maxStakeAmount.toFixed(2) }} $KRK</div>
<div @click="setMaxAmount" class="staking-amount-max">
<b>Max</b>
</div>
</template>
</f-input>
</FInput>
<Icon class="stake-arrow" icon="mdi:chevron-triple-right"></Icon>
<f-input
label="Owner Slots"
class="staking-amount"
disabled
:modelValue="`${stakeSlots}(${supplyFreeze?.toFixed(4)})`"
>
<FInput label="Owner Slots" class="staking-amount" disabled :modelValue="`${stakeSlots}(${supplyFreeze?.toFixed(4)})`">
<template #info>
Slots correspond to a percentage of ownership in the protocol.<br /><br />1,000 Slots =
1% Ownership<br /><br />When you unstake you get the exact percentage of the current
$KRK total supply. When the total supply increased since you staked you get more tokens
back than before.
Slots correspond to a percentage of ownership in the protocol.<br /><br />1,000 Slots = 1% Ownership<br /><br />When you
unstake you get the exact percentage of the current $KRK total supply. When the total supply increased since you staked you
get more tokens back than before.
</template>
</f-input>
</FInput>
</div>
<div class="row row-2">
<f-select :items="adjustTaxRate.taxRates" label="Tax" v-model="taxRate">
<FSelect :items="adjustTaxRate.taxRates" label="Tax" v-model="taxRate">
<template v-slot:info>
The yearly tax you have to pay to keep your slots open. The tax is paid when unstaking
or manually in the dashboard. If someone pays a higher tax they can buy you out.
The yearly tax you have to pay to keep your slots open. The tax is paid when unstaking or manually in the dashboard. If
someone pays a higher tax they can buy you out.
</template>
</f-select>
<f-input label="Floor Tax" disabled :modelValue="snatchSelection.floorTax">
</FSelect>
<FInput label="Floor Tax" disabled :modelValue="String(snatchSelection.floorTax)">
<template v-slot:info> This is the current minimum tax you have to pay to claim owner slots from other owners. </template>
</FInput>
<FInput label="Positions Buyout" disabled :modelValue="String(snatchSelection.snatchablePositions.value.length)">
<template v-slot:info>
This is the current minimum tax you have to pay to claim owner slots from other owners.
This shows you the numbers of staking positions you buy out from current owners by paying a higher tax. If you get bought
out yourself by new owners you get paid out the current market value of your position incl. your profits.
</template>
</f-input>
<f-input label="Positions Buyout" disabled :modelValue="snatchSelection.snatchablePositions.length">
<template v-slot:info>
This shows you the numbers of staking positions you buy out from current owners by
paying a higher tax. If you get bought out yourself by new owners you get paid out the
current market value of your position incl. your profits.
</template>
</f-input>
</FInput>
</div>
</div>
<f-button size="large" disabled block v-if="stake.state === 'NoBalance'">Insufficient Balance</f-button>
<f-button size="large" disabled block v-else-if="stake.stakingAmountNumber < minStakeAmount"
>Stake amount too low</f-button
>
<f-button
<FButton size="large" disabled block v-if="stake.state === 'NoBalance'">Insufficient Balance</FButton>
<FButton size="large" disabled block v-else-if="stake.stakingAmountNumber < minStakeAmount">Stake amount too low</FButton>
<FButton
size="large"
disabled
block
v-else-if="
!snatchSelection.openPositionsAvailable && stake.state === 'StakeAble' && snatchSelection.snatchablePositions.length === 0
!snatchSelection.openPositionsAvailable && stake.state === 'StakeAble' && snatchSelection.snatchablePositions.value.length === 0
"
>taxRate too low to snatch</f-button
>taxRate too low to snatch</FButton
>
<f-button
<FButton
size="large"
block
v-else-if="stake.state === 'StakeAble' && snatchSelection.snatchablePositions.length === 0"
v-else-if="stake.state === 'StakeAble' && snatchSelection.snatchablePositions.value.length === 0"
@click="stakeSnatch"
>Stake</f-button
>Stake</FButton
>
<f-button
<FButton
size="large"
block
v-else-if="stake.state === 'StakeAble' && snatchSelection.snatchablePositions.length > 0"
v-else-if="stake.state === 'StakeAble' && snatchSelection.snatchablePositions.value.length > 0"
@click="stakeSnatch"
>Snatch and Stake</f-button
>Snatch and Stake</FButton
>
<f-button size="large" outlined block v-else-if="stake.state === 'SignTransaction'"
>Sign Transaction ...</f-button
>
<f-button size="large" outlined block v-else-if="stake.state === 'Waiting'">Waiting ...</f-button>
<FButton size="large" outlined block v-else-if="stake.state === 'SignTransaction'">Sign Transaction ...</FButton>
<FButton size="large" outlined block v-else-if="stake.state === 'Waiting'">Waiting ...</FButton>
</template>
</div>
</div>
</template>
<script setup lang="ts">
import FButton from "@/components/fcomponents/FButton.vue";
import FInput from "@/components/fcomponents/FInput.vue";
import FSelect from "@/components/fcomponents/FSelect.vue";
import FLoader from "@/components/fcomponents/FLoader.vue";
import FSlider from "@/components/fcomponents/FSlider.vue";
import FOutput from "@/components/fcomponents/FOutput.vue";
import { Icon } from "@iconify/vue";
import { formatBigIntDivision, InsertCommaNumber, formatBigNumber, bigInt2Number } from "@/utils/helper";
import { formatUnits } from "viem";
import { loadPositions, usePositions } from "@/composables/usePositions";
import { useStake } from "@/composables/useStake";
import { useClaim } from "@/composables/useClaim";
import { useAdjustTaxRate } from "@/composables/useAdjustTaxRates";
import { useSnatchSelection } from "@/composables/useSnatchSelection";
import { getMinStake } from "@/contracts/harb";
import { useWallet } from "@/composables/useWallet";
import { ref, onMounted, watch, computed, inject, watchEffect } from "vue";
import { useStatCollection, loadStats } from "@/composables/useStatCollection";
import { useRoute } from "vue-router";
import FButton from '@/components/fcomponents/FButton.vue';
import FInput from '@/components/fcomponents/FInput.vue';
import FSelect from '@/components/fcomponents/FSelect.vue';
import FLoader from '@/components/fcomponents/FLoader.vue';
import FSlider from '@/components/fcomponents/FSlider.vue';
import { Icon } from '@iconify/vue';
import { bigInt2Number } from '@/utils/helper';
import { loadPositions, usePositions, type Position } from '@/composables/usePositions';
import { useStake } from '@/composables/useStake';
import { useClaim } from '@/composables/useClaim';
import { useAdjustTaxRate } from '@/composables/useAdjustTaxRates';
import { useSnatchSelection } from '@/composables/useSnatchSelection';
import { assetsToShares } from '@/contracts/stake';
import { getMinStake } from '@/contracts/harb';
import { useWallet } from '@/composables/useWallet';
import { ref, onMounted, watch, computed, watchEffect } from 'vue';
import { useStatCollection, loadStats } from '@/composables/useStatCollection';
import { useRoute } from 'vue-router';
const demo = sessionStorage.getItem("demo") === "true";
const demo = sessionStorage.getItem('demo') === 'true';
const route = useRoute();
const adjustTaxRate = useAdjustTaxRate();
const activeTab = ref("stake");
const StakeMenuOpen = ref(false);
const taxRate = ref<number>(1.0);
const loading = ref<boolean>(true);
const stakeSnatchLoading = ref<boolean>(false);
const stake = useStake();
const claim = useClaim();
const _claim = useClaim();
const wallet = useWallet();
const statCollection = useStatCollection();
const { activePositions } = usePositions();
const { activePositions: _activePositions } = usePositions();
const minStake = ref(0n);
const stakeSlots = ref();
const supplyFreeze = ref<number>(0);
let debounceTimer: ReturnType<typeof setTimeout>;
watchEffect(() => {
console.log("supplyFreeze");
if (!stake.stakingAmount) {
supplyFreeze.value = 0;
return;
@ -149,17 +132,15 @@ watchEffect(() => {
const stakeableSupplyNumber = bigInt2Number(statCollection.stakeableSupply, 18);
minStake.value = await getMinStake();
console.log(stakingAmountSharesNumber / stakeableSupplyNumber);
supplyFreeze.value = stakingAmountSharesNumber / stakeableSupplyNumber;
}, 500); // Verzögerung von 500ms
}, 500);
});
watchEffect(() => {
console.log("stakeSlots");
stakeSlots.value = (supplyFreeze.value * 1000)?.toFixed(2);
});
const tokenIssuance = computed(() => {
const _tokenIssuance = computed(() => {
if (statCollection.kraikenTotalSupply === 0n) {
return 0n;
}
@ -175,7 +156,7 @@ async function stakeSnatch() {
await stake.snatch(stake.stakingAmount, taxRate.value, snatchAblePositionsIds);
}
stakeSnatchLoading.value = true;
await new Promise((resolve) => setTimeout(resolve, 10000));
await new Promise(resolve => setTimeout(resolve, 10000));
await loadPositions();
await loadStats();
stakeSnatchLoading.value = false;
@ -183,36 +164,29 @@ async function stakeSnatch() {
watch(
route,
async (to) => {
console.log("to", to.hash);
if (to.hash === "#stake") {
console.log("StakeMenuOpen", StakeMenuOpen.value);
async to => {
if (to.hash === '#stake') {
StakeMenuOpen.value = true;
}
},
{ flush: "pre", immediate: true, deep: true }
{ flush: 'pre', immediate: true, deep: true }
);
onMounted(async () => {
try {
minStake.value = await getMinStake();
stake.stakingAmountNumber = minStakeAmount.value;
} catch (error) {
console.error("error", error);
} finally {
loading.value = false;
}
});
const minStakeAmount = computed(() => {
console.log("minStake", minStake.value);
return bigInt2Number(minStake.value, 18);
});
const maxStakeAmount = computed(() => {
if (wallet.balance?.value) {
console.log("wallet.balance.value", wallet.balance);
console.log("formatBigIntDivision(wallet.balance.value, 10n ** 18n)", bigInt2Number(wallet.balance.value, 18));
return bigInt2Number(wallet.balance.value, 18);
} else {
return 0;
@ -221,8 +195,7 @@ const maxStakeAmount = computed(() => {
watch(
minStakeAmount,
async (newValue) => {
console.log("newValue", newValue);
async newValue => {
if (newValue > stake.stakingAmountNumber && stake.stakingAmountNumber === 0) {
stake.stakingAmountNumber = minStakeAmount.value;
}
@ -231,11 +204,10 @@ watch(
);
function setMaxAmount() {
console.log("maxStakeAmount.value", maxStakeAmount.value);
stake.stakingAmountNumber = maxStakeAmount.value;
}
const snatchSelection = useSnatchSelection(demo);
const snatchSelection = useSnatchSelection(demo, taxRate);
</script>
<style lang="sass">

View file

@ -1,20 +1,20 @@
<template>
<div class="stats-output" :styles="styles">
<f-card>
<FCard>
<h6>{{ props.headline }}</h6>
<f-output :name="props.name" :price="props.price">
<FOutput :name="props.name" :price="props.price">
<template #price>
<slot name="price"></slot>
</template>
</f-output>
</f-card>
</FOutput>
</FCard>
</div>
</template>
<script setup lang="ts">
import { computed } from "vue";
import FCard from "@/components/fcomponents/FCard.vue"
import FOutput from "@/components/fcomponents/FOutput.vue"
import { computed } from 'vue';
import FCard from '@/components/fcomponents/FCard.vue';
import FOutput from '@/components/fcomponents/FOutput.vue';
interface Props {
name?: string;
headline: string;
@ -26,7 +26,10 @@ interface Styles {
width?: string;
}
const props = withDefaults(defineProps<Props>(), {});
const props = withDefaults(defineProps<Props>(), {
name: '',
width: undefined,
});
const styles = computed(() => {
const returnObject: Styles = {};

View file

@ -1,121 +0,0 @@
<template>
<f-card :bg-color="bgcolor" :border-width="0" box-shadow="unset">
<div class="info-popup">
<div class="info-popup__header">
<h6>{{ props.header }}</h6>
</div>
<div class="info-popup__body">
<div>
{{ props.subheader }}
</div>
<div v-if="props.value">
<span class="number-big">{{ props.value }}</span
>&nbsp;<span>{{ props.token }}</span>
</div>
</div>
<div v-if="!props.value">
<hr />
</div>
<div class="info-popup__body2" v-if="props.info" v-html="props.info"></div>
<div class="info-popup__footer">
<f-button light block small @click="closeToast">Okay</f-button>
</div>
</div>
<!-- <div class="body">
<h6 class="header">test{{ props.header }}</h6>
<div class="subheader toast-header">test{{ props.subheader }}</div>
<div class="amount-number">
<div class="token-amount number-mobile">test{{ props.value }}</div>
<div class="token-name">test{{ props.token }}</div>
</div>
<div class="info toast-body">test{{ props.info }}</div>
</div> -->
</f-card>
</template>
<script setup lang="ts">
import { getCurrentInstance, computed } from "vue";
import FButton from "@/components/fcomponents/FButton.vue";
import FCard from "@/components/fcomponents/FCard.vue";
import { useToast } from "vue-toastification";
import type { ToastID } from "vue-toastification/dist/types/types";
const props = defineProps({
value: String,
header: String,
subheader: String,
info: String,
token: String,
type: String,
});
const bgcolor = computed(() => {
let color = "white";
console.log("props.type");
console.log(props.type);
switch (props.type) {
case "info":
color = "#5f4884";
break;
case "error":
color = "#8B0000";
break;
default:
break;
}
return color;
});
// element.classList.add("toast-open");
const toast = useToast();
const instance = getCurrentInstance();
instance!.parent!.parent!.vnode!.el!.classList.add("toast-open");
const id = instance!.attrs["toast-id"] as ToastID;
console.log("instance", instance!.attrs["toast-id"]);
function closeToast() {
instance!.parent!.parent!.vnode!.el!.classList.remove("toast-open");
toast.dismiss(id);
}
</script>
<style lang="sass">
.info-popup
width: 342px
display: flex
color: var(--color-white)
flex-direction: column
gap: 16px
font-size: var(--font-body1)
text-align: center
hr
border: 1px solid var(--color-grey)
.info-popup__header
h6
margin: 0
color: var(--color-white)
.info-popup__body
display: flex
flex-direction: column
gap: 8px
.Vue-Toastification__container
&.toast-open
background-color: rgb(15, 15, 15, 0.7)
height: 100vh
width: 100vw
position: fixed
left: 0
bottom: 0
z-index: 10
@media (min-width: 992px)
background-color: unset
&.top-center
@media (min-width: 600px)
left: unset
margin-left: unset
.Vue-Toastification__toast
box-shadow: unset
&.modal-overlay
background-color: unset
</style>

View file

@ -0,0 +1,118 @@
<template>
<FCard :bg-color="bgcolor" :border-width="0" box-shadow="unset">
<div class="info-popup">
<div class="info-popup__header">
<h6>{{ props.header }}</h6>
</div>
<div class="info-popup__body">
<div>
{{ props.subheader }}
</div>
<div v-if="props.value">
<span class="number-big">{{ props.value }}</span
>&nbsp;<span>{{ props.token }}</span>
</div>
</div>
<div v-if="!props.value">
<hr />
</div>
<div class="info-popup__body2" v-if="props.info" v-html="props.info"></div>
<div class="info-popup__footer">
<FButton light block small @click="closeToast">Okay</FButton>
</div>
</div>
<!-- <div class="body">
<h6 class="header">test{{ props.header }}</h6>
<div class="subheader toast-header">test{{ props.subheader }}</div>
<div class="amount-number">
<div class="token-amount number-mobile">test{{ props.value }}</div>
<div class="token-name">test{{ props.token }}</div>
</div>
<div class="info toast-body">test{{ props.info }}</div>
</div> -->
</FCard>
</template>
<script setup lang="ts">
import { getCurrentInstance, computed } from 'vue';
import FButton from '@/components/fcomponents/FButton.vue';
import FCard from '@/components/fcomponents/FCard.vue';
import { useToast } from 'vue-toastification';
import type { ToastID } from 'vue-toastification/dist/types/types';
interface Props {
value?: string;
header?: string;
subheader?: string;
info?: string;
token?: string;
type?: string;
}
const props = defineProps<Props>();
const bgcolor = computed(() => {
let color = 'white';
switch (props.type) {
case 'info':
color = '#5f4884';
break;
case 'error':
color = '#8B0000';
break;
default:
break;
}
return color;
});
// element.classList.add("toast-open");
const toast = useToast();
const instance = getCurrentInstance();
instance!.parent!.parent!.vnode!.el!.classList.add('toast-open');
const id = instance!.attrs['toast-id'] as ToastID;
function closeToast() {
instance!.parent!.parent!.vnode!.el!.classList.remove('toast-open');
toast.dismiss(id);
}
</script>
<style lang="sass">
.info-popup
width: 342px
display: flex
color: var(--color-white)
flex-direction: column
gap: 16px
font-size: var(--font-body1)
text-align: center
hr
border: 1px solid var(--color-grey)
.info-popup__header
h6
margin: 0
color: var(--color-white)
.info-popup__body
display: flex
flex-direction: column
gap: 8px
.Vue-Toastification__container
&.toast-open
background-color: rgb(15, 15, 15, 0.7)
height: 100vh
width: 100vw
position: fixed
left: 0
bottom: 0
z-index: 10
@media (min-width: 992px)
background-color: unset
&.top-center
@media (min-width: 600px)
left: unset
margin-left: unset
.Vue-Toastification__toast
box-shadow: unset
&.modal-overlay
background-color: unset
</style>

View file

@ -1,29 +1,23 @@
<template>
<chart-js
:snatchedPositions="snatchPositions.map((obj) => obj.id)"
:positions="activePositions"
:dark="darkTheme"
></chart-js>
<ChartJs :snatchedPositions="snatchPositions.map(obj => obj.id)" :positions="activePositions" :dark="darkTheme"></ChartJs>
</template>
<script setup lang="ts">
import ChartJs from "@/components/chart/ChartJs.vue";
import {bigInt2Number, formatBigIntDivision} from "@/utils/helper";
import { computed, ref } from "vue";
import { useStatCollection } from "@/composables/useStatCollection";
import { useStake } from "@/composables/useStake";
import { usePositions, type Position } from "@/composables/usePositions";
import { useDark } from "@/composables/useDark";
import ChartJs from '@/components/chart/ChartJs.vue';
import { bigInt2Number, formatBigIntDivision } from '@/utils/helper';
import { computed, ref } from 'vue';
import { useStatCollection } from '@/composables/useStatCollection';
import { useStake } from '@/composables/useStake';
import { usePositions, type Position } from '@/composables/usePositions';
import { useDark } from '@/composables/useDark';
const { darkTheme } = useDark();
const { activePositions, myActivePositions, tresholdValue, myClosedPositions, createRandomPosition } = usePositions();
const { activePositions } = usePositions();
const ignoreOwner = ref(false);
const taxRate = ref<number>(1.0);
const minStakeAmount = computed(() => {
console.log("minStake", minStake.value);
return formatBigIntDivision(minStake.value, 10n ** 18n);
});
@ -45,7 +39,6 @@ const difference =
bigInt2Number(statCollection.outstandingStake, 18) +
stake.stakingAmountNumber -
bigInt2Number(statCollection.kraikenTotalSupply, 18) * 0.2;
console.log("difference", difference);
//Division ohne Rest, um zu schauen wie viele Positionen gesnatched werden könnten
const snatchAblePositionsCount = Math.floor(difference / minStakeAmount.value);
@ -64,5 +57,4 @@ if (snatchAblePositionsCount > 0) {
return [];
});
</script>

View file

@ -4,15 +4,12 @@
<div class="chart-inner" @click.stop>
<div class="chart--header">
<div class="chart--actions" v-if="props.positions?.length > 0">
<reset-zoom-button @click="resetZoom"></reset-zoom-button>
<fullscreen-button v-if="isMobile" @click="toggleFullscreen"></fullscreen-button>
<ResetZoomButton @click="resetZoom"></ResetZoomButton>
<FullscreenButton v-if="isMobile" @click="toggleFullscreen"></FullscreenButton>
</div>
</div>
<div
class="chart--body"
:class="{ 'disable-actions': props.positions?.length === 0, dark: props.dark }"
>
<div class="chart--body" :class="{ 'disable-actions': props.positions?.length === 0, dark: props.dark }">
<canvas ref="Chart1"></canvas>
<template v-if="props.positions?.length === 0">
<p class="chart--no-positions">No positions</p>
@ -35,7 +32,7 @@
</template>
<script setup lang="ts">
import { onMounted, ref, watch, shallowRef, computed } from "vue";
import { onMounted, ref, watch, shallowRef } from 'vue';
import {
BarController,
BarElement,
@ -46,25 +43,17 @@ import {
LinearScale,
PointElement,
Tooltip,
} from "chart.js";
type TooltipModel,
type ChartConfiguration,
} from 'chart.js';
// import { Chart } from "chart.js";
import zoomPlugin from "chartjs-plugin-zoom";
import { useAccount } from "@wagmi/vue";
import { useMobile } from "@/composables/useMobile";
import type { Position } from "@/composables/usePositions";
import FullscreenButton from "@/components/chart/FullscreenButton.vue";
import ResetZoomButton from "@/components/chart/ResetZoomButton.vue";
Chart.register(
zoomPlugin,
LinearScale,
CategoryScale,
BarController,
BarElement,
LineController,
LineElement,
PointElement,
Tooltip
);
import zoomPlugin from 'chartjs-plugin-zoom';
import { useAccount } from '@wagmi/vue';
import { useMobile } from '@/composables/useMobile';
import type { Position } from '@/composables/usePositions';
import FullscreenButton from '@/components/chart/FullscreenButton.vue';
import ResetZoomButton from '@/components/chart/ResetZoomButton.vue';
Chart.register(zoomPlugin, LinearScale, CategoryScale, BarController, BarElement, LineController, LineElement, PointElement, Tooltip);
interface Props {
positions: Array<Position>;
@ -81,23 +70,22 @@ const fullscreenOpen = ref(false);
const myChart = ref();
const activePosition = ref();
const tooltipRef = ref();
const positionSnatched = ref();
const _positionSnatched = ref();
const account = useAccount();
const isMobile = useMobile();
function resetZoom() {
console.log("resetZoom", { Chart1, myChart });
myChart.value.value.resetZoom();
}
function toggleFullscreen() {
if (fullscreenOpen.value) {
document.body.style.position = "unset";
document.body.style.overflow = "unset";
document.body.style.position = 'unset';
document.body.style.overflow = 'unset';
} else {
document.body.style.overflow = "hidden";
document.body.style.position = "relative";
document.body.style.overflow = 'hidden';
document.body.style.position = 'relative';
}
fullscreenOpen.value = !fullscreenOpen.value;
window.scrollTo(0, 0);
@ -105,9 +93,7 @@ function toggleFullscreen() {
watch(
() => props.positions,
(newData) => {
console.log("props.positions", props.positions);
() => {
myChart.value.value.destroy();
renderChart(props.positions);
@ -121,7 +107,7 @@ watch(
watch(
() => props.dark,
(newData) => {
() => {
myChart.value.value.destroy();
renderChart(props.positions);
@ -132,12 +118,12 @@ watch(
watch(
() => props.snatchedPositions,
(newData) => {
() => {
// myChart.value.value.destroy();
// renderChart(props.positions);
let positionIndex = 0;
if (myChart.value.value.data?.datasets[0]) {
let backgroundColorArray = myChart.value.value.data.datasets[0].backgroundColor;
const backgroundColorArray = myChart.value.value.data.datasets[0].backgroundColor;
for (let index = 0; index < backgroundColorArray.length; index++) {
const position: Position = myChart.value.value.data.datasets[0].data[index];
@ -149,16 +135,16 @@ watch(
positionIndex++;
}
if (positionIndex <= props.snatchedPositions.length && props.snatchedPositions.includes(position.id)) {
backgroundColorArray[index] = "##7550AE";
backgroundColorArray[index] = '##7550AE';
} else {
backgroundColorArray[index] = "##7550AE";
backgroundColorArray[index] = '##7550AE';
// const position = myChart.value.value.data.datasets[0].data[index];
if (position.iAmOwner) {
backgroundColorArray[index] = "#7550AE";
backgroundColorArray[index] = '#7550AE';
} else if (props.dark) {
backgroundColorArray[index] = "white";
backgroundColorArray[index] = 'white';
} else {
backgroundColorArray[index] = "black";
backgroundColorArray[index] = 'black';
}
}
}
@ -173,49 +159,46 @@ watch(
}
);
const externalTooltipHandler = (context: any) => {
const externalTooltipHandler = (context: { chart: Chart; tooltip: TooltipModel<'bar'> }) => {
const { chart, tooltip } = context;
const tooltipEl = tooltipRef.value;
// Tooltip ausblenden, wenn keine Daten angezeigt werden sollen
if (!tooltip.opacity) {
tooltipEl.style.opacity = "0";
tooltipEl.style.display = "none";
tooltipEl.style.opacity = '0';
tooltipEl.style.display = 'none';
return;
}
// Aktive Position setzen (Daten des angezeigten Punktes)
activePosition.value = tooltip.dataPoints[0].element.$context.raw;
activePosition.value = (tooltip.dataPoints[0] as { element?: { $context?: { raw?: Position } } }).element?.$context?.raw;
// Positionierung des Tooltips
const { offsetLeft: chartX, offsetTop: chartY } = chart.canvas;
// Tooltip anpassen
tooltipEl.style.opacity = "1";
tooltipEl.style.display = "flex";
tooltipEl.style.position = "absolute";
tooltipEl.style.opacity = '1';
tooltipEl.style.display = 'flex';
tooltipEl.style.position = 'absolute';
// Tooltip mittig über dem Punkt platzieren
tooltipEl.style.left = `${chartX + tooltip.caretX}px`;
tooltipEl.style.top = `${chartY + tooltip.y}px`;
// Tooltip für saubere Mitte ausrichten
tooltipEl.style.transform = "translateX(-50%)";
tooltipEl.style.pointerEvents = "none";
tooltipEl.style.transform = 'translateX(-50%)';
tooltipEl.style.pointerEvents = 'none';
};
function renderChart(data: any) {
console.log("renderChart");
function renderChart(data: Position[]) {
const backgroundColors = [];
const data1 = data.map((obj: any) => {
const data1 = data.map(obj => {
return {
...obj,
// taxRatePercentage: obj.taxRate * 100,
iAmOwner: obj.owner?.toLowerCase() === account.address.value?.toLowerCase(),
};
});
console.log("data1", data1);
for (let index = 0; index < data1.length; index++) {
const position = data1[index];
@ -226,43 +209,41 @@ function renderChart(data: any) {
// }
if (position.iAmOwner) {
backgroundColors.push("rgba(117,80,174, 0.8)");
backgroundColors.push('rgba(117,80,174, 0.8)');
} else if (props.dark) {
backgroundColors[index] = "white";
backgroundColors[index] = 'white';
} else {
backgroundColors[index] = "black";
backgroundColors[index] = 'black';
}
}
console.log("backgroundColors", backgroundColors);
myChart.value = shallowRef(
new Chart(Chart1.value, {
type: "bar",
type: 'bar',
data: {
labels: data1.map((row: any) => row.id),
labels: data1.map(row => row.id),
datasets: [
{
type: "line",
label: "TaxRate",
type: 'line',
label: 'TaxRate',
data: data1,
backgroundColor: ["#7550AE"],
borderColor: ["#7550AE"],
yAxisID: "y",
backgroundColor: ['#7550AE'],
borderColor: ['#7550AE'],
yAxisID: 'y',
parsing: {
yAxisKey: "taxRatePercentage",
xAxisKey: "id",
yAxisKey: 'taxRatePercentage',
xAxisKey: 'id',
},
},
{
type: "bar",
label: "Amount",
type: 'bar',
label: 'Amount',
data: data1,
backgroundColor: backgroundColors,
yAxisID: "y1",
yAxisID: 'y1',
parsing: {
yAxisKey: "amount",
xAxisKey: "id",
yAxisKey: 'amount',
xAxisKey: 'id',
},
},
],
@ -275,7 +256,7 @@ function renderChart(data: any) {
},
interaction: {
intersect: false,
mode: "index",
mode: 'index',
},
plugins: {
legend: {
@ -283,13 +264,13 @@ function renderChart(data: any) {
},
tooltip: {
enabled: false,
position: "nearest",
position: 'nearest',
external: externalTooltipHandler,
},
zoom: {
pan: {
enabled: true,
mode: "xy",
mode: 'xy',
},
limits: {
y: { min: 0 },
@ -301,12 +282,12 @@ function renderChart(data: any) {
pinch: {
enabled: true,
},
mode: "xy",
onZoomComplete(object: any) {
mode: 'xy',
onZoomComplete(object: { chart?: Chart }) {
// This update is needed to display up to date zoom level in the title.
// Without this, previous zoom level is displayed.
// The reason is: title uses the same beforeUpdate hook, and is evaluated before zoom.
object.chart?.update("none");
object.chart?.update('none');
},
},
},
@ -315,31 +296,31 @@ function renderChart(data: any) {
y: {
// type: "linear",
display: true,
position: "left",
max: Math.max(...data.map((o: any) => o.taxRate)) * 100 * 1.5,
position: 'left',
max: Math.max(...data.map(o => o.taxRate)) * 100 * 1.5,
// min: 0,
title: {
display: true,
text: "Tax",
color: "#7550AE",
text: 'Tax',
color: '#7550AE',
font: {
size: 16, // Hier die Schriftgröße ändern (z. B. 16px)
weight: "bold", // Falls du den Text fett machen möchtest
weight: 'bold', // Falls du den Text fett machen möchtest
},
},
},
y1: {
// type: "linear",
display: true,
position: "right",
max: Math.max(...data.map((o: any) => o.amount)) * 1.5,
position: 'right',
max: Math.max(...data.map(o => o.amount)) * 1.5,
title: {
display: true,
text: "Slots",
color: "white",
text: 'Slots',
color: 'white',
font: {
size: 16, // Hier die Schriftgröße ändern (z. B. 16px)
weight: "bold", // Falls du den Text fett machen möchtest
weight: 'bold', // Falls du den Text fett machen möchtest
},
},
grid: {
@ -357,18 +338,18 @@ function renderChart(data: any) {
},
title: {
display: true,
text: "Positions",
color: "white",
text: 'Positions',
color: 'white',
font: {
size: 16, // Hier die Schriftgröße ändern (z. B. 16px)
weight: "bold", // Falls du den Text fett machen möchtest
weight: 'bold', // Falls du den Text fett machen möchtest
},
},
},
},
},
plugins: [],
} as any)
} as unknown as ChartConfiguration)
);
}
onMounted(() => {

View file

@ -1,11 +1,7 @@
<template>
<button class="chart-button">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
class="stroke"
d="M10.8027 1.00195H16.002C16.3433 1.00195 16.6201 1.27869 16.6201 1.62006V6.81927"
stroke-width="1.85431"
/>
<path class="stroke" d="M10.8027 1.00195H16.002C16.3433 1.00195 16.6201 1.27869 16.6201 1.62006V6.81927" stroke-width="1.85431" />
<path
class="stroke"
d="M16.6191 10.5273L16.6191 15.7266C16.6191 16.0679 16.3424 16.3447 16.001 16.3447L10.8018 16.3447"

View file

@ -1,10 +1,10 @@
<template>
<f-collapse class="f-collapse-active" @collapse:opened="loadActivePositionData" :loading="loading">
<FCollapse class="f-collapse-active" @collapse:opened="loadActivePositionData" :loading="loading">
<template v-slot:header>
<div class="collapse-header">
<div class="collapse-header-row1">
<div><span class="subheader2">Tax</span> {{ props.taxRate }} %</div>
<f-button size="tiny" @click="payTax(props.id)">Pay Tax</f-button>
<FButton size="tiny" @click="payTax(props.id)">Pay Tax</FButton>
<div class="position-id">
<span class="subheader2">ID</span> <span class="number-small">{{ props.id }}</span>
</div>
@ -17,7 +17,7 @@
</div>
</div>
<div class="tags-list">
<f-tag v-if="tag">{{ tag }}</f-tag>
<FTag v-if="tag">{{ tag }}</FTag>
</div>
</div>
<!-- <div class="collapse-amount">
@ -44,66 +44,47 @@
</div>
<div class="collapsed-body--actions">
<div :class="{ 'collapse-menu-open': showTaxMenu }">
<f-button size="small" dense block outlined v-if="adjustTaxRate.state === 'SignTransaction'"
>Sign Transaction ...</f-button
<FButton size="small" dense block outlined v-if="adjustTaxRate.state === 'SignTransaction'">Sign Transaction ...</FButton>
<FButton size="small" dense outlined block v-else-if="adjustTaxRate.state === 'Waiting'" @click="unstakePosition"
>Waiting ...</FButton
>
<f-button
size="small"
dense
outlined
block
v-else-if="adjustTaxRate.state === 'Waiting'"
@click="unstakePosition"
>Waiting ...</f-button
>
<f-button
size="small"
dense
block
v-else-if="adjustTaxRate.state === 'Action' && !showTaxMenu"
@click="showTaxMenu = true"
>Adjust Tax Rate</f-button
<FButton size="small" dense block v-else-if="adjustTaxRate.state === 'Action' && !showTaxMenu" @click="showTaxMenu = true"
>Adjust Tax Rate</FButton
>
<template v-else>
<div class="collapse-menu-input">
<f-select :items="filteredTaxRates" v-model="newTaxRate"> </f-select>
<FSelect :items="filteredTaxRates" v-model="newTaxRate"> </FSelect>
</div>
<div>
<f-button size="small" dense @click="changeTax(props.id, newTaxRate)">Confirm</f-button>
<f-button size="small" dense outlined @click="showTaxMenu = false">Cancel</f-button>
<FButton size="small" dense @click="changeTax(props.id, newTaxRate)">Confirm</FButton>
<FButton size="small" dense outlined @click="showTaxMenu = false">Cancel</FButton>
</div>
</template>
</div>
<div></div>
<div>
<f-button size="small" dense block outlined v-if="unstake.state === 'SignTransaction'"
>Sign Transaction ...</f-button
>
<f-button size="small" dense outlined block v-else-if="unstake.state === 'Waiting'"
>Waiting ...</f-button
>
<f-button size="small" dense block v-else-if="unstake.state === 'Unstakeable'" @click="unstakePosition"
>Unstake</f-button
>
<FButton size="small" dense block outlined v-if="unstake.state === 'SignTransaction'">Sign Transaction ...</FButton>
<FButton size="small" dense outlined block v-else-if="unstake.state === 'Waiting'">Waiting ...</FButton>
<FButton size="small" dense block v-else-if="unstake.state === 'Unstakeable'" @click="unstakePosition">Unstake</FButton>
</div>
</div>
</f-collapse>
</FCollapse>
</template>
<script setup lang="ts">
import FButton from "@/components/fcomponents/FButton.vue";
import FTag from "@/components/fcomponents/FTag.vue";
import FSelect from "@/components/fcomponents/FSelect.vue";
import FCollapse from "@/components/fcomponents/FCollapse.vue";
import { compactNumber, formatBigNumber } from "@/utils/helper";
import { useUnstake } from "@/composables/useUnstake";
import { useAdjustTaxRate } from "@/composables/useAdjustTaxRates";
import { computed, ref, onMounted } from "vue";
import { getTaxDue, payTax } from "@/contracts/stake";
import { type Position, loadPositions } from "@/composables/usePositions";
import { useStatCollection } from "@/composables/useStatCollection";
import FButton from '@/components/fcomponents/FButton.vue';
import FTag from '@/components/fcomponents/FTag.vue';
import FSelect from '@/components/fcomponents/FSelect.vue';
import FCollapse from '@/components/fcomponents/FCollapse.vue';
import { compactNumber, formatBigNumber } from '@/utils/helper';
import { useUnstake } from '@/composables/useUnstake';
import { useAdjustTaxRate } from '@/composables/useAdjustTaxRates';
import { computed, ref, onMounted } from 'vue';
import { getTaxDue, payTax } from '@/contracts/stake';
import { type Position, loadPositions } from '@/composables/usePositions';
import { useStatCollection } from '@/composables/useStatCollection';
import { formatUnits } from "viem";
import { formatUnits } from 'viem';
const unstake = useUnstake();
const adjustTaxRate = useAdjustTaxRate();
@ -126,9 +107,9 @@ const loading = ref<boolean>(false);
const tag = computed(() => {
if (props.taxRate < props.treshold) {
return "Low Tax!";
return 'Low Tax!';
}
return "";
return '';
});
const total = computed(() => props.amount + profit.value! + -taxPaidGes.value!);
@ -139,48 +120,37 @@ async function changeTax(id: bigint, newTaxRate: number) {
}
async function unstakePosition() {
console.log(props.id);
await unstake.exitPosition(props.id);
loading.value = true;
await new Promise((resolve) => setTimeout(resolve, 5000));
console.log("loadPositions begin");
await new Promise(resolve => setTimeout(resolve, 5000));
await loadPositions();
console.log("loadPositions end");
loading.value = false;
}
async function loadActivePositionData() {
console.log("loadActivePositionData", props.position);
//loadTaxDue
taxDue.value = await getTaxDue(props.id);
console.log("taxDue", taxDue.value);
taxPaidGes.value = formatBigNumber(taxDue.value + BigInt(props.position.taxPaid), 18);
console.log("loadActivePositionData", taxPaidGes.value);
//loadTotalSupply
const multiplier =
Number(formatUnits(props.position.totalSupplyInit, 18)) /
Number(formatUnits(statCollection.kraikenTotalSupply, 18));
console.log("props.position.totalSupplyInit", props.position.totalSupplyInit);
console.log("multiplier", multiplier);
const multiplier = Number(formatUnits(props.position.totalSupplyInit, 18)) / Number(formatUnits(statCollection.kraikenTotalSupply, 18));
profit.value =
Number(formatUnits(statCollection.kraikenTotalSupply, 18)) * multiplier -
Number(formatUnits(statCollection.kraikenTotalSupply, 18));
Number(formatUnits(statCollection.kraikenTotalSupply, 18)) * multiplier - Number(formatUnits(statCollection.kraikenTotalSupply, 18));
}
onMounted(() => {
const taxRate = adjustTaxRate.taxRates.find((obj) => obj.index === props.position.taxRateIndex + 1);
if (props.position.taxRateIndex !== undefined) {
const taxRate = adjustTaxRate.taxRates.find(obj => obj.index === props.position.taxRateIndex! + 1);
if (taxRate) {
newTaxRate.value = taxRate.year;
}
}
});
const filteredTaxRates = computed(() => adjustTaxRate.taxRates.filter((obj) => obj.year > props.taxRate));
const filteredTaxRates = computed(() => adjustTaxRate.taxRates.filter(obj => obj.year > props.taxRate));
</script>
<style lang="sass">

View file

@ -1,5 +1,5 @@
<template>
<f-collapse class="f-collapse-history">
<FCollapse class="f-collapse-history">
<template v-slot:header>
<div class="collapse-header">
<div><span class="subheader2">Tax</span> {{ props.taxRate }} %</div>
@ -22,16 +22,16 @@
><span class="caption"> $KRK</span>
</div>
</div>
</f-collapse>
</FCollapse>
</template>
<script setup lang="ts">
import type { Position } from "@/composables/usePositions";
import FCollapse from "@/components/fcomponents/FCollapse.vue";
import { compactNumber } from "@/utils/helper";
import { formatUnits } from "viem";
import type { Position } from '@/composables/usePositions';
import FCollapse from '@/components/fcomponents/FCollapse.vue';
import { compactNumber } from '@/utils/helper';
import { formatUnits } from 'viem';
import { computed } from "vue";
import { computed } from 'vue';
const props = defineProps<{
taxRate: number;
taxPaid: string;
@ -42,15 +42,8 @@ const props = defineProps<{
}>();
const profit = computed(() => {
const multiplier =
Number(formatUnits(props.position.totalSupplyInit, 18)) /
Number(formatUnits(props.position.totalSupplyEnd!, 18));
console.log("multiplier", multiplier);
console.log("props.position.amount", props.position.amount);
return (
Number(formatUnits(props.position.totalSupplyEnd!, 18)) * multiplier -
Number(formatUnits(props.position.totalSupplyEnd!, 18))
);
const multiplier = Number(formatUnits(props.position.totalSupplyInit, 18)) / Number(formatUnits(props.position.totalSupplyEnd!, 18));
return Number(formatUnits(props.position.totalSupplyEnd!, 18)) * multiplier - Number(formatUnits(props.position.totalSupplyEnd!, 18));
});
</script>

View file

@ -17,30 +17,31 @@ interface Props {
dark?: boolean;
}
import { computed } from "vue";
import { computed } from 'vue';
const props = withDefaults(defineProps<Props>(), {
size: "medium",
size: 'medium',
bgColor: '',
});
const classObject = computed(() => ({
"f-btn--tiny": props.size === "tiny",
"f-btn--small": props.size === "small",
"f-btn--medium": props.size === "medium",
"f-btn--large": props.size === "large",
"f-btn--dense": props.dense,
"f-btn--disabled": props.disabled,
"f-btn--invert": props.invert,
"f-btn--block": props.block,
"f-btn--outlined": props.outlined,
"f-btn--light": props.light,
"f-btn--dark": props.dark,
'f-btn--tiny': props.size === 'tiny',
'f-btn--small': props.size === 'small',
'f-btn--medium': props.size === 'medium',
'f-btn--large': props.size === 'large',
'f-btn--dense': props.dense,
'f-btn--disabled': props.disabled,
'f-btn--invert': props.invert,
'f-btn--block': props.block,
'f-btn--outlined': props.outlined,
'f-btn--light': props.light,
'f-btn--dark': props.dark,
}));
const styleObject = computed(() => {
const returnObject: any = {};
const returnObject: Record<string, string> = {};
if (props.bgColor) {
returnObject["background-color"] = props.bgColor;
returnObject['background-color'] = props.bgColor;
}
return returnObject;
});

View file

@ -12,7 +12,7 @@
</template>
<script setup lang="ts">
import { onMounted, ref, computed } from "vue";
import { onMounted, ref, computed } from 'vue';
const fCard = ref();
@ -23,12 +23,17 @@ interface Props {
title?: string;
}
const props = withDefaults(defineProps<Props>(), {});
const props = withDefaults(defineProps<Props>(), {
bgColor: '',
borderWidth: undefined,
boxShadow: '',
title: '',
});
const computedStyles = computed(() => ({
"border-width": props.borderWidth,
"background-color": props.bgColor,
"box-shadow": props.boxShadow,
'border-width': props.borderWidth,
'background-color': props.bgColor,
'box-shadow': props.boxShadow,
}));
onMounted(() => {

View file

@ -2,7 +2,7 @@
<div class="f-collapse">
<div class="f-collapse-wrapper" :class="{ loading: props.loading }">
<template v-if="loading">
<f-loader></f-loader>
<FLoader></FLoader>
</template>
<div class="f-collapse-inner">
<slot name="header"></slot>
@ -27,23 +27,28 @@
</template>
<script setup lang="ts">
import { ref } from "vue";
import {Icon} from "@iconify/vue";
import FLoader from "@/components/fcomponents/FLoader.vue";
import { ref } from 'vue';
import { Icon } from '@iconify/vue';
import FLoader from '@/components/fcomponents/FLoader.vue';
const emit = defineEmits(["collapse:opened", "collapse:closed"]);
interface Emits {
(event: 'collapse:opened'): void;
(event: 'collapse:closed'): void;
}
const emit = defineEmits<Emits>();
const props = defineProps<{
loading?: boolean;
}>();
const isShow = ref(false);
const isShow = ref<boolean>(false);
const openClose = () => {
isShow.value = !isShow.value;
if (isShow.value) {
emit("collapse:opened")
emit('collapse:opened');
} else {
emit("collapse:closed")
emit('collapse:closed');
}
};
</script>
@ -92,6 +97,4 @@ const openClose = () => {
to
max-height: 0px
overflow: hidden
</style>

View file

@ -2,11 +2,11 @@
<div class="f-input" :class="classObject">
<div class="f-input-label subheader2">
<label v-if="props.label" :for="name">{{ props.label }}</label>
<icon>
<Icon>
<template v-slot:text v-if="slots.info">
<slot name="info"></slot>
</template>
</icon>
</Icon>
</div>
<div class="f-input__wrapper" ref="inputWrapper" @click="setFocus">
<input
@ -23,7 +23,6 @@
</div>
</div>
<div class="f-input--details" v-if="slots.details">
<slot name="details"> </slot>
</div>
@ -31,18 +30,18 @@
</template>
<script setup lang="ts">
import { computed, getCurrentInstance, useSlots, ref } from "vue";
import useClickOutside from "@/composables/useClickOutside"
import { computed, getCurrentInstance, useSlots, ref } from 'vue';
import useClickOutside from '@/composables/useClickOutside';
import Icon from "@/components/icons/IconInfo.vue";
import Icon from '@/components/icons/IconInfo.vue';
interface Props {
size?: string;
info?: string;
label?: string;
disabled?: boolean;
modelValue?: any;
modelValue?: string | number;
type?: string;
readonly?: boolean
readonly?: boolean;
}
const slots = useSlots();
@ -53,40 +52,47 @@ const instance = getCurrentInstance();
const name = `f-input-${instance!.uid}`;
const props = withDefaults(defineProps<Props>(), {
size: "normal",
size: 'normal',
disabled: false,
readonly: false,
type: "string",
type: 'string',
info: '',
label: '',
modelValue: '',
});
useClickOutside(inputWrapper, () => {
removeFocus();
})
});
const emit = defineEmits(["update:modelValue"]);
interface Emits {
(e: 'update:modelValue', value: string): void;
}
const emit = defineEmits<Emits>();
function updateModelValue($event: any) {
emit("update:modelValue", $event.target.value);
function updateModelValue($event: Event) {
const target = $event.target as HTMLInputElement;
emit('update:modelValue', target.value);
}
const classObject = computed(() => ({
"f-input--normal": props.size === "normal",
"f-input--big": props.size === "big",
"f-input--disabled": props.disabled,
'f-input--normal': props.size === 'normal',
'f-input--big': props.size === 'big',
'f-input--disabled': props.disabled,
}));
function removeFocus() {
const target = inputWrapper.value as HTMLElement
target.classList.remove("f-input__wrapper--focused")
const target = inputWrapper.value as HTMLElement;
target.classList.remove('f-input__wrapper--focused');
}
function setFocus(event:MouseEvent){
console.log("setFocus");
function setFocus(_event: MouseEvent) {
// console.log("setFocus");
if (props.disabled) {
return
return;
}
const target = inputWrapper.value as HTMLElement
target.classList.add("f-input__wrapper--focused")
const target = inputWrapper.value as HTMLElement;
target.classList.add('f-input__wrapper--focused');
}
</script>
<style lang="sass">
@ -131,5 +137,4 @@ function setFocus(event:MouseEvent){
display: flex
align-items: center
margin-right: 10px
</style>

View file

@ -23,15 +23,16 @@
</template>
<script setup lang="ts">
import { useSlots } from "vue";
const props = defineProps({
name: String,
price: [String, Number],
variant: {
type: Number,
required: false,
default: 1,
},
import { useSlots, withDefaults } from 'vue';
interface Props {
name?: string;
price?: string | number;
variant?: number;
}
const props = withDefaults(defineProps<Props>(), {
variant: 1,
name: '',
price: '',
});
const slots = useSlots();

View file

@ -1,7 +1,14 @@
<template>
<div class="o-select" @click="clickSelect" ref="componentRef">
<f-input hide-details :clearable="props.clearable" :label="props.label" :selectedable="false"
:focus="showList" :modelValue="`${year} % yearly`" readonly>
<FInput
hide-details
:clearable="props.clearable"
:label="props.label ?? undefined"
:selectedable="false"
:focus="showList"
:modelValue="`${year} % yearly`"
readonly
>
<template #info v-if="slots.info">
<slot name="info"></slot>
</template>
@ -9,11 +16,18 @@
<Icon class="toggle-collapse" v-if="showList" icon="mdi:chevron-down"></Icon>
<Icon class="toggle-collapse" v-else icon="mdi:chevron-up"></Icon>
</template>
</f-input>
</FInput>
<div class="select-list-wrapper" v-show="showList" ref="selectList">
<div class="select-list-inner" @click.stop>
<div class="select-list-item" v-for="(item, index) in props.items" :key="item.year" :class="{'active': year === item.year, 'hovered': activeIndex === index}"
@click.stop="clickItem(item)" @mouseenter="mouseEnter($event, index)" @mouseleave="mouseLeave($event, index)">
<div
class="select-list-item"
v-for="(item, index) in props.items"
:key="item.year"
:class="{ active: year === item.year, hovered: activeIndex === index }"
@click.stop="clickItem(item)"
@mouseenter="mouseEnter($event, index)"
@mouseleave="mouseLeave($event, index)"
>
<div class="circle">
<div class="active" v-if="year === item.year"></div>
<div class="hovered" v-else-if="activeIndex === index"></div>
@ -34,130 +48,94 @@
</template>
<script setup lang="ts">
import { ref, computed, onMounted, getCurrentInstance, type ComputedRef, useSlots } from "vue"
import FInput from "@/components/fcomponents/FInput.vue"
import useClickOutside from "@/composables/useClickOutside"
import {Icon} from "@iconify/vue";
const emit = defineEmits(['update:modelValue'])
const slots = useSlots();
import { ref, computed, onMounted, useSlots, withDefaults } from 'vue';
import FInput from '@/components/fcomponents/FInput.vue';
import useClickOutside from '@/composables/useClickOutside';
import { Icon } from '@iconify/vue';
interface Item {
year: number;
daily: number;
}
const props = defineProps({
items: {
type: [Array<Item>],
required: false,
default: []
},
clearable: {
type: Boolean,
required: false,
default: false
},
itemTitle: {
type: String,
required: false,
default: "label"
},
itemValue: {
type: String,
required: false,
default: "value"
},
label: {
type: String,
required: false,
default: null
},
modelValue: {
type: Number,
required: false,
default: null
},
value: {
type: String,
required: false,
default: null
},
editor: {
type: Boolean,
required: false,
default: false
interface Props {
items?: Item[];
clearable?: boolean;
itemTitle?: string;
itemValue?: string;
label?: string | null;
modelValue?: number | null;
value?: string | null;
editor?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
items: () => [],
clearable: false,
itemTitle: 'label',
itemValue: 'value',
label: null,
modelValue: null,
value: null,
editor: false,
});
const showList = ref(<boolean>false);
const slotElements = ref(<any[]>[])
const componentRef = ref(<any>null);
const selectList = ref();
interface Emits {
(e: 'update:modelValue', value: number): void;
}
const emit = defineEmits<Emits>();
const slots = useSlots();
const showList = ref<boolean>(false);
const _slotElements = ref<unknown[]>([]);
const componentRef = ref<HTMLElement | null>(null);
const selectList = ref<HTMLElement | null>(null);
const activeIndex = ref();
useClickOutside(componentRef, () => {
showList.value = false
})
showList.value = false;
});
const year = computed({
// getter
get() {
return props.modelValue || props.items[0].year
return props.modelValue || props.items[0].year;
},
// setter
set(newValue: number) {
emit("update:modelValue", newValue)
}
})
emit('update:modelValue', newValue);
},
});
function mouseEnter(event: MouseEvent, index: number) {
const target = event.target as HTMLElement;
activeIndex.value = index;
target.classList.add("active")
target.classList.add('active');
}
function mouseLeave(event:MouseEvent, index:number){
const elements = selectList.value.querySelectorAll('.select-list-item');
elements.forEach((element:HTMLElement) => {
element.classList.remove("active")
function mouseLeave(_event: MouseEvent, _index: number) {
const elements = selectList.value?.querySelectorAll('.select-list-item');
elements?.forEach(element => {
(element as HTMLElement).classList.remove('active');
});
}
onMounted(() => {
onMounted(() => {});
})
function clickSelect(event: any) {
function clickSelect(_event: unknown) {
showList.value = !showList.value;
}
function clickItem(item: any) {
console.log("item", item);
year.value = item.year
function clickItem(item: { year: number }) {
// console.log("item", item);
year.value = item.year;
showList.value = false;
console.log("showList.value", showList.value);
// console.log("showList.value", showList.value);
// emit('input', item)
}
</script>
<style lang="sass">
.o-select
position: relative
@ -228,7 +206,4 @@ function clickItem(item: any) {
&::-webkit-scrollbar-thumb
border-radius: 10px
background-color: lightgrey
</style>

View file

@ -26,16 +26,19 @@
></div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, ref, computed } from "vue";
import { onMounted, onUnmounted, ref, computed } from 'vue';
const sliderInput = ref();
const sliderInput = ref<HTMLInputElement | null>(null);
const emit = defineEmits(["update:modelValue"]);
interface Emits {
(event: 'update:modelValue', value: number): void;
}
const emit = defineEmits<Emits>();
const props = defineProps<{
modelValue: number;
@ -45,29 +48,33 @@ const props = defineProps<{
const minPercentage = computed(() => {
if (!props.min || !props.max) {
return 0n;
return 0;
}
return (props.min * 100) / props.max;
});
function updateModelValue(event: any) {
emit("update:modelValue", event.target.valueAsNumber);
function updateModelValue(event: Event) {
emit('update:modelValue', (event.target as HTMLInputElement).valueAsNumber);
}
const sliderValue = computed({
// getter
get() {
return props.modelValue || props.min;
return Number.isFinite(props.modelValue) ? props.modelValue : props.min;
},
// setter
set(newValue) {
emit("update:modelValue", newValue);
set(newValue: number) {
emit('update:modelValue', newValue);
},
});
const percentageDot = computed(() => {
let percentage = (100 * (sliderValue.value - props.min)) / (props.max - props.min);
const range = props.max - props.min;
if (range <= 0) {
return 0;
}
let percentage = (100 * (sliderValue.value - props.min)) / range;
if (percentage < 20) {
percentage = percentage + 1;
@ -77,31 +84,34 @@ const percentageDot = computed(() => {
percentage = percentage - 3;
}
return percentage
return percentage;
});
const settings = {
fill: "#7550AE",
background: "#000000",
fill: '#7550AE',
background: '#000000',
};
onMounted(() => {
sliderInput.value.addEventListener("input", setSliderValue);
if (sliderInput.value) {
sliderInput.value.addEventListener('input', setSliderValue);
}
});
function setSliderValue(event: any){
sliderValue.value = event.target.value;
function setSliderValue(event: Event) {
const target = event.target as HTMLInputElement | null;
if (target) {
sliderValue.value = target.valueAsNumber;
}
}
function testMove(event: any) {
}
function testMove(_event: unknown) {}
onUnmounted(() => {
console.log("sliderInput.value", sliderInput.value);
if (sliderInput.value) {
sliderInput.value.removeEventListener("input", setSliderValue);
sliderInput.value.removeEventListener('input', setSliderValue);
}
})
});
</script>
<style lang="scss">
@ -166,7 +176,7 @@ $range-label-width: 60px !default;
.range-slider {
width: $range-width;
position: relative;
accent-color: #7550AE;
accent-color: #7550ae;
}
.range-slider__range {
@ -242,7 +252,7 @@ $range-label-width: 60px !default;
border-top: 7px solid transparent;
border-right: 7px solid $range-label-color;
border-bottom: 7px solid transparent;
content: "";
content: '';
}
}

View file

@ -5,13 +5,20 @@
</template>
<script setup lang="ts">
import { onMounted, onBeforeMount, getCurrentInstance, ref, computed, watch, inject } from "vue";
const instance = ref<any>();
import { onMounted, onBeforeMount, getCurrentInstance, ref, watch, inject, type ComponentInternalInstance } from 'vue';
const isActive = ref<any>(null);
interface Tab {
uid: number;
name: string;
label: string;
}
const value = inject("value");
var updateTab: any = inject("updateTab");
const instance = ref<ComponentInternalInstance | null>();
const isActive = ref<unknown>(null);
const value = inject('value');
const updateTab: (tab: Tab) => void = inject('updateTab') as (tab: Tab) => void;
defineExpose({
isActive,
@ -21,9 +28,9 @@ onBeforeMount(() => {});
onMounted(() => {
instance.value = getCurrentInstance();
var addTab: any = inject("addTab");
const addTab = inject('addTab') as (tab: Tab) => void;
addTab({
uid: instance.value!.uid,
uid: instance.value?.uid ?? 0,
name: props.name,
label: props.label,
});
@ -38,25 +45,18 @@ onMounted(() => {
// })
//Props
const props = defineProps({
label: {
type: String,
required: true,
default: null,
},
name: {
type: [String],
required: true,
default: null,
},
});
interface Props {
label: string;
name: string;
}
const props = defineProps<Props>();
watch(props, (newValue, oldValue) => {
console.log("newValue", newValue);
console.log("instance", instance.value);
watch(props, (_newValue, _oldValue) => {
// console.log("newValue", newValue);
// console.log("instance", instance.value);
updateTab({
uid: instance.value!.uid,
uid: instance.value?.uid ?? 0,
name: props.name,
label: props.label,
});

View file

@ -1,14 +1,7 @@
<template>
<div class="f-tabs" ref="tabsRef">
<div class="f-tabs__header" ref="headerRef">
<div
class="f-tab"
ref="tabsRef1"
:id="`f-tab-${tab.uid}`"
v-for="tab in tabs"
:key="tab.uid"
@click="setActive(tab)"
>
<div class="f-tab" ref="tabsRef1" :id="`f-tab-${tab.uid}`" v-for="tab in tabs" :key="tab.uid" @click="setActive(tab)">
<h5>{{ tab.label }}</h5>
</div>
</div>
@ -19,40 +12,49 @@
</template>
<script setup lang="ts">
import { ref, onMounted, nextTick, h, computed, useSlots, provide, getCurrentInstance } from "vue";
import { ref, onMounted, nextTick, useSlots, provide, withDefaults } from 'vue';
interface Tab {
uid: number;
name: string;
label: string;
}
defineOptions({
inheritAttrs: false,
});
const props = defineProps({
modelValue: {
type: String,
required: false,
default: null,
},
interface Props {
modelValue?: string | null;
}
const props = withDefaults(defineProps<Props>(), {
modelValue: null,
});
const emit = defineEmits(["update:modelValue"]);
interface Emits {
(e: 'update:modelValue', value: string): void;
}
const emit = defineEmits<Emits>();
const slots1 = useSlots();
const _slots1 = useSlots();
const tabsRef = ref<HTMLElement>();
const tabsRef1 = ref<HTMLElement>();
const headerRef = ref<HTMLElement>();
const tabsArray = ref<Array<HTMLElement>>();
const tabsComponents = ref<Array<any>>();
const content = ref<any>();
const target = ref<any>();
const slots = ref<any>();
const value = ref<any>();
const tabsTest = ref<any>();
const tabs = ref<Array<any>>([]);
provide("value", value);
provide("addTab", (tab: any) => {
const _tabsArray = ref<Array<HTMLElement>>();
const _tabsComponents = ref<Array<unknown>>();
const _content = ref<unknown>();
const _target = ref<unknown>();
const _slots = ref<unknown>();
const value = ref<unknown>();
const tabsTest = ref<unknown>();
const tabs = ref<Array<Tab>>([]);
provide('value', value);
provide('addTab', (tab: Tab) => {
tabs.value.push(tab);
});
provide("updateTab", (tab: any) => {
let changedTabIndex = tabs.value.findIndex((obj) => obj.uid === tab.uid);
provide('updateTab', (tab: Tab) => {
const changedTabIndex = tabs.value.findIndex(obj => obj.uid === tab.uid);
if (changedTabIndex > -1) {
tabs.value[changedTabIndex] = tab;
}
@ -62,27 +64,32 @@ provide("updateTab", (tab: any) => {
onMounted(() => {
nextTick(() => {
if (props.modelValue) {
const tab = tabs.value.find((obj) => obj.name === props.modelValue)
const tab = tabs.value.find(obj => obj.name === props.modelValue);
if (tab) {
setActive(tab);
}
} else {
setActive(tabs.value[0]);
const firstTab = tabs.value[0];
if (firstTab) {
setActive(firstTab);
}
}
});
});
function setActive(tab: any) {
function setActive(tab: Tab) {
nextTick(() => {
const tabElement: any = headerRef.value?.querySelector(`#f-tab-${tab.uid}`);
var array = Array.prototype.slice.call(tabsRef.value?.children[0].children);
const tabElement = headerRef.value?.querySelector(`#f-tab-${tab.uid}`) as HTMLElement | null;
const array = Array.prototype.slice.call(tabsRef.value?.children[0].children);
array.forEach((element: HTMLElement) => {
element.classList.remove("f-tab--active");
element.classList.remove('f-tab--active');
});
tabElement.classList.add("f-tab--active");
if (tabElement) {
tabElement.classList.add('f-tab--active');
}
value.value = tab.name;
emit("update:modelValue", tab.name);
emit('update:modelValue', tab.name);
});
}
</script>

View file

@ -44,7 +44,6 @@ describe('FTabs.vue', () => {
expect(sections[0].attributes('style')).not.toContain('display: none');
expect(sections[1].attributes('style')).toContain('display: none');
});
it('switches to the correct tab on click', async () => {

View file

@ -16,7 +16,7 @@
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import { ref } from 'vue';
const svgPath = ref();
@ -24,6 +24,7 @@ interface Props {
color?: string;
}
const props = withDefaults(defineProps<Props>(), {});
const props = withDefaults(defineProps<Props>(), {
color: 'currentColor',
});
</script>

View file

@ -1,5 +1,5 @@
<template>
<tippy v-if="slots.text" theme="my-theme" trigger="click">
<Tippy v-if="slots.text" theme="my-theme" trigger="click">
<div class="info-icon">
<svg :width="props.size" :height="props.size" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
@ -17,26 +17,23 @@
</div>
<template #content>
<slot name="text"></slot>
</template>
</tippy>
</Tippy>
</template>
<script setup lang="ts">
import { useSlots, ref, onMounted } from "vue";
import { Tippy } from "vue-tippy";
import "tippy.js/dist/tippy.css"; // optional for styling
import { useSlots } from 'vue';
import { Tippy } from 'vue-tippy';
import 'tippy.js/dist/tippy.css'; // optional for styling
const slots = useSlots();
interface Props {
size?: string;
}
const props = withDefaults(defineProps<Props>(), {
size: "15px",
size: '15px',
});
</script>
<style lang="sass">
@ -58,5 +55,4 @@ const props = withDefaults(defineProps<Props>(), {
border-radius: var(--border-radius)
// @media (min-width: 768px)
// max-width: 400px
</style>

View file

@ -8,7 +8,7 @@
/>
<path
d="M7.45616 13.2757L6.16016 9.01067L16.1361 3.09253L8.76406 10.3509L7.45616 13.2757Z"
:fill="(props.color === 'black' ? 'white' : 'black')"
:fill="props.color === 'black' ? 'white' : 'black'"
/>
</g>
<defs>
@ -20,7 +20,7 @@
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import { ref } from 'vue';
const svgPath = ref();
@ -28,6 +28,7 @@ interface Props {
color?: string;
}
const props = withDefaults(defineProps<Props>(), {});
const props = withDefaults(defineProps<Props>(), {
color: 'currentColor',
});
</script>

View file

@ -17,7 +17,7 @@
</svg>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import { ref } from 'vue';
const svgPath = ref();
@ -25,6 +25,7 @@ interface Props {
color?: string;
}
const props = withDefaults(defineProps<Props>(), {});
const props = withDefaults(defineProps<Props>(), {
color: 'currentColor',
});
</script>

View file

@ -1,23 +1,23 @@
<template>
<template v-if="status === 'disconnected'">
<f-button class="connect-button--disconnected">Connect</f-button>
<FButton class="connect-button--disconnected">Connect</FButton>
</template>
<template v-else-if="status === 'connected'">
<f-button class="connect-button connect-button--connected">
<FButton class="connect-button connect-button--connected">
<img :src="getBlocky(address!)" alt="avatar" />
<div>{{ getAddressShortName(address!) }}</div>
</f-button>
</FButton>
</template>
<template v-else>
<f-button class="connect-button--loading">loading</f-button>
<FButton class="connect-button--loading">loading</FButton>
</template>
</template>
<script setup lang="ts">
import FButton from "@/components/fcomponents/FButton.vue";
import { getAddressShortName } from "@/utils/helper";
import { useAccount } from "@wagmi/vue";
import { getBlocky } from "@/utils/blockies";
import FButton from '@/components/fcomponents/FButton.vue';
import { getAddressShortName } from '@/utils/helper';
import { useAccount } from '@wagmi/vue';
import { getBlocky } from '@/utils/blockies';
const { address, status } = useAccount();
</script>

View file

@ -14,18 +14,16 @@
</div>
<h6 class="connected-tokens-headline">Your Tokens</h6>
<div class="connected-tokens">
<f-output name="$KRK" :price="harbAmount" :variant="3">
<FOutput name="$KRK" :price="harbAmount" :variant="3">
<template #end>
<f-button size="small" dense
><a :href="chain.chainData?.uniswap" target="_blank">Buy</a></f-button
>
<FButton size="small" dense><a :href="chain.chainData?.uniswap" target="_blank">Buy</a></FButton>
</template>
</f-output>
<f-output name="Staked $KRK" :price="compactNumber(stakedAmount)" :variant="3">
</FOutput>
<FOutput name="Staked $KRK" :price="compactNumber(stakedAmount)" :variant="3">
<template #end>
<f-button size="small" dense @click="stakeLink">Stake</f-button>
<FButton size="small" dense @click="stakeLink">Stake</FButton>
</template>
</f-output>
</FOutput>
</div>
</template>
<template v-else-if="status === 'disconnected'">
@ -56,32 +54,24 @@
</template>
<script setup lang="ts">
import { getCurrentInstance, computed } from "vue";
import { useRouter } from "vue-router";
import { getAddressShortName, compactNumber, formatBigIntDivision } from "@/utils/helper";
import { getBlocky } from "@/utils/blockies";
import FButton from "@/components/fcomponents/FButton.vue";
import FOutput from "@/components/fcomponents/FOutput.vue";
import { getCurrentInstance, computed } from 'vue';
import { useRouter } from 'vue-router';
import { getAddressShortName, compactNumber, formatBigIntDivision } from '@/utils/helper';
import { getBlocky } from '@/utils/blockies';
import FButton from '@/components/fcomponents/FButton.vue';
import FOutput from '@/components/fcomponents/FOutput.vue';
// import { usePositions } from "@/composables/usePositions";
// import { useWallet } from "@/composables/useWallet";
import { usePositions } from "@/composables/usePositions";
import { useWallet } from "@/composables/useWallet";
import { useChain } from "@/composables/useChain";
import { usePositions } from '@/composables/usePositions';
import { useWallet } from '@/composables/useWallet';
import { useChain } from '@/composables/useChain';
import {
useAccount,
useDisconnect,
useConnect,
useChainId,
useBalance,
type CreateConnectorFn,
type Connector,
} from "@wagmi/vue";
import { useAccount, useDisconnect, useConnect, useChainId, type CreateConnectorFn, type Connector } from '@wagmi/vue';
const { address, status } = useAccount();
const { disconnect } = useDisconnect();
const { connectors, connect, error } = useConnect();
const { connectors, connect } = useConnect();
const router = useRouter();
const chainId = useChainId();
const { myActivePositions } = usePositions();
@ -100,7 +90,7 @@ const harbAmount = computed(() => {
if (wallet.balance?.value) {
return compactNumber(formatBigIntDivision(wallet.balance?.value, 10n ** BigInt(wallet.balance.decimals)));
} else {
return "0";
return '0';
}
});
@ -142,19 +132,19 @@ const instance = getCurrentInstance();
// }
function closeModal() {
instance!.parent!.emit("update:modelValue", false);
instance!.parent!.emit('update:modelValue', false);
}
// //special case for metaMask, but I think that is the most used wallet
async function connectWallet(connector: CreateConnectorFn | Connector, chainId: any) {
console.log("connector", connector);
console.log("connector", connector.name);
async function connectWallet(connector: CreateConnectorFn | Connector, chainId: number) {
// console.log("connector", connector);
// console.log("connector", connector.name);
connect({ connector, chainId });
closeModal();
}
function stakeLink() {
router.push("/dashboard#stake");
router.push('/dashboard#stake');
closeModal();
}
</script>

View file

@ -1,24 +1,24 @@
<template>
<div class="footer" :style="{ color: (props.dark) ? 'white': 'black' }">
<div class="footer" :style="{ color: props.dark ? 'white' : 'black' }">
<div class="footer-inner">
<div class="footer-headline subheader2">Follow HARBERG Protocol</div>
<div class="social-links">
<social-button :dark="props.dark" type="discord" :href="discord"></social-button>
<social-button :dark="props.dark" type="telegram" :href="telegram"></social-button>
<social-button :dark="props.dark" type="twitter" :href="twitter"></social-button>
<SocialButton :dark="props.dark" type="discord" :href="discord"></SocialButton>
<SocialButton :dark="props.dark" type="telegram" :href="telegram"></SocialButton>
<SocialButton :dark="props.dark" type="twitter" :href="twitter"></SocialButton>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import socialButton from '@/components/socialButton.vue';
import SocialButton from '@/components/SocialButton.vue';
interface Props {
dark?: boolean;
}
const discord = import.meta.env.VITE_DISCORD
const discord = import.meta.env.VITE_DISCORD;
const telegram = import.meta.env.VITE_TELEGRAM;
const twitter = import.meta.env.VITE_TWITTER;

View file

@ -21,17 +21,12 @@
</nav>
<template v-if="!isMobile">
<div class="vertical-line"></div>
<network-changer></network-changer>
<NetworkChanger></NetworkChanger>
<div class="vertical-line"></div>
</template>
<connect-button @click="showPanel = true" v-if="!isMobile"></connect-button>
<icon-login @click="showPanel = true" v-else-if="isMobile && !address"></icon-login>
<img
@click="showPanel = true"
v-else-if="isMobile && address"
:src="getBlocky(address)"
alt="avatar"
/>
<ConnectButton @click="showPanel = true" v-if="!isMobile"></ConnectButton>
<IconLogin @click="showPanel = true" v-else-if="isMobile && !address"></IconLogin>
<img @click="showPanel = true" v-else-if="isMobile && address" :src="getBlocky(address)" alt="avatar" />
</div>
</div>
</div>
@ -39,27 +34,26 @@
</template>
<script setup lang="ts">
import { getBlocky } from "@/utils/blockies";
import { RouterLink, useRouter } from "vue-router";
import IconLogin from "@/components/icons/IconLogin.vue";
import ThemeToggle from "@/components/layouts/ThemeToggle.vue";
import NetworkChanger from "@/components/layouts/NetworkChanger.vue";
import ConnectButton from "@/components/layouts/ConnectButton.vue";
import { computed, inject, ref } from "vue";
import { useAccount } from "@wagmi/vue";
import { useDark } from "@/composables/useDark";
const {darkTheme} = useDark();
import { getBlocky } from '@/utils/blockies';
import { RouterLink, useRouter } from 'vue-router';
import IconLogin from '@/components/icons/IconLogin.vue';
// import ThemeToggle from "@/components/layouts/ThemeToggle.vue";
import NetworkChanger from '@/components/layouts/NetworkChanger.vue';
import ConnectButton from '@/components/layouts/ConnectButton.vue';
import { inject, ref } from 'vue';
import { useAccount } from '@wagmi/vue';
import { useDark } from '@/composables/useDark';
const { darkTheme: _darkTheme } = useDark();
const { address } = useAccount();
const router = useRouter();
const showPanel = inject<boolean>('showPanel', false);
const isMobile = inject<boolean>('isMobile', false);
const showPanel = inject<boolean>("showPanel", false);
const isMobile = inject<boolean>("isMobile", false);
const dark1 = ref(false)
const _dark1 = ref(false);
const routes = router.getRoutes();
const navbarRoutes = routes.flatMap((obj) => {
if (obj.meta.group === "navbar") {
const navbarRoutes = routes.flatMap(obj => {
if (obj.meta.group === 'navbar') {
return { title: obj.meta.title, name: obj.name, path: obj.path };
} else {
return [];

View file

@ -1,7 +1,7 @@
<template>
<div class="network-changer" v-click-outside="closeMenu">
<div class="network-changer-inner" @click="showMenu = !showMenu">
<icon-base></icon-base>
<IconBase></IconBase>
<Icon v-if="showMenu" class="toggle-icon" icon="mdi:chevron-down"></Icon>
<Icon v-else class="toggle-icon" icon="mdi:chevron-up"></Icon>
</div>
@ -10,14 +10,14 @@
<div class="list-inner" ref="listInner">
<div class="list--element" v-for="chain in chains" :key="chain.id">
<template v-if="chain.id === wallet.account.chainId">
<icon-base></icon-base>
<IconBase></IconBase>
<div>{{ chain.name }}</div>
<div>Connected</div>
</template>
<template v-else>
<icon-base></icon-base>
<IconBase></IconBase>
<div>{{ chain.name }}</div>
<f-button outlined dense @click="switchNetwork(chain)">Switch Network</f-button>
<FButton outlined dense @click="switchNetwork(chain)">Switch Network</FButton>
</template>
</div>
</div>
@ -27,12 +27,12 @@
</template>
<script setup lang="ts">
import IconBase from "@/components/icons/IconBase.vue";
import { ref } from "vue";
import { useChains, useChainId, useSwitchChain } from "@wagmi/vue";
import {Icon} from "@iconify/vue";
import FButton from "@/components/fcomponents/FButton.vue";
import { useWallet } from "@/composables/useWallet";
import IconBase from '@/components/icons/IconBase.vue';
import { ref } from 'vue';
import { useChains, useSwitchChain } from '@wagmi/vue';
import { Icon } from '@iconify/vue';
import FButton from '@/components/fcomponents/FButton.vue';
import { useWallet } from '@/composables/useWallet';
const chains = useChains();
const wallet = useWallet();
const { switchChain } = useSwitchChain();
@ -42,13 +42,12 @@ const listInner = ref();
function closeMenu() {
if (showMenu.value) {
console.log("tesat");
// console.log("tesat");
showMenu.value = false;
}
}
async function switchNetwork(chain: any) {
console.log("chain", chain);
async function switchNetwork(chain: { id: number }) {
switchChain({ chainId: chain.id });
}
</script>

View file

@ -8,15 +8,19 @@
</template>
<script setup lang="ts">
import { inject, watchEffect } from "vue";
import { inject } from 'vue';
const props = defineProps({
modelValue: Boolean,
});
interface Props {
modelValue?: boolean;
}
const props = defineProps<Props>();
defineEmits(["update:modelValue"]);
interface Emits {
(e: 'update:modelValue', value: boolean): void;
}
defineEmits<Emits>();
const showPanel = inject("showPanel");
const showPanel = inject('showPanel');
</script>
<style lang="sass">

View file

@ -13,104 +13,32 @@
style="enable-background: new 0 0 496 496"
xml:space="preserve"
>
<rect
x="152.994"
y="58.921"
transform="matrix(0.3827 0.9239 -0.9239 0.3827 168.6176 -118.5145)"
width="40.001"
height="16"
/>
<rect
x="46.9"
y="164.979"
transform="matrix(0.9239 0.3827 -0.3827 0.9239 71.29 -12.4346)"
width="40.001"
height="16"
/>
<rect
x="46.947"
y="315.048"
transform="matrix(0.9239 -0.3827 0.3827 0.9239 -118.531 50.2116)"
width="40.001"
height="16"
/>
<rect x="152.994" y="58.921" transform="matrix(0.3827 0.9239 -0.9239 0.3827 168.6176 -118.5145)" width="40.001" height="16" />
<rect x="46.9" y="164.979" transform="matrix(0.9239 0.3827 -0.3827 0.9239 71.29 -12.4346)" width="40.001" height="16" />
<rect x="46.947" y="315.048" transform="matrix(0.9239 -0.3827 0.3827 0.9239 -118.531 50.2116)" width="40.001" height="16" />
<rect
x="164.966"
y="409.112"
transform="matrix(-0.9238 -0.3828 0.3828 -0.9238 168.4872 891.7491)"
width="16"
height="39.999"
/>
<rect x="164.966" y="409.112" transform="matrix(-0.9238 -0.3828 0.3828 -0.9238 168.4872 891.7491)" width="16" height="39.999" />
<rect
x="303.031"
y="421.036"
transform="matrix(-0.3827 -0.9239 0.9239 -0.3827 50.2758 891.6655)"
width="40.001"
height="16"
/>
<rect x="303.031" y="421.036" transform="matrix(-0.3827 -0.9239 0.9239 -0.3827 50.2758 891.6655)" width="40.001" height="16" />
<rect
x="409.088"
y="315.018"
transform="matrix(-0.9239 -0.3827 0.3827 -0.9239 701.898 785.6559)"
width="40.001"
height="16"
/>
<rect x="409.088" y="315.018" transform="matrix(-0.9239 -0.3827 0.3827 -0.9239 701.898 785.6559)" width="40.001" height="16" />
<rect
x="409.054"
y="165.011"
transform="matrix(-0.9239 0.3827 -0.3827 -0.9239 891.6585 168.6574)"
width="40.001"
height="16"
/>
<rect
x="315.001"
y="46.895"
transform="matrix(0.9238 0.3828 -0.3828 0.9238 50.212 -118.5529)"
width="16"
height="39.999"
/>
<rect x="409.054" y="165.011" transform="matrix(-0.9239 0.3827 -0.3827 -0.9239 891.6585 168.6574)" width="40.001" height="16" />
<rect x="315.001" y="46.895" transform="matrix(0.9238 0.3828 -0.3828 0.9238 50.212 -118.5529)" width="16" height="39.999" />
<path
d="M248,88c-88.224,0-160,71.776-160,160s71.776,160,160,160s160-71.776,160-160S336.224,88,248,88z M248,392
c-79.4,0-144-64.6-144-144s64.6-144,144-144s144,64.6,144,144S327.4,392,248,392z"
/>
<rect x="240" width="16" height="72" />
<rect
x="62.097"
y="90.096"
transform="matrix(0.7071 0.7071 -0.7071 0.7071 98.0963 -40.6334)"
width="71.999"
height="16"
/>
<rect x="62.097" y="90.096" transform="matrix(0.7071 0.7071 -0.7071 0.7071 98.0963 -40.6334)" width="71.999" height="16" />
<rect y="240" width="72" height="16" />
<rect
x="90.091"
y="361.915"
transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 -113.9157 748.643)"
width="16"
height="71.999"
/>
<rect x="90.091" y="361.915" transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 -113.9157 748.643)" width="16" height="71.999" />
<rect x="240" y="424" width="16" height="72" />
<rect
x="361.881"
y="389.915"
transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 397.8562 960.6281)"
width="71.999"
height="16"
/>
<rect x="361.881" y="389.915" transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 397.8562 960.6281)" width="71.999" height="16" />
<rect x="424" y="240" width="72" height="16" />
<rect
x="389.911"
y="62.091"
transform="matrix(0.7071 0.7071 -0.7071 0.7071 185.9067 -252.6357)"
width="16"
height="71.999"
/>
<rect x="389.911" y="62.091" transform="matrix(0.7071 0.7071 -0.7071 0.7071 185.9067 -252.6357)" width="16" height="71.999" />
</svg>
<svg
version="1.1"
@ -137,14 +65,21 @@
</template>
<script setup lang="ts">
const emit = defineEmits(["update:modelValue"]);
interface Emits {
(event: 'update:modelValue', value: boolean): void;
}
const emit = defineEmits<Emits>();
const props = defineProps<{
modelValue: boolean;
}>();
function toggleTheme(event: any) {
emit("update:modelValue", event.target.checked);
function toggleTheme(event: Event) {
const target = event.target as HTMLInputElement | null;
if (target) {
emit('update:modelValue', target.checked);
}
}
</script>

View file

@ -1,64 +1,64 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { defineAsyncComponent } from "vue";
import { mount, flushPromises } from "@vue/test-utils";
import SocialBadge from "./socialButton.vue";
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { mount } from '@vue/test-utils';
import { defineComponent } from 'vue';
import SocialBadge from './SocialButton.vue';
const mockIconComponent = (name:string) => ({
default: {
function mockIconComponent(name: string) {
return {
__esModule: true,
default: defineComponent({
name,
template: `<svg class='${name.toLowerCase()}'></svg>`,
},
});
}),
};
}
vi.mock("../components/icons/IconDiscord.vue", () => mockIconComponent("IconDiscord"));
vi.mock("../components/icons/IconTwitter.vue", () => mockIconComponent("IconTwitter"));
vi.mock("../components/icons/IconTelegram.vue", () => mockIconComponent("IconTelegram"));
vi.mock('@/components/icons/IconDiscord.vue', () => mockIconComponent('IconDiscord'));
vi.mock('@/components/icons/IconTwitter.vue', () => mockIconComponent('IconTwitter'));
vi.mock('@/components/icons/IconTelegram.vue', () => mockIconComponent('IconTelegram'));
describe("SocialBadge.vue", () => {
describe('SocialBadge.vue', () => {
let wrapper;
beforeEach(() => {
wrapper = null;
});
it("renders the correct link", () => {
it('renders the correct link', () => {
wrapper = mount(SocialBadge, {
props: {
href: "https://example.com",
href: 'https://example.com',
},
});
const link = wrapper.find("a");
const link = wrapper.find('a');
expect(link.exists()).toBe(true);
expect(link.attributes("href")).toBe("https://example.com");
expect(link.attributes("target")).toBe("_blank");
expect(link.attributes('href')).toBe('https://example.com');
expect(link.attributes('target')).toBe('_blank');
});
it("applies the correct color for light mode", () => {
it('applies the correct color for light mode', () => {
wrapper = mount(SocialBadge, {
props: {
dark: false,
},
});
const badge = wrapper.find(".social-badge");
expect(badge.attributes("style")).toContain("color: black");
expect(badge.attributes("style")).toContain("border-color: black");
const badge = wrapper.find('.social-badge');
expect(badge.attributes('style')).toContain('color: black');
expect(badge.attributes('style')).toContain('border-color: black');
});
it("applies the correct color for dark mode", () => {
it('applies the correct color for dark mode', () => {
wrapper = mount(SocialBadge, {
props: {
dark: true,
},
});
const badge = wrapper.find(".social-badge");
expect(badge.attributes("style")).toContain("color: white");
expect(badge.attributes("style")).toContain("border-color: white");
const badge = wrapper.find('.social-badge');
expect(badge.attributes('style')).toContain('color: white');
expect(badge.attributes('style')).toContain('border-color: white');
});
// it("renders the correct icon based on the type prop", async () => {
@ -76,20 +76,20 @@ describe("SocialBadge.vue", () => {
// // expect(icon.exists()).toBe(true);
// });
it("does not render an icon if the type is unsupported", async () => {
it('does not render an icon if the type is unsupported', async () => {
wrapper = mount(SocialBadge, {
props: {
type: "unsupported",
type: 'unsupported',
},
});
await wrapper.vm.$nextTick();
const icon = wrapper.find(".social-badge-icon").find("component");
const icon = wrapper.find('.social-badge-icon').find('component');
expect(icon.exists()).toBe(false);
});
it("renders without crashing when no props are provided", () => {
it('renders without crashing when no props are provided', () => {
wrapper = mount(SocialBadge);
expect(wrapper.exists()).toBe(true);

View file

@ -1,64 +0,0 @@
<template>
<a :href="props.href" target="_blank">
<div
class="social-badge"
:style="{ color: color, 'border-color': color }"
:class="{ 'social-badge--dark': props.dark }"
>
<div class="social-badge-icon">
<component :color="color" :is="img" />
</div>
</div>
</a>
</template>
<script setup lang="ts">
import { defineAsyncComponent, computed } from "vue";
interface Props {
type?: string;
dark?: boolean;
href?: string;
}
const props = withDefaults(defineProps<Props>(), {});
const color = computed(() => (props.dark ? "white" : "black"));
const img = computed(() => {
let img;
switch (props.type) {
case "discord":
img = defineAsyncComponent(() => import(`../components/icons/IconDiscord.vue`));
break;
case "twitter":
img = defineAsyncComponent(() => import(`../components/icons/IconTwitter.vue`));
break;
case "telegram":
img = defineAsyncComponent(() => import(`../components/icons/IconTelegram.vue`));
break;
default:
break;
}
return img;
});
</script>
<style lang="sass">
.social-badge
border-radius: 14px
display: flex
border: 1px solid var(--color-social-border)
padding: 6px 20px
align-items: center
flex: 0 1 0
color: black
&:hover,&:active,&:focus
background-color: var(--color-white-hovered)
cursor: pointer
&.social-badge--dark
&:hover, &:active, &:focus
background-color: var(--color-black-hovered)
// font-size: 0
</style>

View file

@ -1,71 +1,94 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { ref, nextTick } from 'vue'
import { useSnatchSelection } from '../useSnatchSelection'
import { usePositions } from '../usePositions'
import { useStake } from '../useStake'
import { useWallet } from '../useWallet'
import { useStatCollection } from '../useStatCollection'
import { useAdjustTaxRate } from '../useAdjustTaxRates'
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { ref, nextTick, type Ref } from 'vue';
import { useSnatchSelection } from '../useSnatchSelection';
import { usePositions, type Position } from '../usePositions';
import { useStake } from '../useStake';
import { useWallet } from '../useWallet';
import { useStatCollection } from '../useStatCollection';
import { useAdjustTaxRate } from '../useAdjustTaxRates';
interface MockPositionsReturn {
activePositions: Ref<Partial<Position>[]>;
}
interface MockStakeReturn {
stakingAmountShares: bigint;
taxRate: number;
}
interface MockWalletReturn {
account: { address: string };
}
interface MockStatCollectionReturn {
stakeTotalSupply?: bigint;
kraikenTotalSupply?: bigint;
outstandingStake?: bigint;
}
interface MockAdjustTaxRateReturn {
taxRates: Array<{ year: number }>;
}
// Mock all composables
vi.mock('../usePositions', () => ({
usePositions: vi.fn()
}))
usePositions: vi.fn(),
}));
vi.mock('../useStake', () => ({
useStake: vi.fn()
}))
useStake: vi.fn(),
}));
vi.mock('../useWallet', () => ({
useWallet: vi.fn()
}))
useWallet: vi.fn(),
}));
vi.mock('../useStatCollection', () => ({
useStatCollection: vi.fn()
}))
useStatCollection: vi.fn(),
}));
vi.mock('../useAdjustTaxRates', () => ({
useAdjustTaxRate: vi.fn()
}))
useAdjustTaxRate: vi.fn(),
}));
vi.mock('kraiken-lib/staking', () => ({
calculateSnatchShortfall: vi.fn((outstandingStake, stakingShares, stakeTotalSupply) => {
return stakingShares > outstandingStake ? 0n : outstandingStake - stakingShares
})
}))
calculateSnatchShortfall: vi.fn((outstandingStake, stakingShares, _stakeTotalSupply) => {
return stakingShares > outstandingStake ? 0n : outstandingStake - stakingShares;
}),
}));
vi.mock('kraiken-lib/snatch', () => ({
selectSnatchPositions: vi.fn((candidates, options) => {
if (candidates.length === 0) {
return { selected: [], remainingShortfall: options.shortfallShares }
return { selected: [], remainingShortfall: options.shortfallShares };
}
return {
selected: candidates,
remainingShortfall: 0n,
maxSelectedTaxRateIndex: candidates[candidates.length - 1].taxRateIndex
}
maxSelectedTaxRateIndex: candidates[candidates.length - 1].taxRateIndex,
};
}),
minimumTaxRate: vi.fn(() => 0.01)
}))
minimumTaxRate: vi.fn(() => 0.01),
}));
describe('useSnatchSelection', () => {
beforeEach(() => {
// Reset all mocks
vi.clearAllMocks()
vi.clearAllMocks();
// Setup default mock values with proper refs
vi.mocked(usePositions).mockReturnValue({
activePositions: ref([])
} as any)
activePositions: ref([]),
} as MockPositionsReturn);
vi.mocked(useStake).mockReturnValue({
stakingAmountShares: 0n,
taxRate: 1.0
} as any)
taxRate: 1.0,
} as MockStakeReturn);
vi.mocked(useWallet).mockReturnValue({
account: { address: '0x123' }
} as any)
account: { address: '0x123' },
} as MockWalletReturn);
// Mock with realistic values for local computation
// stakeTotalSupply is typically 10^(18+7) = 10^25 from contract
@ -73,40 +96,40 @@ describe('useSnatchSelection', () => {
vi.mocked(useStatCollection).mockReturnValue({
stakeTotalSupply: 10000000000000000000000000n, // 10^25
kraikenTotalSupply: 10000000000000000000000000n, // 10^25
outstandingStake: 500n
} as any)
outstandingStake: 500n,
} as MockStatCollectionReturn);
vi.mocked(useAdjustTaxRate).mockReturnValue({
taxRates: [{ year: 1 }]
} as any)
})
taxRates: [{ year: 1 }],
} as MockAdjustTaxRateReturn);
});
it('should initialize with empty snatchable positions', () => {
const { snatchablePositions } = useSnatchSelection()
expect(snatchablePositions.value).toEqual([])
})
const { snatchablePositions } = useSnatchSelection();
expect(snatchablePositions.value).toEqual([]);
});
it('should handle no active positions', () => {
const { snatchablePositions, floorTax } = useSnatchSelection()
expect(snatchablePositions.value).toEqual([])
expect(floorTax.value).toBe(1)
})
const { snatchablePositions, floorTax } = useSnatchSelection();
expect(snatchablePositions.value).toEqual([]);
expect(floorTax.value).toBe(1);
});
it('should handle no shortfall', () => {
vi.mocked(useStatCollection).mockReturnValue({
stakeTotalSupply: 1000n,
outstandingStake: 100n
})
outstandingStake: 100n,
});
vi.mocked(useStake).mockReturnValue({
stakingAmountShares: 900n,
taxRate: 1.0
})
taxRate: 1.0,
});
const { snatchablePositions, openPositionsAvailable } = useSnatchSelection()
expect(snatchablePositions.value).toEqual([])
expect(openPositionsAvailable.value).toBe(true)
})
const { snatchablePositions, openPositionsAvailable } = useSnatchSelection();
expect(snatchablePositions.value).toEqual([]);
expect(openPositionsAvailable.value).toBe(true);
});
it('should filter out positions with higher tax rate', async () => {
vi.mocked(usePositions).mockReturnValue({
@ -117,23 +140,23 @@ describe('useSnatchSelection', () => {
harbDeposit: 100n,
taxRate: 2.0,
taxRateIndex: 1,
iAmOwner: false
}
])
} as any)
iAmOwner: false,
},
]) as Ref<Partial<Position>[]>,
} as MockPositionsReturn);
vi.mocked(useStake).mockReturnValue({
stakingAmountShares: 100n,
taxRate: 1.0
} as any)
taxRate: 1.0,
} as MockStakeReturn);
const { snatchablePositions } = useSnatchSelection()
const { snatchablePositions } = useSnatchSelection();
// Wait for watchEffect to run
await new Promise(resolve => setTimeout(resolve, 0))
await new Promise(resolve => setTimeout(resolve, 0));
expect(snatchablePositions.value).toEqual([])
})
expect(snatchablePositions.value).toEqual([]);
});
it('should filter out owned positions by default', async () => {
vi.mocked(usePositions).mockReturnValue({
@ -144,15 +167,15 @@ describe('useSnatchSelection', () => {
harbDeposit: 100n,
taxRate: 0.5,
taxRateIndex: 1,
iAmOwner: true
}
])
} as any)
iAmOwner: true,
},
]) as Ref<Partial<Position>[]>,
} as MockPositionsReturn);
const { snatchablePositions } = useSnatchSelection()
await new Promise(resolve => setTimeout(resolve, 0))
expect(snatchablePositions.value).toEqual([])
})
const { snatchablePositions } = useSnatchSelection();
await new Promise(resolve => setTimeout(resolve, 0));
expect(snatchablePositions.value).toEqual([]);
});
it('should include owned positions when demo mode is enabled', async () => {
const position = {
@ -161,32 +184,32 @@ describe('useSnatchSelection', () => {
harbDeposit: 100n,
taxRate: 0.005, // 0.5% tax rate (less than maxTaxRate)
taxRateIndex: 1,
iAmOwner: true
}
iAmOwner: true,
};
vi.mocked(usePositions).mockReturnValue({
activePositions: ref([position])
} as any)
activePositions: ref([position]),
} as MockPositionsReturn);
vi.mocked(useStake).mockReturnValue({
stakingAmountShares: 100n,
taxRate: 1.0 // Will be converted to 0.01 (1%) decimal
} as any)
taxRate: 1.0, // Will be converted to 0.01 (1%) decimal
} as MockStakeReturn);
// Need outstandingStake > stakingAmountShares to create shortfall
vi.mocked(useStatCollection).mockReturnValue({
stakeTotalSupply: 10000000000000000000000000n,
kraikenTotalSupply: 10000000000000000000000000n,
outstandingStake: 500n
} as any)
outstandingStake: 500n,
} as MockStatCollectionReturn);
const { snatchablePositions } = useSnatchSelection(true)
const { snatchablePositions } = useSnatchSelection(true);
// Wait for watchEffect to run (no longer async)
await nextTick()
await nextTick();
expect(snatchablePositions.value).toContainEqual(position)
})
expect(snatchablePositions.value).toContainEqual(position);
});
it('should handle partial fills', async () => {
const position1 = {
@ -195,8 +218,8 @@ describe('useSnatchSelection', () => {
harbDeposit: 100n,
taxRate: 0.005, // 0.5% tax rate
taxRateIndex: 1,
iAmOwner: false
}
iAmOwner: false,
};
const position2 = {
positionId: 2n,
@ -204,32 +227,32 @@ describe('useSnatchSelection', () => {
harbDeposit: 200n,
taxRate: 0.006, // 0.6% tax rate
taxRateIndex: 2,
iAmOwner: false
}
iAmOwner: false,
};
vi.mocked(usePositions).mockReturnValue({
activePositions: ref([position1, position2])
} as any)
activePositions: ref([position1, position2]) as Ref<Position[]>,
} as MockPositionsReturn);
vi.mocked(useStake).mockReturnValue({
stakingAmountShares: 150n,
taxRate: 1.0 // Will be converted to 0.01 (1%) decimal
} as any)
taxRate: 1.0, // Will be converted to 0.01 (1%) decimal
} as MockStakeReturn);
// Need outstandingStake > stakingAmountShares to create shortfall
vi.mocked(useStatCollection).mockReturnValue({
stakeTotalSupply: 10000000000000000000000000n,
kraikenTotalSupply: 10000000000000000000000000n,
outstandingStake: 500n
} as any)
outstandingStake: 500n,
} as MockStatCollectionReturn);
const { snatchablePositions } = useSnatchSelection()
const { snatchablePositions } = useSnatchSelection();
// Wait for watchEffect to run
await nextTick()
await nextTick();
expect(snatchablePositions.value).toEqual([position1, position2])
})
expect(snatchablePositions.value).toEqual([position1, position2]);
});
it('should update floor tax based on selected positions', async () => {
const position = {
@ -238,40 +261,36 @@ describe('useSnatchSelection', () => {
harbDeposit: 100n,
taxRate: 0.005, // 0.5% tax rate
taxRateIndex: 1,
iAmOwner: false
}
iAmOwner: false,
};
vi.mocked(usePositions).mockReturnValue({
activePositions: ref([position])
} as any)
activePositions: ref([position]),
} as MockPositionsReturn);
vi.mocked(useStake).mockReturnValue({
stakingAmountShares: 100n,
taxRate: 1.0 // Will be converted to 0.01 (1%) decimal
} as any)
taxRate: 1.0, // Will be converted to 0.01 (1%) decimal
} as MockStakeReturn);
// Need outstandingStake > stakingAmountShares to create shortfall
vi.mocked(useStatCollection).mockReturnValue({
stakeTotalSupply: 10000000000000000000000000n,
kraikenTotalSupply: 10000000000000000000000000n,
outstandingStake: 500n
} as any)
outstandingStake: 500n,
} as MockStatCollectionReturn);
vi.mocked(useAdjustTaxRate).mockReturnValue({
taxRates: [
{ year: 1 },
{ year: 2 },
{ year: 3 }
]
} as any)
taxRates: [{ year: 1 }, { year: 2 }, { year: 3 }],
} as MockAdjustTaxRateReturn);
const { floorTax } = useSnatchSelection()
const { floorTax } = useSnatchSelection();
// Wait for watchEffect to run
await nextTick()
await nextTick();
// Floor tax should be taxRates[maxSelectedTaxRateIndex + 1]
// Position has taxRateIndex: 1, so nextIndex = 2, taxRates[2] = { year: 3 }
expect(floorTax.value).toBe(3)
})
})
expect(floorTax.value).toBe(3);
});
});

View file

@ -1,17 +1,17 @@
import { ref, onMounted, onUnmounted, reactive, computed, type ComputedRef } from "vue";
import * as StakeContract from "@/contracts/stake";
import { waitForTransactionReceipt } from "@wagmi/core";
import { config } from "@/wagmi";
import { compactNumber, formatBigIntDivision } from "@/utils/helper";
import { useContractToast } from "./useContractToast";
import { TAX_RATE_OPTIONS } from "kraiken-lib/taxRates";
import { ref, reactive, computed, type ComputedRef } from 'vue';
import * as StakeContract from '@/contracts/stake';
import { waitForTransactionReceipt, type Config } from '@wagmi/core';
import { config } from '@/wagmi';
// import { compactNumber, formatBigIntDivision } from "@/utils/helper";
import { useContractToast } from './useContractToast';
import { TAX_RATE_OPTIONS } from 'kraiken-lib/taxRates';
const contractToast = useContractToast();
enum State {
SignTransaction = "SignTransaction",
Waiting = "Waiting",
Action = "Action",
SignTransaction = 'SignTransaction',
Waiting = 'Waiting',
Action = 'Action',
}
export const taxRates = TAX_RATE_OPTIONS.map(({ index, year, daily }) => ({
@ -24,7 +24,6 @@ export function useAdjustTaxRate() {
const loading = ref();
const waiting = ref();
const state: ComputedRef<State> = computed(() => {
if (loading.value) {
return State.SignTransaction;
@ -37,31 +36,25 @@ export function useAdjustTaxRate() {
async function changeTax(positionId: bigint, taxRate: number) {
try {
console.log("changeTax", { positionId, taxRate });
// console.log("changeTax", { positionId, taxRate });
loading.value = true;
const index = taxRates.findIndex((obj) => obj.year === taxRate)
const index = taxRates.findIndex(obj => obj.year === taxRate);
const hash = await StakeContract.changeTax(positionId, index);
console.log("hash", hash);
// console.log("hash", hash);
loading.value = false;
waiting.value = true;
const data = await waitForTransactionReceipt(config as any, {
await waitForTransactionReceipt(config as Config, {
hash: hash,
});
contractToast.showSuccessToast(
taxRate.toString(),
"Success!",
"You adjusted your position tax to",
"",
"%"
);
contractToast.showSuccessToast(taxRate.toString(), 'Success!', 'You adjusted your position tax to', '', '%');
waiting.value = false;
} catch (error: any) {
console.error("error", error);
console.log(JSON.stringify(error, (_, v) => (typeof v === "bigint" ? v.toString() : v)));
} catch (error: unknown) {
// console.error("error", error);
// console.log(JSON.stringify(error, (_, v) => (typeof v === "bigint" ? v.toString() : v)));
contractToast.showFailToast(error.shortMessage);
contractToast.showFailToast((error as { shortMessage?: string })?.shortMessage ?? 'Transaction failed');
} finally {
loading.value = false;
waiting.value = false;

View file

@ -1,27 +1,22 @@
import { ref, reactive, computed } from "vue";
import { getChainId, watchChainId, getAccount, watchAccount } from "@wagmi/core";
import { config } from "@/wagmi";
import { setHarbContract } from "@/contracts/harb";
import { setStakeContract } from "@/contracts/stake";
import {chainsData} from "@/config"
import logger from "@/utils/logger";
const activeChain = ref()
let unwatch: any = null;
import { ref, reactive, computed } from 'vue';
// import { getChainId, watchChainId, getAccount, watchAccount } from "@wagmi/core";
// import { config } from "@/wagmi";
// import { setHarbContract } from "@/contracts/harb";
// import { setStakeContract } from "@/contracts/stake";
import { chainsData } from '@/config';
// import logger from "@/utils/logger";
const activeChain = ref();
export const chainData = computed(() => {
return chainsData.find((obj) => obj.id === activeChain.value)
})
return chainsData.find(obj => obj.id === activeChain.value);
});
export function useChain() {
// if (!unwatch) {
// console.log("useChain function");
// const chain = getChainId(config as any)
// const chain = getChainId(config as Config)
// activeChain.value = chain
// unwatch = watchChainId(config as any, {
// unwatch = watchChainId(config as Config, {
// async onChange(chainId) {
// console.log("Chain changed", chainId);
// activeChain.value = chainId
@ -34,7 +29,7 @@ export function useChain() {
// if (!unwatch) {
// console.log("useWallet function");
// unwatch = watchAccount(config as any, {
// unwatch = watchAccount(config as Config, {
// async onChange(data) {
// console.log("watchaccount-useChain", data);
@ -46,6 +41,5 @@ export function useChain() {
// });
// }
return reactive({ chainData });
}

View file

@ -1,15 +1,13 @@
import { ref, onMounted, onUnmounted, reactive, computed } from "vue";
import { type ComputedRef } from "vue";
import { config } from "@/wagmi";
import { AbiEncodingArrayLengthMismatchError, type WatchEventReturnType } from "viem";
import axios from "axios";
import { getAccount, watchContractEvent, readContract, waitForTransactionReceipt, watchAccount } from "@wagmi/core";
import { type WatchAccountReturnType } from "@wagmi/core";
import * as HarbContract from "@/contracts/harb";
import { ref, reactive, computed } from 'vue';
import { type ComputedRef } from 'vue';
import { config } from '@/wagmi';
// import axios from "axios";
import { waitForTransactionReceipt, watchAccount, type Config } from '@wagmi/core';
import { type WatchAccountReturnType } from '@wagmi/core';
import * as HarbContract from '@/contracts/harb';
// import HarbJson from "@/assets/contracts/harb.json";
import { type Abi, type Address } from "viem";
import { useWallet } from "./useWallet";
import { compactNumber, formatBigIntDivision } from "@/utils/helper";
import { useWallet } from './useWallet';
// import { compactNumber, formatBigIntDivision } from "@/utils/helper";
const wallet = useWallet();
@ -19,15 +17,14 @@ const ubiDue = HarbContract.ubiDue;
let unwatch: WatchAccountReturnType;
enum ClaimState {
NothingToClaim = "NothingToClaim",
StakeAble = "StakeAble",
SignTransaction = "SignTransaction",
Waiting = "Waiting",
NotEnoughApproval = "NotEnoughApproval",
NothingToClaim = 'NothingToClaim',
StakeAble = 'StakeAble',
SignTransaction = 'SignTransaction',
Waiting = 'Waiting',
NotEnoughApproval = 'NotEnoughApproval',
}
export function useClaim() {
const state: ComputedRef<ClaimState> = computed(() => {
if (loading.value) {
return ClaimState.SignTransaction;
@ -46,34 +43,29 @@ export function useClaim() {
loading.value = true;
const hash = await HarbContract.claimUbi(address);
console.log("hash", hash);
// console.log("hash", hash);
loading.value = false;
waiting.value = true;
const data = await waitForTransactionReceipt(config as any, {
await waitForTransactionReceipt(config as Config, {
hash: hash,
});
console.log("data.logs", data.logs);
} catch (error) {
console.error("error", error);
} catch (_error) {
// console.error("error", error);
} finally {
loading.value = false;
waiting.value = false;
}
}
onMounted(async () => {});
if (!unwatch) {
console.log("useClaim function");
// console.log("useClaim function");
unwatch = watchAccount(config as any, {
unwatch = watchAccount(config as Config, {
async onChange(data) {
console.log("watchAccount", data);
// console.log("watchAccount", data);
if (data.address) {
await HarbContract.setHarbContract();
}
},
});

View file

@ -1,20 +1,21 @@
import {onBeforeUnmount, onMounted} from 'vue'
import { onBeforeUnmount, onMounted, type Ref } from 'vue';
export default function useClickOutside(component: any, callback: any) {
if (!component) return
const listener = (event: any) => {
if (event.target !== component.value && event.composedPath().includes(component.value)) {
return
export default function useClickOutside(component: Ref<HTMLElement | null>, callback: () => void) {
if (!component) return;
const listener = (event: MouseEvent) => {
if (event.target !== component.value && event.composedPath().includes(component.value as EventTarget)) {
return;
}
if (typeof callback === 'function') {
callback()
callback();
}
}
onMounted(() => { window.addEventListener('click', listener) })
onBeforeUnmount(() => {window.removeEventListener('click', listener)})
};
onMounted(() => {
window.addEventListener('click', listener);
});
onBeforeUnmount(() => {
window.removeEventListener('click', listener);
});
return {listener}
return { listener };
}

View file

@ -1,37 +1,30 @@
import Toast from "@/components/Toast.vue";
import { POSITION, useToast } from "vue-toastification";
import { reactive } from "vue";
import ToastNotification from '@/components/ToastNotification.vue';
import { POSITION, useToast } from 'vue-toastification';
import { reactive } from 'vue';
const toast = useToast();
export function useContractToast() {
function showFailToast(name?: string) {
console.log("name", name);
if (name === "UserRejectedRequestError") {
// console.log("name", name);
if (name === 'UserRejectedRequestError') {
//
} else {
showSuccessToast(
"",
"Failed!",
"Your transaction didnt go through. Please try again.",
"If the issue persists, please send a message in #helpdesk on our Discord.",
"",
"error"
'',
'Failed!',
'Your transaction didnt go through. Please try again.',
'If the issue persists, please send a message in #helpdesk on our Discord.',
'',
'error'
);
}
}
function showSuccessToast(
value: string,
header: string,
subheader: string,
info: string,
unit: string,
type: string = "info"
) {
function showSuccessToast(value: string, header: string, subheader: string, info: string, unit: string, type: string = 'info') {
// Define the content object with the component, props and listeners
const content = {
component: Toast,
component: ToastNotification,
// Any prop can be passed, but don't expect them to be reactive
props: {
value: value,
@ -51,7 +44,7 @@ export function useContractToast() {
position: POSITION.TOP_RIGHT,
icon: false,
closeOnClick: false,
toastClassName: "modal-overlay",
toastClassName: 'modal-overlay',
closeButton: false,
hideProgressBar: true,
timeout: 10000,

View file

@ -1,38 +1,35 @@
import { ref, onMounted, watch } from 'vue'
import { ref, onMounted, watch } from 'vue';
// by convention, composable function names start with "use"
const darkTheme = ref(false)
const darkTheme = ref(false);
export function useDark() {
onMounted(() => {
if(localStorage.getItem("theme") === "dark") {
document.documentElement.classList.add("dark")
darkTheme.value = true
if (localStorage.getItem('theme') === 'dark') {
document.documentElement.classList.add('dark');
darkTheme.value = true;
} else {
darkTheme.value = false
darkTheme.value = false;
}
})
});
watch(
() => darkTheme.value,
(newData) => {
document.documentElement.removeAttribute("data-theme");
localStorage.removeItem("theme")
newData => {
document.documentElement.removeAttribute('data-theme');
localStorage.removeItem('theme');
if (newData) {
// import('@/assets/sass/elementplus-dark.scss');
document.documentElement.classList.add("dark")
document.documentElement.setAttribute("data-theme", "dark");
localStorage.setItem("theme", "dark");
document.documentElement.classList.add('dark');
document.documentElement.setAttribute('data-theme', 'dark');
localStorage.setItem('theme', 'dark');
} else {
// import('@/assets/sass/elementplus-light.scss');
document.documentElement.classList.remove("dark")
document.documentElement.setAttribute("data-theme", "default");
localStorage.setItem("theme", "default");
document.documentElement.classList.remove('dark');
document.documentElement.setAttribute('data-theme', 'default');
localStorage.setItem('theme', 'default');
}
}
);
return { darkTheme }
return { darkTheme };
}

View file

@ -1,4 +1,4 @@
import { ref, onMounted, onUnmounted } from "vue";
import { ref, onMounted, onUnmounted } from 'vue';
// by convention, composable function names start with "use"
export function useMobile() {
@ -18,12 +18,12 @@ export function useMobile() {
}
onMounted(async () => {
window.addEventListener("resize", handleWindowSizeChange);
window.addEventListener('resize', handleWindowSizeChange);
handleWindowSizeChange();
});
onUnmounted(() => {
window.removeEventListener("resize", handleWindowSizeChange);
window.removeEventListener('resize', handleWindowSizeChange);
});
return isMobile;

View file

@ -1,17 +1,16 @@
import { ref, onMounted, onUnmounted, reactive, computed, type ComputedRef } from "vue";
import { config } from "@/wagmi";
import { type WatchEventReturnType, toBytes, type Hex } from "viem";
import axios from "axios";
import { getAccount, watchContractEvent, watchChainId, watchAccount } from "@wagmi/core";
import type { WatchChainIdReturnType, WatchAccountReturnType, GetAccountReturnType} from "@wagmi/core";
import { ref, computed, type ComputedRef, onMounted, onUnmounted } from 'vue';
import { config } from '@/wagmi';
import { type WatchEventReturnType, type Hex, toBytes } from 'viem';
import axios from 'axios';
import { getAccount, watchContractEvent, watchChainId, watchAccount, type Config } from '@wagmi/core';
import type { WatchChainIdReturnType, WatchAccountReturnType, GetAccountReturnType } from '@wagmi/core';
import { HarbContract } from "@/contracts/harb";
import { bytesToUint256, uint256ToBytes } from "kraiken-lib";
import { bigInt2Number } from "@/utils/helper";
import { useChain } from "@/composables/useChain";
import { taxRates } from "@/composables/useAdjustTaxRates";
import { chainData } from "@/composables/useWallet";
import logger from "@/utils/logger";
import { HarbContract } from '@/contracts/harb';
import { bytesToUint256 } from 'kraiken-lib';
import { bigInt2Number } from '@/utils/helper';
import { taxRates } from '@/composables/useAdjustTaxRates';
import { chainData } from '@/composables/useWallet';
import logger from '@/utils/logger';
const rawActivePositions = ref<Array<Position>>([]);
const rawClosedPositoins = ref<Array<Position>>([]);
const loading = ref(false);
@ -21,19 +20,18 @@ 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);
const account = getAccount(config as Config);
return rawActivePositions.value
.map((obj: any) => {
.map(obj => {
return {
...obj,
positionId: formatId(obj.id),
positionId: formatId(obj.id as Hex),
amount: bigInt2Number(obj.harbDeposit, 18),
taxRatePercentage: Number(obj.taxRate) * 100,
taxRate: Number(obj.taxRate),
taxRateIndex: taxRates.find((taxRate) => taxRate.year === Number(obj.taxRate) * 100)?.index,
taxRateIndex: taxRates.find(taxRate => taxRate.year === Number(obj.taxRate) * 100)?.index,
iAmOwner: obj.owner?.toLowerCase() === account.address?.toLowerCase(),
totalSupplyEnd: obj.totalSupplyEnd ? BigInt(obj.totalSupplyEnd) : undefined,
totalSupplyInit: BigInt(obj.totalSupplyInit),
@ -60,7 +58,7 @@ export interface Position {
lastTaxTime: Date;
taxPaid: bigint;
taxRate: number;
taxRateIndex: number;
taxRateIndex?: number;
taxRatePercentage: number;
share: number;
status: string;
@ -72,24 +70,23 @@ export interface Position {
}
const myClosedPositions: ComputedRef<Position[]> = computed(() => {
const account = getAccount(config as any);
const account = getAccount(config as Config);
return rawClosedPositoins.value.map((obj: any) => {
const taxRatePosition = obj.taxRate * 100;
console.log("taxRatePosition", taxRatePosition);
return rawClosedPositoins.value.map(obj => {
// console.log("taxRatePosition", taxRatePosition);
console.log("taxRates[taxRatePosition]", taxRates[taxRatePosition]);
// console.log("taxRates[taxRatePosition]", taxRates[taxRatePosition]);
return {
...obj,
positionId: formatId(obj.id),
positionId: formatId(obj.id as Hex),
amount: obj.share * 1000000,
// amount: bigInt2Number(obj.harbDeposit, 18),
taxRatePercentage: Number(obj.taxRate) * 100,
taxRate: Number(obj.taxRate),
taxRateIndex: taxRates.find((taxRate) => taxRate.year === Number(obj.taxRate) * 100)?.index,
taxRateIndex: taxRates.find(taxRate => taxRate.year === Number(obj.taxRate) * 100)?.index,
iAmOwner: obj.owner?.toLowerCase() === account.address?.toLowerCase(),
totalSupplyEnd: BigInt(obj.totalSupplyEnd),
totalSupplyEnd: obj.totalSupplyEnd !== undefined ? BigInt(obj.totalSupplyEnd) : undefined,
totalSupplyInit: BigInt(obj.totalSupplyInit),
taxPaid: BigInt(obj.taxPaid),
share: Number(obj.share),
@ -104,8 +101,8 @@ const myActivePositions: ComputedRef<Position[]> = computed(() =>
);
const tresholdValue = computed(() => {
const arrayTaxRatePositions = activePositions.value.map((obj) => obj.taxRatePercentage);
const sortedPositions = arrayTaxRatePositions.sort((a: any, b: any) => (a > b ? 1 : -1));
const arrayTaxRatePositions = activePositions.value.map(obj => obj.taxRatePercentage);
const sortedPositions = arrayTaxRatePositions.sort((a, b) => (a > b ? 1 : -1));
const sumq = sortedPositions.reduce((partialSum, a) => partialSum + a, 0);
const avg = sumq / sortedPositions.length;
@ -116,12 +113,14 @@ export async function loadActivePositions(endpoint?: string) {
logger.info(`loadActivePositions for chain: ${chainData.value?.path}`);
const targetEndpoint = endpoint ?? chainData.value?.graphql?.trim();
if (!targetEndpoint) {
throw new Error("GraphQL endpoint not configured for this chain.");
throw new Error('GraphQL endpoint not configured for this chain.');
}
console.log("chainData.value?.graphql", targetEndpoint);
// console.log("chainData.value?.graphql", targetEndpoint);
const res = await axios.post(targetEndpoint, {
const res = await axios.post(
targetEndpoint,
{
query: `query ActivePositions {
positionss(where: { status: "Active" }, orderBy: "taxRate", orderDirection: "asc", limit: 1000) {
items {
@ -140,17 +139,19 @@ export async function loadActivePositions(endpoint?: string) {
}
}
}`,
}, { timeout: GRAPHQL_TIMEOUT_MS });
},
{ 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(", "));
throw new Error(errors.map((err: unknown) => (err as { message?: string })?.message ?? 'GraphQL error').join(', '));
}
const items = res.data?.data?.positionss?.items ?? [];
return items.map((item: any) => ({
return items.map((item: Record<string, unknown>) => ({
...item,
harbDeposit: item.kraikenDeposit ?? "0",
harbDeposit: item.kraikenDeposit ?? '0',
})) as Position[];
}
@ -164,9 +165,11 @@ export async function loadMyClosedPositions(endpoint: string | undefined, accoun
logger.info(`loadMyClosedPositions for chain: ${chainData.value?.path}`);
const targetEndpoint = endpoint ?? chainData.value?.graphql?.trim();
if (!targetEndpoint) {
throw new Error("GraphQL endpoint not configured for this chain.");
throw new Error('GraphQL endpoint not configured for this chain.');
}
const res = await axios.post(targetEndpoint, {
const res = await axios.post(
targetEndpoint,
{
query: `query ClosedPositions {
positionss(where: { status: "Closed", owner: "${account.address?.toLowerCase()}" }, limit: 1000) {
items {
@ -185,15 +188,17 @@ export async function loadMyClosedPositions(endpoint: string | undefined, accoun
}
}
}`,
}, { timeout: GRAPHQL_TIMEOUT_MS });
},
{ 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(", "));
throw new Error(errors.map((err: unknown) => (err as { message?: string })?.message ?? 'GraphQL error').join(', '));
}
const items = res.data?.data?.positionss?.items ?? [];
return items.map((item: any) => ({
return items.map((item: Record<string, unknown>) => ({
...item,
harbDeposit: item.kraikenDeposit ?? "0",
harbDeposit: item.kraikenDeposit ?? '0',
})) as Position[];
}
@ -204,7 +209,7 @@ export async function loadPositions() {
if (!endpoint) {
rawActivePositions.value = [];
rawClosedPositoins.value = [];
positionsError.value = "GraphQL endpoint not configured for this chain.";
positionsError.value = 'GraphQL endpoint not configured for this chain.';
clearPositionsRetryTimer();
positionsRetryDelayMs.value = POSITIONS_RETRY_BASE_DELAY;
loading.value = false;
@ -213,7 +218,7 @@ export async function loadPositions() {
try {
rawActivePositions.value = await loadActivePositions(endpoint);
const account = getAccount(config as any);
const account = getAccount(config as Config);
if (account.address) {
rawClosedPositoins.value = await loadMyClosedPositions(endpoint, account);
} else {
@ -223,7 +228,6 @@ export async function loadPositions() {
positionsRetryDelayMs.value = POSITIONS_RETRY_BASE_DELAY;
clearPositionsRetryTimer();
} catch (error) {
console.warn("[positions] loadPositions() failed", error);
rawActivePositions.value = [];
rawClosedPositoins.value = [];
positionsError.value = formatGraphqlError(error);
@ -240,9 +244,9 @@ let unwatchAccountChanged: WatchAccountReturnType | null;
function formatGraphqlError(error: unknown): string {
if (axios.isAxiosError(error)) {
const responseErrors = (error.response?.data as any)?.errors;
const responseErrors = (error.response?.data as { errors?: unknown[] })?.errors;
if (Array.isArray(responseErrors) && responseErrors.length > 0) {
return responseErrors.map((err: any) => err?.message ?? "GraphQL error").join(", ");
return responseErrors.map((err: unknown) => (err as { message?: string })?.message ?? 'GraphQL error').join(', ');
}
if (error.response?.status) {
return `GraphQL request failed with status ${error.response.status}`;
@ -254,7 +258,7 @@ function formatGraphqlError(error: unknown): string {
if (error instanceof Error && error.message) {
return error.message;
}
return "Unknown GraphQL error";
return 'Unknown GraphQL error';
}
function clearPositionsRetryTimer() {
@ -265,7 +269,7 @@ function clearPositionsRetryTimer() {
}
function schedulePositionsRetry() {
if (typeof window === "undefined") {
if (typeof window === 'undefined') {
return;
}
if (positionsRetryTimer !== null) {
@ -280,12 +284,12 @@ function schedulePositionsRetry() {
}
export function usePositions() {
function watchEvent() {
unwatch = watchContractEvent(config as any, {
unwatch = watchContractEvent(config as Config, {
address: HarbContract.contractAddress,
abi: HarbContract.abi,
eventName: "PositionCreated",
async onLogs(logs) {
console.log("new Position", logs);
eventName: 'PositionCreated',
async onLogs(_logs) {
// console.log("new Position", logs);
await loadPositions();
// await getMinStake();
},
@ -293,12 +297,12 @@ export function usePositions() {
}
function watchPositionRemoved() {
unwatchPositionRemovedEvent = watchContractEvent(config as any, {
unwatchPositionRemovedEvent = watchContractEvent(config as Config, {
address: HarbContract.contractAddress,
abi: HarbContract.abi,
eventName: "PositionRemoved",
async onLogs(logs) {
console.log("Position removed", logs);
eventName: 'PositionRemoved',
async onLogs(_logs) {
// console.log("Position removed", logs);
await loadPositions();
// await getMinStake();
},
@ -321,15 +325,15 @@ export function usePositions() {
}
if (!unwatchChainSwitch) {
unwatchChainSwitch = watchChainId(config as any, {
async onChange(chainId) {
unwatchChainSwitch = watchChainId(config as Config, {
async onChange(_chainId) {
await loadPositions();
},
});
}
if (!unwatchAccountChanged) {
unwatchAccountChanged = watchAccount(config as any, {
unwatchAccountChanged = watchAccount(config as Config, {
async onChange() {
await loadPositions();
},
@ -361,16 +365,16 @@ export function usePositions() {
for (let index = 0; index < amount; index++) {
const newPosition: Position = {
creationTime: new Date(),
id: "123",
id: '123',
positionId: 123n,
owner: "bla",
owner: 'bla',
lastTaxTime: new Date(),
taxPaid: 100n,
taxRate: randomInRange(0.01, 1),
taxRateIndex: randomInRange(1, 30),
taxRateIndex: Math.floor(randomInRange(1, 30)),
taxRatePercentage: getRandomInt(1, 100),
share: getRandomInt(0.001, 0.09),
status: "active",
status: 'active',
totalSupplyEnd: undefined,
totalSupplyInit: 1000000000000n,
amount: 150,

View file

@ -1,15 +1,11 @@
import { ref, watchEffect, computed } from 'vue'
import { usePositions, type Position } from './usePositions'
import { useStake } from './useStake'
import { useWallet } from './useWallet'
import { useStatCollection } from './useStatCollection'
import { useAdjustTaxRate } from './useAdjustTaxRates'
import { calculateSnatchShortfall } from 'kraiken-lib/staking'
import {
selectSnatchPositions,
minimumTaxRate,
type SnatchablePosition,
} from 'kraiken-lib/snatch'
import { ref, watchEffect, computed, type Ref } from 'vue';
import { usePositions, type Position } from './usePositions';
import { useStake } from './useStake';
import { useWallet } from './useWallet';
import { useStatCollection } from './useStatCollection';
import { useAdjustTaxRate } from './useAdjustTaxRates';
import { calculateSnatchShortfall } from 'kraiken-lib/staking';
import { selectSnatchPositions, minimumTaxRate, type SnatchablePosition } from 'kraiken-lib/snatch';
/**
* Converts Kraiken token assets to shares using the same formula as Stake.sol:
@ -20,113 +16,100 @@ import {
* @param stakeTotalSupply - Total supply of stake shares (constant from contract)
* @returns Number of shares corresponding to the assets
*/
function assetsToSharesLocal(
assets: bigint,
kraikenTotalSupply: bigint,
stakeTotalSupply: bigint
): bigint {
function assetsToSharesLocal(assets: bigint, kraikenTotalSupply: bigint, stakeTotalSupply: bigint): bigint {
if (kraikenTotalSupply === 0n) {
return 0n
return 0n;
}
// Equivalent to: assets.mulDiv(stakeTotalSupply, kraikenTotalSupply, Math.Rounding.Down)
return (assets * stakeTotalSupply) / kraikenTotalSupply
return (assets * stakeTotalSupply) / kraikenTotalSupply;
}
export function useSnatchSelection(demo = false, taxRate?: Ref<number>) {
const { activePositions } = usePositions();
const stake = useStake();
const wallet = useWallet();
const statCollection = useStatCollection();
const adjustTaxRate = useAdjustTaxRate();
export function useSnatchSelection(demo = false) {
const { activePositions } = usePositions()
const stake = useStake()
const wallet = useWallet()
const statCollection = useStatCollection()
const adjustTaxRate = useAdjustTaxRate()
const snatchablePositions = ref<Position[]>([]);
const shortfallShares = ref<bigint>(0n);
const floorTax = ref(1);
let selectionRun = 0;
const snatchablePositions = ref<Position[]>([])
const shortfallShares = ref<bigint>(0n)
const floorTax = ref(1)
let selectionRun = 0
const openPositionsAvailable = computed(() => shortfallShares.value <= 0n)
const openPositionsAvailable = computed(() => shortfallShares.value <= 0n);
function getMinFloorTax() {
const minRate = minimumTaxRate(activePositions.value, 0)
return Math.round(minRate * 100)
const minRate = minimumTaxRate(activePositions.value, 0);
return Math.round(minRate * 100);
}
watchEffect((onCleanup) => {
const runId = ++selectionRun
let cancelled = false
watchEffect(onCleanup => {
const runId = ++selectionRun;
let cancelled = false;
onCleanup(() => {
cancelled = true
})
cancelled = true;
});
// No longer async since we compute shares locally
const compute = () => {
if (statCollection.stakeTotalSupply === 0n) {
shortfallShares.value = 0n
shortfallShares.value = 0n;
if (!cancelled && runId === selectionRun) {
snatchablePositions.value = []
floorTax.value = getMinFloorTax()
snatchablePositions.value = [];
floorTax.value = getMinFloorTax();
}
return
return;
}
const stakingShares = stake.stakingAmountShares ?? 0n
const shortfall = calculateSnatchShortfall(
statCollection.outstandingStake,
stakingShares,
statCollection.stakeTotalSupply,
2n,
10n
)
const stakingShares = stake.stakingAmountShares ?? 0n;
const shortfall = calculateSnatchShortfall(statCollection.outstandingStake, stakingShares, statCollection.stakeTotalSupply, 2n, 10n);
shortfallShares.value = shortfall
shortfallShares.value = shortfall;
if (shortfall <= 0n) {
if (!cancelled && runId === selectionRun) {
snatchablePositions.value = []
floorTax.value = getMinFloorTax()
snatchablePositions.value = [];
floorTax.value = getMinFloorTax();
}
return
return;
}
const maxTaxRateDecimal = (stake.taxRate ?? 0) / 100
const includeOwned = demo
const recipient = wallet.account.address ?? null
const stakeTaxRate = (stake as { taxRate?: number }).taxRate;
const taxRatePercent = taxRate?.value ?? stakeTaxRate ?? Number.POSITIVE_INFINITY;
const maxTaxRateDecimal = Number.isFinite(taxRatePercent) ? taxRatePercent / 100 : Number.POSITIVE_INFINITY;
const includeOwned = demo;
const recipient = wallet.account.address ?? null;
const eligiblePositions = activePositions.value.filter((position: Position) => {
if (position.taxRate >= maxTaxRateDecimal) {
return false
return false;
}
if (!includeOwned && position.iAmOwner) {
return false
return false;
}
return true
})
return true;
});
if (eligiblePositions.length === 0) {
if (!cancelled && runId === selectionRun) {
snatchablePositions.value = []
floorTax.value = getMinFloorTax()
snatchablePositions.value = [];
floorTax.value = getMinFloorTax();
}
return
return;
}
const candidates: SnatchablePosition[] = []
const candidates: SnatchablePosition[] = [];
// Compute shares locally using the same formula as Stake.sol
for (const position of eligiblePositions) {
const shares = assetsToSharesLocal(
position.harbDeposit,
statCollection.kraikenTotalSupply,
statCollection.stakeTotalSupply
)
const shares = assetsToSharesLocal(position.harbDeposit, statCollection.kraikenTotalSupply, statCollection.stakeTotalSupply);
candidates.push({
id: position.positionId,
owner: position.owner,
stakeShares: shares,
taxRate: position.taxRate,
taxRateIndex: position.taxRateIndex,
})
});
}
const selection = selectSnatchPositions(candidates, {
@ -134,47 +117,45 @@ export function useSnatchSelection(demo = false) {
maxTaxRate: maxTaxRateDecimal,
includeOwned,
recipientAddress: recipient,
})
});
if (cancelled || runId !== selectionRun) {
return
return;
}
if (selection.remainingShortfall > 0n) {
snatchablePositions.value = []
floorTax.value = getMinFloorTax()
return
snatchablePositions.value = [];
floorTax.value = getMinFloorTax();
return;
}
const positionById = new Map<bigint, Position>()
const positionById = new Map<bigint, Position>();
for (const position of activePositions.value) {
positionById.set(position.positionId, position)
positionById.set(position.positionId, position);
}
const selectedPositions = selection.selected
.map((candidate) => positionById.get(candidate.id))
.filter((value): value is Position => Boolean(value))
.map(candidate => positionById.get(candidate.id))
.filter((value): value is Position => Boolean(value));
snatchablePositions.value = selectedPositions
snatchablePositions.value = selectedPositions;
if (selection.maxSelectedTaxRateIndex !== undefined) {
const nextIndex = selection.maxSelectedTaxRateIndex + 1
const option =
adjustTaxRate.taxRates[nextIndex] ??
adjustTaxRate.taxRates[selection.maxSelectedTaxRateIndex]
floorTax.value = option ? option.year : getMinFloorTax()
const nextIndex = selection.maxSelectedTaxRateIndex + 1;
const option = adjustTaxRate.taxRates[nextIndex] ?? adjustTaxRate.taxRates[selection.maxSelectedTaxRateIndex];
floorTax.value = option ? option.year : getMinFloorTax();
} else {
floorTax.value = getMinFloorTax()
}
floorTax.value = getMinFloorTax();
}
};
compute()
})
compute();
});
return {
snatchablePositions,
shortfallShares,
floorTax,
openPositionsAvailable,
}
};
}

View file

@ -1,42 +1,26 @@
import { ref, onMounted, onUnmounted, reactive, computed } from "vue";
import { type ComputedRef } from "vue";
import { config } from "@/wagmi";
import { AbiEncodingArrayLengthMismatchError, type WatchEventReturnType, decodeEventLog, type Hex } from "viem";
import axios from "axios";
import {
getAccount,
watchContractEvent,
readContract,
signTypedData,
waitForTransactionReceipt,
watchAccount,
verifyTypedData,
} from "@wagmi/core";
import { HarbContract } from "@/contracts/harb";
import { type Abi, type Address } from "viem";
import { StakeContract, minStake, snatchService, permitAndSnatch, assetsToShares } from "@/contracts/stake";
import { getNonce, nonce, getName } from "@/contracts/harb";
import { useWallet } from "@/composables/useWallet";
import { createPermitObject, getSignatureRSV } from "@/utils/blockchain";
import { formatBigIntDivision, compactNumber } from "@/utils/helper";
import { useToast } from "vue-toastification";
import { taxRates } from "@/composables/useAdjustTaxRates";
import { useContractToast } from "./useContractToast";
import { ref, onMounted, reactive, computed } from 'vue';
import { type ComputedRef } from 'vue';
import { config } from '@/wagmi';
import { decodeEventLog, type Hex, type DecodeEventLogReturnType, type TypedDataDefinition } from 'viem';
import { getAccount, signTypedData, waitForTransactionReceipt, type Config } from '@wagmi/core';
import { HarbContract } from '@/contracts/harb';
import { StakeContract, permitAndSnatch, minStake } from '@/contracts/stake';
import { getNonce, nonce, getName } from '@/contracts/harb';
import { useWallet } from '@/composables/useWallet';
import { createPermitObject, getSignatureRSV } from '@/utils/blockchain';
import { formatBigIntDivision, compactNumber } from '@/utils/helper';
import { taxRates } from '@/composables/useAdjustTaxRates';
import { useContractToast } from './useContractToast';
const wallet = useWallet();
const toast = useToast();
const contractToast = useContractToast();
const wagmiConfig: Config = config;
enum StakeState {
NoBalance = "NoBalance",
StakeAble = "StakeAble",
SignTransaction = "SignTransaction",
Waiting = "Waiting",
NotEnoughApproval = "NotEnoughApproval",
}
interface PositionCreatedEvent {
eventName: undefined;
args: PositionCreatedArgs;
NoBalance = 'NoBalance',
StakeAble = 'StakeAble',
SignTransaction = 'SignTransaction',
Waiting = 'Waiting',
NotEnoughApproval = 'NotEnoughApproval',
}
interface PositionCreatedArgs {
@ -60,9 +44,8 @@ export function useStake() {
const state: ComputedRef<StakeState> = computed(() => {
const balance = wallet.balance.value;
console.log("balance123", balance);
console.log("wallet", wallet);
// console.log("balance123", balance);
// console.log("wallet", wallet);
if (loading.value) {
return StakeState.SignTransaction;
@ -97,18 +80,16 @@ export function useStake() {
},
});
// const stakingAmountNumber = computed(() => return staking)
async function snatch(stakingAmount: BigInt, taxRate: number, positions:Array<any> = []) {
console.log("snatch", { stakingAmount, taxRate, positions });
const account = getAccount(config as any);
const taxRateObj = taxRates.find((obj) => obj.year === taxRate);
async function snatch(stakingAmount: bigint, taxRate: number, positions: Array<bigint> = []) {
// console.log("snatch", { stakingAmount, taxRate, positions });
const account = getAccount(wagmiConfig);
const taxRateObj = taxRates.find(obj => obj.year === taxRate);
try {
const assets: BigInt = stakingAmount;
const receiver = wallet.account.address!;
console.log("receiver", receiver);
const assets: bigint = stakingAmount;
// console.log("receiver", receiver);
// await snatchService(assets, receiver, taxRate, []);
// assets: BigInt, receiver: Address, taxRate: Number, positionsToSnatch: Array<BigInt>
@ -126,58 +107,55 @@ export function useStake() {
account.chainId!,
name
);
console.log("resultPermitObject", { types, message, domain, primaryType });
// console.log("resultPermitObject", { types, message, domain, primaryType });
const signature = await signTypedData(config as any, {
domain: domain as any,
message: message,
primaryType: primaryType,
types: types,
});
const typedData: TypedDataDefinition = {
domain,
message,
primaryType,
types,
};
console.log("signature", {
domain: domain as any,
message: message,
primaryType: primaryType,
types: types,
});
const signature = await signTypedData(wagmiConfig, typedData);
// console.log("signature", {
// domain,
// message,
// primaryType,
// types,
// });
const { r, s, v } = getSignatureRSV(signature);
loading.value = true;
console.log("permitAndSnatch", assets, account.address!, taxRateObj?.index!, positions, deadline, v, r, s);
// console.log("permitAndSnatch", assets, account.address!, taxRateObj?.index!, positions, deadline, v, r, s);
const hash = await permitAndSnatch(assets, account.address!, taxRateObj?.index!, positions, deadline, v, r, s);
console.log("hash", hash);
const taxRateIndex = taxRateObj?.index ?? 0;
const hash = await permitAndSnatch(assets, account.address!, taxRateIndex, positions, deadline, v, r, s);
// console.log("hash", hash);
loading.value = false;
waiting.value = true;
const data = await waitForTransactionReceipt(config as any, {
const data = await waitForTransactionReceipt(wagmiConfig, {
hash: hash,
});
const topics: any = decodeEventLog({
const topics = decodeEventLog({
abi: StakeContract.abi,
data: data.logs[3].data,
topics: data.logs[3].topics,
});
}) as DecodeEventLogReturnType & { args: PositionCreatedArgs };
const eventArgs: PositionCreatedArgs = topics.args;
const amount = compactNumber(
formatBigIntDivision(eventArgs.harbergDeposit, 10n ** BigInt(wallet.balance.decimals))
);
contractToast.showSuccessToast(
amount,
"Success!",
"You Staked",
"Check your positions on the<br /> Staker Dashboard",
"$KRK"
);
const decimalsAsBigInt = typeof wallet.balance.decimals === 'bigint' ? wallet.balance.decimals : BigInt(wallet.balance.decimals);
const amount = compactNumber(formatBigIntDivision(eventArgs.harbergDeposit, 10n ** decimalsAsBigInt));
contractToast.showSuccessToast(amount, 'Success!', 'You Staked', 'Check your positions on the<br /> Staker Dashboard', '$KRK');
waiting.value = false;
await getNonce();
} catch (error: any) {
console.error("error", error);
console.log(JSON.parse(JSON.stringify(error)));
contractToast.showFailToast(error.name);
} catch (error) {
// console.error("error", error);
// console.log(JSON.parse(JSON.stringify(error)));
const message = error instanceof Error ? error.name : 'Transaction failed';
contractToast.showFailToast(message);
} finally {
loading.value = false;
waiting.value = false;

View file

@ -1,12 +1,13 @@
import { ref, onMounted, onUnmounted, reactive, computed } from "vue";
import axios from "axios";
import { chainData } from "./useWallet";
import { watchBlocks, watchChainId } from "@wagmi/core";
import { config } from "@/wagmi";
import logger from "@/utils/logger";
import type { WatchBlocksReturnType } from "viem";
import { bigInt2Number } from "@/utils/helper";
const demo = sessionStorage.getItem("demo") === "true";
import { ref, reactive, computed } from 'vue';
import axios from 'axios';
import { chainData } from './useWallet';
import { watchChainId } from '@wagmi/core';
import type { Config } from '@wagmi/core';
import { config } from '@/wagmi';
import logger from '@/utils/logger';
import type { WatchBlocksReturnType } from 'viem';
import { bigInt2Number } from '@/utils/helper';
const demo = sessionStorage.getItem('demo') === 'true';
const GRAPHQL_TIMEOUT_MS = 15_000;
const RETRY_BASE_DELAY_MS = 1_500;
@ -37,9 +38,9 @@ let statsRetryTimer: number | null = null;
function formatGraphqlError(error: unknown): string {
if (axios.isAxiosError(error)) {
const responseErrors = (error.response?.data as any)?.errors;
const responseErrors = (error.response?.data as { errors?: unknown[] })?.errors;
if (Array.isArray(responseErrors) && responseErrors.length > 0) {
return responseErrors.map((err: any) => err?.message ?? "GraphQL error").join(", ");
return responseErrors.map((err: unknown) => (err as { message?: string })?.message ?? 'GraphQL error').join(', ');
}
if (error.response?.status) {
return `GraphQL request failed with status ${error.response.status}`;
@ -51,7 +52,7 @@ function formatGraphqlError(error: unknown): string {
if (error instanceof Error && error.message) {
return error.message;
}
return "Unknown GraphQL error";
return 'Unknown GraphQL error';
}
function clearStatsRetryTimer() {
@ -62,7 +63,7 @@ function clearStatsRetryTimer() {
}
function scheduleStatsRetry() {
if (typeof window === "undefined") {
if (typeof window === 'undefined') {
return;
}
if (statsRetryTimer !== null) {
@ -78,7 +79,9 @@ function scheduleStatsRetry() {
export async function loadStatsCollection(endpoint: string) {
logger.info(`loadStatsCollection for chain: ${chainData.value?.path}`);
const res = await axios.post(endpoint, {
const res = await axios.post(
endpoint,
{
query: `query StatsQuery {
stats(id: "0x01") {
burnNextHourProjected
@ -96,16 +99,18 @@ export async function loadStatsCollection(endpoint: string) {
totalMinted
}
}`,
}, { timeout: GRAPHQL_TIMEOUT_MS });
},
{ 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(", "));
throw new Error(errors.map((err: unknown) => (err as { message?: string })?.message ?? 'GraphQL error').join(', '));
}
const stats = res.data?.data?.stats as StatsRecord | undefined;
if (!stats) {
throw new Error("Stats entity not found in GraphQL response");
throw new Error('Stats entity not found in GraphQL response');
}
return [{ ...stats, kraikenTotalSupply: stats.kraikenTotalSupply }];
@ -172,10 +177,7 @@ const stakeTotalSupply = computed(() => {
//Total Supply Change / 7d=mintedLastWeekburnedLastWeek
const totalSupplyChange7d = computed(() => {
if (rawStatsCollections.value?.length > 0) {
return (
BigInt(rawStatsCollections.value[0].mintedLastWeek) -
BigInt(rawStatsCollections.value[0].burnedLastWeek)
);
return BigInt(rawStatsCollections.value[0].mintedLastWeek) - BigInt(rawStatsCollections.value[0].burnedLastWeek);
} else {
return 0n;
}
@ -184,10 +186,7 @@ const totalSupplyChange7d = computed(() => {
//totalsupply Change7d / harbtotalsupply
const inflation7d = computed(() => {
if (rawStatsCollections.value?.length > 0 && BigInt(rawStatsCollections.value[0].kraikenTotalSupply) > 0n) {
return (
BigInt(rawStatsCollections.value[0].mintedLastWeek) -
BigInt(rawStatsCollections.value[0].burnedLastWeek)
);
return BigInt(rawStatsCollections.value[0].mintedLastWeek) - BigInt(rawStatsCollections.value[0].burnedLastWeek);
} else {
return 0n;
}
@ -195,7 +194,7 @@ const inflation7d = computed(() => {
const stakeableSupply = computed(() => {
if (rawStatsCollections.value?.length > 0 && BigInt(rawStatsCollections.value[0].kraikenTotalSupply) > 0n) {
console.log("rawStatsCollections.value[0]", rawStatsCollections.value[0]);
// console.log("rawStatsCollections.value[0]", rawStatsCollections.value[0]);
return stakeTotalSupply.value / 5n;
} else {
@ -206,7 +205,7 @@ const stakeableSupply = computed(() => {
//maxSlots
const maxSlots = computed(() => {
if (rawStatsCollections.value?.length > 0 && BigInt(rawStatsCollections.value[0].kraikenTotalSupply) > 0n) {
console.log("rawStatsCollections.value[0]", rawStatsCollections.value[0]);
// console.log("rawStatsCollections.value[0]", rawStatsCollections.value[0]);
return (bigInt2Number(stakeTotalSupply.value, 18) * 0.2) / 100;
} else {
@ -230,7 +229,7 @@ export async function loadStats() {
const endpoint = chainData.value?.graphql?.trim();
if (!endpoint) {
rawStatsCollections.value = [];
statsError.value = "GraphQL endpoint not configured for this chain.";
statsError.value = 'GraphQL endpoint not configured for this chain.';
clearStatsRetryTimer();
statsRetryDelayMs.value = RETRY_BASE_DELAY_MS;
loading.value = false;
@ -244,7 +243,7 @@ export async function loadStats() {
statsRetryDelayMs.value = RETRY_BASE_DELAY_MS;
clearStatsRetryTimer();
} catch (error) {
console.warn("[stats] loadStats() failed", error);
// console.warn('[stats] loadStats() failed', error);
rawStatsCollections.value = [];
statsError.value = formatGraphqlError(error);
scheduleStatsRetry();
@ -254,9 +253,9 @@ export async function loadStats() {
}
}
let unwatch: any = null;
let unwatchBlock: WatchBlocksReturnType;
const loadingWatchBlock = ref(false);
import { onMounted, onUnmounted } from 'vue';
let unwatch: WatchBlocksReturnType | null = null;
export function useStatCollection() {
onMounted(async () => {
//initial loading stats
@ -265,16 +264,16 @@ export function useStatCollection() {
}
});
if (!unwatch) {
console.log("watchChain");
// console.log("watchChain");
//chain Switch reload stats for other chain
unwatch = watchChainId(config as any, {
async onChange(chainId) {
unwatch = watchChainId(config as Config, {
async onChange(_chainId) {
await loadStats();
},
});
// const unwatchBlock = watchBlocks(config as any, {
// const unwatchBlock = watchBlocks(config as Config, {
// async onBlock(block) {
// console.log('Block changed!', block)
// await loadStats();
@ -283,7 +282,9 @@ export function useStatCollection() {
}
onUnmounted(() => {
clearStatsRetryTimer();
if (unwatch) {
unwatch();
}
});
return reactive({

View file

@ -1,19 +1,20 @@
import { ref, onMounted, onUnmounted, reactive, computed, type ComputedRef } from "vue";
import * as StakeContract from "@/contracts/stake";
import { waitForTransactionReceipt } from "@wagmi/core";
import { config } from "@/wagmi";
import { useContractToast } from "./useContractToast";
import { compactNumber, formatBigIntDivision } from "@/utils/helper";
import { decodeEventLog } from "viem";
import { useWallet } from "./useWallet";
import { ref, reactive, computed, type ComputedRef } from 'vue';
import * as StakeContract from '@/contracts/stake';
import { waitForTransactionReceipt } from '@wagmi/core';
import type { Config } from '@wagmi/core';
import { config } from '@/wagmi';
import { useContractToast } from './useContractToast';
import { compactNumber, formatBigIntDivision } from '@/utils/helper';
import { decodeEventLog, type DecodeEventLogReturnType } from 'viem';
import { useWallet } from './useWallet';
const contractToast = useContractToast();
const wallet = useWallet();
enum StakeState {
SignTransaction = "SignTransaction",
Waiting = "Waiting",
Unstakeable = "Unstakeable",
SignTransaction = 'SignTransaction',
Waiting = 'Waiting',
Unstakeable = 'Unstakeable',
}
interface PositionRemovedArgs {
@ -38,35 +39,33 @@ export function useUnstake() {
async function exitPosition(positionId: bigint) {
try {
console.log("positionId", positionId);
// console.log("positionId", positionId);
loading.value = true;
const hash = await StakeContract.exitPosition(positionId);
console.log("hash", hash);
// console.log("hash", hash);
loading.value = false;
waiting.value = true;
const data = await waitForTransactionReceipt(config as any, {
const data = await waitForTransactionReceipt(config as Config, {
hash: hash,
});
const topics: any = decodeEventLog({
const topics = decodeEventLog({
abi: StakeContract.StakeContract.abi,
data: data.logs[2].data,
topics: data.logs[2].topics,
});
}) as DecodeEventLogReturnType & { args: PositionRemovedArgs };
const eventArgs: PositionRemovedArgs = topics.args;
const amount = compactNumber(
formatBigIntDivision(eventArgs.harbergPayout, 10n ** BigInt(wallet.balance.decimals))
);
contractToast.showSuccessToast(amount, "Success!", "You unstaked", "", "$KRK");
const amount = compactNumber(formatBigIntDivision(eventArgs.harbergPayout, 10n ** BigInt(wallet.balance.decimals)));
contractToast.showSuccessToast(amount, 'Success!', 'You unstaked', '', '$KRK');
waiting.value = false;
wallet.loadBalance();
} catch (error: any) {
console.error("error", error);
contractToast.showFailToast(error.name);
} catch (error) {
// console.error("error", error);
contractToast.showFailToast((error as Error).name);
} finally {
loading.value = false;
waiting.value = false;

View file

@ -1,45 +1,43 @@
import { ref, onMounted, onUnmounted, reactive, computed } from "vue";
import { type Ref } from "vue";
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";
import logger from "@/utils/logger";
import { setHarbContract } from "@/contracts/harb";
import { setStakeContract } from "@/contracts/stake";
import {chainsData, DEFAULT_CHAIN_ID} from "@/config"
import { ref, reactive, computed } from 'vue';
import { getAccount, getBalance, watchAccount, watchChainId, type Config } from '@wagmi/core';
import { type WatchAccountReturnType, type GetAccountReturnType, type GetBalanceReturnType } from '@wagmi/core';
import { config } from '@/wagmi';
import { getAllowance, HarbContract, getNonce } from '@/contracts/harb';
import logger from '@/utils/logger';
import { setHarbContract } from '@/contracts/harb';
import { setStakeContract } from '@/contracts/stake';
import { chainsData, DEFAULT_CHAIN_ID } from '@/config';
const balance = ref<GetBalanceReturnType>({
value: 0n,
decimals: 0,
symbol: "",
formatted: ""
symbol: '',
formatted: '',
});
const account = ref<GetAccountReturnType>(getAccount(config as any));
const account = ref<GetAccountReturnType>(getAccount(config as Config));
if (!account.value.chainId) {
(account.value as any).chainId = DEFAULT_CHAIN_ID;
account.value.chainId = DEFAULT_CHAIN_ID;
}
const selectedChainId = computed(() => account.value.chainId ?? DEFAULT_CHAIN_ID);
export const chainData = computed(() => {
return chainsData.find((obj) => obj.id === selectedChainId.value)
})
return chainsData.find(obj => obj.id === selectedChainId.value);
});
let unwatch: any = null;
let unwatchChain: any = null;
let unwatch: WatchAccountReturnType | null = null;
let unwatchChain: WatchAccountReturnType | null = null;
export function useWallet() {
async function loadBalance() {
logger.contract("loadBalance")
logger.contract('loadBalance');
if (account.value.address) {
console.log("HarbContract",HarbContract );
// console.log("HarbContract",HarbContract );
balance.value = await getBalance(config as any, {
balance.value = await getBalance(config as Config, {
address: account.value.address,
token: HarbContract.contractAddress,
});
console.log("balance.value", balance.value);
// console.log("balance.value", balance.value);
return balance.value;
} else {
@ -48,20 +46,20 @@ export function useWallet() {
}
if (!unwatch) {
console.log("useWallet function");
// console.log("useWallet function");
unwatch = watchAccount(config as any, {
unwatch = watchAccount(config as Config, {
async onChange(data) {
console.log("watchaccount-useWallet", data);
// console.log("watchaccount-useWallet", data);
if (!data.address) {
logger.info(`disconnected`);
balance.value = {
value: 0n,
decimals: 0,
symbol: "",
formatted: ""
}
symbol: '',
formatted: '',
};
} else if (account.value.address !== data.address || account.value.chainId !== data.chainId) {
logger.info(`Account changed!:`, data.address);
account.value = data;
@ -69,26 +67,23 @@ export function useWallet() {
await getAllowance();
// await loadPositions();
await getNonce();
setHarbContract()
setStakeContract()
setHarbContract();
setStakeContract();
}
},
});
}
//funzt nicht mehr-> library Änderung?
if (!unwatchChain) {
console.log("unwatchChain");
unwatchChain = watchChainId(config as any, {
async onChange(chainId) {
console.log("chainId123", chainId);
// console.log("unwatchChain");
unwatchChain = watchChainId(config as Config, {
async onChange(_chainId) {
await loadBalance();
await getAllowance();
await getNonce();
}
},
});
}

View file

@ -1,18 +1,28 @@
import deploymentsLocal from "../../onchain/deployments-local.json";
import deploymentsLocal from '../../onchain/deployments-local.json';
const env = import.meta.env;
const LOCAL_PONDER_URL = env.VITE_PONDER_BASE_SEPOLIA_LOCAL_FORK ?? "http://127.0.0.1:42069/graphql";
const LOCAL_TXNBOT_URL = env.VITE_TXNBOT_BASE_SEPOLIA_LOCAL_FORK ?? "http://127.0.0.1:43069";
const LOCAL_RPC_URL = env.VITE_LOCAL_RPC_URL ?? "/rpc/anvil";
const LOCAL_PONDER_URL = env.VITE_PONDER_BASE_SEPOLIA_LOCAL_FORK ?? 'http://127.0.0.1:42069/graphql';
const LOCAL_TXNBOT_URL = env.VITE_TXNBOT_BASE_SEPOLIA_LOCAL_FORK ?? 'http://127.0.0.1:43069';
const LOCAL_RPC_URL = env.VITE_LOCAL_RPC_URL ?? '/rpc/anvil';
const localContracts = (deploymentsLocal as any)?.contracts ?? {};
const localInfra = (deploymentsLocal as any)?.infrastructure ?? {};
const LOCAL_KRAIKEN = (env.VITE_KRAIKEN_ADDRESS ?? localContracts.Kraiken ?? "").trim();
const LOCAL_STAKE = (env.VITE_STAKE_ADDRESS ?? localContracts.Stake ?? "").trim();
const LOCAL_LM = (env.VITE_LIQUIDITY_MANAGER ?? localContracts.LiquidityManager ?? "").trim();
const LOCAL_WETH = (env.VITE_LOCAL_WETH ?? localInfra.weth ?? "0x4200000000000000000000000000000000000006").trim();
const LOCAL_ROUTER = (env.VITE_SWAP_ROUTER ?? "0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4").trim();
interface DeploymentContracts {
Kraiken?: string;
Stake?: string;
LiquidityManager?: string;
}
interface DeploymentInfrastructure {
weth?: string;
}
const localContracts = (deploymentsLocal as { contracts?: DeploymentContracts })?.contracts ?? {};
const localInfra = (deploymentsLocal as { infrastructure?: DeploymentInfrastructure })?.infrastructure ?? {};
const LOCAL_KRAIKEN = (env.VITE_KRAIKEN_ADDRESS ?? localContracts.Kraiken ?? '').trim();
const LOCAL_STAKE = (env.VITE_STAKE_ADDRESS ?? localContracts.Stake ?? '').trim();
const LOCAL_LM = (env.VITE_LIQUIDITY_MANAGER ?? localContracts.LiquidityManager ?? '').trim();
const LOCAL_WETH = (env.VITE_LOCAL_WETH ?? localInfra.weth ?? '0x4200000000000000000000000000000000000006').trim();
const LOCAL_ROUTER = (env.VITE_SWAP_ROUTER ?? '0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4').trim();
function detectDefaultChainId(): number {
const envValue = import.meta.env.VITE_DEFAULT_CHAIN_ID;
@ -23,9 +33,9 @@ function detectDefaultChainId(): number {
}
}
if (typeof window !== "undefined") {
if (typeof window !== 'undefined') {
const host = window.location.hostname.toLowerCase();
if (host === "localhost" || host === "127.0.0.1" || host === "::1") {
if (host === 'localhost' || host === '127.0.0.1' || host === '::1') {
return 31337;
}
}
@ -40,57 +50,58 @@ export const chainsData = [
// local base sepolia fork
id: 31337,
graphql: LOCAL_PONDER_URL,
path: "local",
path: 'local',
stake: LOCAL_STAKE,
harb: LOCAL_KRAIKEN,
uniswap: "",
uniswap: '',
cheats: {
weth: LOCAL_WETH,
swapRouter: LOCAL_ROUTER,
liquidityManager: LOCAL_LM,
txnBot: LOCAL_TXNBOT_URL,
rpc: LOCAL_RPC_URL,
}
},
},
{
// sepolia
id: 11155111,
graphql: import.meta.env.VITE_PONDER_SEPOLIA ?? "",
path: "sepolia",
stake: "0xCd21a41a137BCAf8743E47D048F57D92398f7Da9",
harb: "0x087F256D11fe533b0c7d372e44Ee0F9e47C89dF9",
uniswap: "https://app.uniswap.org/swap?chain=mainnet&inputCurrency=NATIVE",
cheats: null
}, {
graphql: import.meta.env.VITE_PONDER_SEPOLIA ?? '',
path: 'sepolia',
stake: '0xCd21a41a137BCAf8743E47D048F57D92398f7Da9',
harb: '0x087F256D11fe533b0c7d372e44Ee0F9e47C89dF9',
uniswap: 'https://app.uniswap.org/swap?chain=mainnet&inputCurrency=NATIVE',
cheats: null,
},
{
// base-sepolia (local dev default)
id: 84532,
graphql: import.meta.env.VITE_PONDER_BASE_SEPOLIA_LOCAL_FORK ?? LOCAL_PONDER_URL,
path: "sepoliabase",
stake: "0xe28020BCdEeAf2779dd47c670A8eFC2973316EE2",
harb: "0x22c264Ecf8D4E49D1E3CabD8DD39b7C4Ab51C1B8",
uniswap: "https://app.uniswap.org/swap?chain=mainnet&inputCurrency=NATIVE",
path: 'sepoliabase',
stake: '0xe28020BCdEeAf2779dd47c670A8eFC2973316EE2',
harb: '0x22c264Ecf8D4E49D1E3CabD8DD39b7C4Ab51C1B8',
uniswap: 'https://app.uniswap.org/swap?chain=mainnet&inputCurrency=NATIVE',
cheats: {
weth: "0x4200000000000000000000000000000000000006",
swapRouter: "0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4",
txnBot: import.meta.env.VITE_TXNBOT_BASE_SEPOLIA_LOCAL_FORK ?? LOCAL_TXNBOT_URL
}
weth: '0x4200000000000000000000000000000000000006',
swapRouter: '0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4',
txnBot: import.meta.env.VITE_TXNBOT_BASE_SEPOLIA_LOCAL_FORK ?? LOCAL_TXNBOT_URL,
},
},
{
// base mainnet
id: 8453,
graphql: import.meta.env.VITE_PONDER_BASE ?? "",
path: "base",
stake: "0xed70707fab05d973ad41eae8d17e2bcd36192cfc",
harb: "0x45caa5929f6ee038039984205bdecf968b954820",
uniswap: "https://app.uniswap.org/swap?chain=mainnet&inputCurrency=NATIVE",
graphql: import.meta.env.VITE_PONDER_BASE ?? '',
path: 'base',
stake: '0xed70707fab05d973ad41eae8d17e2bcd36192cfc',
harb: '0x45caa5929f6ee038039984205bdecf968b954820',
uniswap: 'https://app.uniswap.org/swap?chain=mainnet&inputCurrency=NATIVE',
cheats: {
weth: "0x4200000000000000000000000000000000000006",
swapRouter: "",
txnBot: import.meta.env.VITE_TXNBOT_BASE ?? ""
}
weth: '0x4200000000000000000000000000000000000006',
swapRouter: '',
txnBot: import.meta.env.VITE_TXNBOT_BASE ?? '',
},
},
];
export function getChain(id: number) {
return chainsData.find((obj) => obj.id === id)
return chainsData.find(obj => obj.id === id);
}

View file

@ -1,24 +1,15 @@
import { ref, onMounted, onUnmounted, reactive, computed, watch } from "vue";
import { config } from "@/wagmi";
import { type WatchEventReturnType } from "viem";
import {
getAccount,
watchContractEvent,
readContract,
writeContract,
waitForTransactionReceipt,
type WaitForTransactionReceiptParameters,
getChainId
} from "@wagmi/core";
import { KraikenAbi } from "kraiken-lib";
import { type Abi, type Address } from "viem";
import { StakeContract } from "@/contracts/stake";
import {getChain} from "@/config"
import logger from "@/utils/logger";
import { ref } from 'vue';
import { config } from '@/wagmi';
import { getAccount, readContract, writeContract, waitForTransactionReceipt, getChainId } from '@wagmi/core';
import type { Config } from '@wagmi/core';
import { KraikenAbi } from 'kraiken-lib';
import { type Abi, type Address, type Hash } from 'viem';
import { StakeContract } from '@/contracts/stake';
import { getChain } from '@/config';
import logger from '@/utils/logger';
// const chain1 = useChain();
// console.log("chain1", chain1);
interface Contract {
abi: Abi;
contractAddress: Address;
@ -34,18 +25,18 @@ export const totalSupply = ref(0n);
export let HarbContract = getHarbJson();
function getHarbJson() {
console.log("getHarbJson");
// console.log("getHarbJson");
const chainId = getChainId(config as any);
console.log("chainId", chainId);
const chainId = getChainId(config as Config);
// console.log("chainId", chainId);
const chain = getChain(chainId)
const chain = getChain(chainId);
return { abi: KraikenAbi as Abi, contractAddress: chain?.harb } as Contract;
}
export function setHarbContract() {
console.log("setHarbContract");
// console.log("setHarbContract");
HarbContract = getHarbJson();
}
@ -55,18 +46,17 @@ export function setHarbContract(){
// });
export async function getAllowance() {
logger.contract("getAllowance");
logger.contract('getAllowance');
const account = getAccount(config as any);
const account = getAccount(config as Config);
if (!account.address) {
return 0n;
}
const result = await readContract(config as any, {
const result = await readContract(config as Config, {
abi: HarbContract.abi,
address: HarbContract.contractAddress,
functionName: "allowance",
functionName: 'allowance',
args: [account.address, StakeContract.contractAddress],
});
allowance.value = result;
@ -74,31 +64,31 @@ export async function getAllowance() {
}
export async function getMinStake() {
logger.contract("getMinStake");
logger.contract('getMinStake');
const result: bigint = await readContract(config as any, {
const result: bigint = (await readContract(config as Config, {
abi: HarbContract.abi,
address: HarbContract.contractAddress,
functionName: "minStake",
functionName: 'minStake',
args: [],
}) as bigint;
})) as bigint;
allowance.value = result;
return result;
}
export async function getNonce() {
logger.contract("getNonce");
logger.contract('getNonce');
const account = getAccount(config as any);
const account = getAccount(config as Config);
if (!account.address) {
return 0n;
}
console.log("HarbContract.contractAddress", HarbContract.contractAddress);
// console.log("HarbContract.contractAddress", HarbContract.contractAddress);
const result = await readContract(config as any, {
const result = await readContract(config as Config, {
abi: HarbContract.abi,
address: HarbContract.contractAddress,
functionName: "nonces",
functionName: 'nonces',
args: [account.address],
});
nonce.value = result;
@ -107,13 +97,12 @@ export async function getNonce() {
}
export async function getName() {
logger.contract("getName");
logger.contract('getName');
const result = await readContract(config as any, {
const result = await readContract(config as Config, {
abi: HarbContract.abi,
address: HarbContract.contractAddress,
functionName: "name",
functionName: 'name',
args: [],
});
name.value = result;
@ -121,48 +110,45 @@ export async function getName() {
return result as string;
}
export async function approve(amount: bigint): Promise<any> {
const account = getAccount(config as any);
export async function approve(amount: bigint): Promise<Hash> {
const account = getAccount(config as Config);
if (!account.address) {
throw new Error("no address found");
throw new Error('no address found');
}
const result = await writeContract(config as any, {
const result = await writeContract(config as Config, {
abi: HarbContract.abi,
address: HarbContract.contractAddress,
functionName: "approve",
functionName: 'approve',
args: [StakeContract.contractAddress, amount],
});
console.log("result", result);
const transactionReceipt = waitForTransactionReceipt(config as any, {
// console.log("result", result);
await waitForTransactionReceipt(config as Config, {
hash: result,
});
console.log("transactionReceipt", transactionReceipt);
// console.log("transactionReceipt", transactionReceipt);
return transactionReceipt;
return result;
}
//claim
export async function claimUbi(address: Address): Promise<any> {
const result = await writeContract(config as any, {
export async function claimUbi(address: Address): Promise<Hash> {
const result = await writeContract(config as Config, {
abi: HarbContract.abi,
address: HarbContract.contractAddress,
functionName: "claimUbi",
functionName: 'claimUbi',
args: [address],
});
return result;
}
export async function getTotalSupply() {
logger.contract("getTotalSupply");
logger.contract('getTotalSupply');
const result = await readContract(config as any, {
const result = await readContract(config as Config, {
abi: HarbContract.abi,
address: HarbContract.contractAddress,
functionName: "totalSupply",
functionName: 'totalSupply',
args: [],
});
totalSupply.value = result as bigint;

View file

@ -1,10 +1,11 @@
import { ref } from "vue";
import { config } from "@/wagmi";
import { readContract, writeContract, getChainId } from "@wagmi/core";
import { StakeAbi } from "kraiken-lib";
import { type Abi, type Address } from "viem";
import {getChain} from "@/config"
import logger from "@/utils/logger";
import { ref } from 'vue';
import { config } from '@/wagmi';
import { readContract, writeContract, getChainId } from '@wagmi/core';
import type { Config } from '@wagmi/core';
import { StakeAbi } from 'kraiken-lib';
import { type Abi, type Address, type Hash, type Hex } from 'viem';
import { getChain } from '@/config';
import logger from '@/utils/logger';
const TAX_FLOOR_DURATION = 60 * 60 * 24 * 3;
interface Contract {
@ -12,51 +13,43 @@ interface Contract {
contractAddress: Address;
}
export const minStake = ref();
export const totalSupply = ref(0n);
export const outstandingSupply = ref(0n)
export const outstandingSupply = ref(0n);
export let StakeContract = getStakeJson()
export let StakeContract = getStakeJson();
function getStakeJson() {
const chainId = getChainId(config as any);
console.log("chainId", chainId);
const chainId = getChainId(config as Config);
// console.log("chainId", chainId);
const chain = getChain(chainId)
const chain = getChain(chainId);
return { abi: StakeAbi as Abi, contractAddress: chain?.stake } as Contract;
}
export function setStakeContract() {
logger.contract("setStakeContract")
logger.contract('setStakeContract');
StakeContract = getStakeJson();
}
export async function snatchService(assets: bigint, receiver: Address, taxRate: number, positionsToSnatch: Array<bigint>) {
// console.log("StakeContract", StakeContract);
export async function snatchService(
assets: BigInt,
receiver: Address,
taxRate: Number,
positionsToSnatch: Array<BigInt>
) {
console.log("StakeContract", StakeContract);
const result = await writeContract(config as any, {
const result = await writeContract(config as Config, {
abi: StakeContract.abi,
address: StakeContract.contractAddress,
functionName: "snatch",
functionName: 'snatch',
args: [assets, receiver, taxRate, positionsToSnatch],
});
return result;
}
export async function exitPosition(positionId: bigint): Promise<any> {
const result = await writeContract(config as any, {
export async function exitPosition(positionId: bigint): Promise<Hash> {
const result = await writeContract(config as Config, {
abi: StakeContract.abi,
address: StakeContract.contractAddress,
functionName: "exitPosition",
functionName: 'exitPosition',
args: [positionId],
});
@ -64,11 +57,11 @@ export async function exitPosition(positionId: bigint): Promise<any> {
}
//changeTax
export async function changeTax(positionId: bigint, taxRate: number): Promise<any> {
const result = await writeContract(config as any, {
export async function changeTax(positionId: bigint, taxRate: number): Promise<Hash> {
const result = await writeContract(config as Config, {
abi: StakeContract.abi,
address: StakeContract.contractAddress,
functionName: "changeTax",
functionName: 'changeTax',
args: [positionId, taxRate],
});
@ -79,48 +72,48 @@ export async function changeTax(positionId: bigint, taxRate: number): Promise<an
* snatch/stake with permit
*/
export async function permitAndSnatch(
assets: BigInt,
assets: bigint,
receiver: Address,
taxRate: number,
positionsToSnatch: Array<BigInt>,
deadline: BigInt,
v: any,
r: any,
s: any
positionsToSnatch: Array<bigint>,
deadline: bigint,
v: number,
r: Hex,
s: Hex
) {
console.log("permitAndSnatch", assets, receiver, taxRate, positionsToSnatch, deadline, v, r, s);
// console.log("permitAndSnatch", assets, receiver, taxRate, positionsToSnatch, deadline, v, r, s);
const result = await writeContract(config as any, {
const result = await writeContract(config as Config, {
abi: StakeContract.abi,
address: StakeContract.contractAddress,
functionName: "permitAndSnatch",
functionName: 'permitAndSnatch',
args: [assets, receiver, taxRate, positionsToSnatch, deadline, v, r, s],
});
return result;
}
export async function getTotalSupply() {
logger.contract("getTotalSupply")
logger.contract('getTotalSupply');
await setStakeContract();
const result = await readContract(config as any, {
const result = await readContract(config as Config, {
abi: StakeContract.abi,
address: StakeContract.contractAddress,
functionName: "totalSupply",
functionName: 'totalSupply',
args: [],
});
console.log("result", result);
// console.log("result", result);
totalSupply.value = result as bigint;
return result;
}
export async function getOutstandingSupply() {
logger.contract("getOutstandingSupply")
logger.contract('getOutstandingSupply');
const result = await readContract(config as any, {
const result = await readContract(config as Config, {
abi: StakeContract.abi,
address: StakeContract.contractAddress,
functionName: "outstandingStake",
functionName: 'outstandingStake',
args: [],
});
@ -129,35 +122,35 @@ export async function getOutstandingSupply() {
}
export async function getTaxDue(positionID: bigint) {
logger.contract("getTaxDue")
const result = await readContract(config as any, {
logger.contract('getTaxDue');
const result = await readContract(config as Config, {
abi: StakeContract.abi,
address: StakeContract.contractAddress,
functionName: "taxDue",
functionName: 'taxDue',
args: [positionID, TAX_FLOOR_DURATION],
});
return result as bigint;
}
export async function payTax(positionID: bigint) {
console.log("payTax", positionID);
// console.log("payTax", positionID);
const result = await writeContract(config as any, {
const result = await writeContract(config as Config, {
abi: StakeContract.abi,
address: StakeContract.contractAddress,
functionName: "payTax",
functionName: 'payTax',
args: [positionID],
});
return result;
}
export async function assetsToShares(asset: bigint) {
console.log("assetsToShares", asset);
// console.log("assetsToShares", asset);
const result = await readContract(config as any, {
const result = await readContract(config as Config, {
abi: StakeContract.abi,
address: StakeContract.contractAddress,
functionName: "assetsToShares",
functionName: 'assetsToShares',
args: [asset],
});
return result as bigint;

View file

@ -1,17 +1,25 @@
import type { DirectiveBinding } from 'vue';
interface ClickOutsideElement extends HTMLElement {
clickOutsideEvent?: (event: MouseEvent) => void;
}
export default {
beforeMount(el: any, binding: any) {
el.clickOutsideEvent = function (event: any) {
beforeMount(el: ClickOutsideElement, binding: DirectiveBinding<(event: MouseEvent) => void>) {
el.clickOutsideEvent = function (event: MouseEvent) {
// Check if the clicked element is neither the element
// to which the directive is applied nor its child
if (!(el === event.target || el.contains(event.target))) {
if (!(el === event.target || el.contains(event.target as Node))) {
// Invoke the provided method
binding.value(event);
}
};
document.addEventListener("click", el.clickOutsideEvent);
document.addEventListener('click', el.clickOutsideEvent);
},
unmounted(el: any) {
unmounted(el: ClickOutsideElement) {
// Remove the event listener when the bound element is unmounted
document.removeEventListener("click", el.clickOutsideEvent);
if (el.clickOutsideEvent) {
document.removeEventListener('click', el.clickOutsideEvent);
}
},
};

View file

@ -1,10 +1,10 @@
<!-- src/layouts/NavbarLayout.vue -->
<template>
<navbar></navbar>
<slideout-panel v-model="showPanel">
<connect-wallet></connect-wallet>
<NavbarHeader></NavbarHeader>
<SlideoutPanel v-model="showPanel">
<ConnectWallet></ConnectWallet>
<!-- <f-footer :dark="true"></f-footer> -->
</slideout-panel>
</SlideoutPanel>
<div class="navbar-layout">
<main>
<slot></slot>
@ -16,13 +16,13 @@
<div class="mobile-navigation-bar" v-if="isMobile">
<div class="mobile-navigation-tab" @click="router.push('/')">
<div class="mobile-navigation-tab__icon">
<icon-home />
<IconHome />
</div>
<div class="mobile-navigation-tab__title">Stake</div>
</div>
<div class="mobile-navigation-tab" @click="openDocs">
<div class="mobile-navigation-tab__icon">
<icon-docs />
<IconDocs />
</div>
<div class="mobile-navigation-tab__title">Docs</div>
</div>
@ -30,28 +30,24 @@
</template>
<script setup lang="ts">
import Navbar from "@/components/layouts/Navbar.vue";
import SlideoutPanel from "@/components/layouts/SlideoutPanel.vue";
import ConnectWallet from "@/components/layouts/ConnectWallet.vue";
import ThemeToggle from "@/components/layouts/ThemeToggle.vue";
import FFooter from "@/components/layouts/FFooter.vue";
import { useMobile } from "@/composables/useMobile";
import { useDark } from "@/composables/useDark";
import IconHome from "@/components/icons/IconHome.vue";
import IconDocs from "@/components/icons/IconDocs.vue";
import { ref, provide } from "vue";
import {useRouter } from "vue-router"
import NavbarHeader from '@/components/layouts/NavbarHeader.vue';
import SlideoutPanel from '@/components/layouts/SlideoutPanel.vue';
import ConnectWallet from '@/components/layouts/ConnectWallet.vue';
import { useMobile } from '@/composables/useMobile';
import IconHome from '@/components/icons/IconHome.vue';
import IconDocs from '@/components/icons/IconDocs.vue';
import { ref, provide } from 'vue';
import { useRouter } from 'vue-router';
const showPanel = ref(false);
const { darkTheme } = useDark();
const router = useRouter()
const router = useRouter();
const isMobile = useMobile();
provide("isMobile", isMobile);
provide("showPanel", showPanel);
provide('isMobile', isMobile);
provide('showPanel', showPanel);
function openDocs() {
window.open("https://emberspirit007.github.io/KraikenLanding/#/docs/Introduction")
window.open('https://emberspirit007.github.io/KraikenLanding/#/docs/Introduction');
}
</script>

View file

@ -1,25 +1,25 @@
import { WagmiPlugin } from "@wagmi/vue";
import { QueryClient, VueQueryPlugin } from "@tanstack/vue-query";
import { createApp } from "vue";
import { config } from "./wagmi";
import ClickOutSide from "@/directives/ClickOutsideDirective";
import router from "./router";
import { WagmiPlugin } from '@wagmi/vue';
import { QueryClient, VueQueryPlugin } from '@tanstack/vue-query';
import { createApp } from 'vue';
import { config } from './wagmi';
import ClickOutSide from '@/directives/ClickOutsideDirective';
import router from './router';
import App from "./App.vue";
import "./assets/styles/main.sass";
import Toast from "vue-toastification";
import "vue-toastification/dist/index.css";
import App from './App.vue';
import './assets/styles/main.sass';
import Toast from 'vue-toastification';
import 'vue-toastification/dist/index.css';
const queryClient = new QueryClient();
const app = createApp(App);
app.directive("click-outside", ClickOutSide);
app.directive('click-outside', ClickOutSide);
app.use(WagmiPlugin, { config });
app.use(VueQueryPlugin, { queryClient });
app.use(router);
app.use(Toast, {
transition: "Vue-Toastification__fade",
containerClassName: "harb-toast-container",
transition: 'Vue-Toastification__fade',
containerClassName: 'harb-toast-container',
});
app.mount("#app");
app.mount('#app');

View file

@ -1,42 +1,41 @@
import { createRouter, createWebHashHistory } from "vue-router";
import HomeView from "../views/HomeView.vue";
import { createRouter, createWebHashHistory } from 'vue-router';
import { authGuard } from './authGuard';
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: "/",
name: "home",
redirect: "/stake",
path: '/',
name: 'home',
redirect: '/stake',
},
{
path: "/stake",
name: "stake",
path: '/stake',
name: 'stake',
meta: {
title: "Stake",
group: "navbar",
layout: 'NavbarLayout'
title: 'Stake',
group: 'navbar',
layout: 'NavbarLayout',
},
beforeEnter: authGuard,
component: () => import("../views/StakeView.vue"),
component: () => import('../views/StakeView.vue'),
},
{
path: "/login",
name: "login",
path: '/login',
name: 'login',
meta: {
layout: 'DefaultLayout'
, },
component: () => import("../views/LoginView.vue"),
layout: 'DefaultLayout',
},
component: () => import('../views/LoginView.vue'),
},
{
path: "/cheats",
name: "cheats",
path: '/cheats',
name: 'cheats',
meta: {
title: "Cheats",
layout: 'NavbarLayout'
title: 'Cheats',
layout: 'NavbarLayout',
},
component: () => import("../views/CheatsView.vue"),
component: () => import('../views/CheatsView.vue'),
},
],
});

View file

@ -1,13 +1,12 @@
import { type Address, type TypedDataDomain, type Hex, slice, hexToNumber, hexToBigInt, recoverAddress } from "viem";
import { type Address, type TypedDataDomain, slice, hexToBigInt } from 'viem';
export function createPermitObject(
verifyingContract: Address,
fromAddress: Address,
spender: Address,
nonce: BigInt,
deadline: BigInt,
value: BigInt,
nonce: bigint,
deadline: bigint,
value: bigint,
chain: number,
domainName: string
) {
@ -19,45 +18,44 @@ export function createPermitObject(
value: value,
};
const domainType = [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" },
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
];
const primaryType: "EIP712Domain" | "Permit" = "Permit";
const primaryType: 'EIP712Domain' | 'Permit' = 'Permit';
const types = {
EIP712Domain: domainType,
Permit: [
{
name: "owner",
type: "address",
name: 'owner',
type: 'address',
},
{
name: "spender",
type: "address",
name: 'spender',
type: 'address',
},
{
name: "value",
type: "uint256",
name: 'value',
type: 'uint256',
},
{
name: "nonce",
type: "uint256",
name: 'nonce',
type: 'uint256',
},
{
name: "deadline",
type: "uint256",
name: 'deadline',
type: 'uint256',
},
],
};
const domain: TypedDataDomain | undefined = {
name: domainName,
version: "1",
version: '1',
chainId: chain,
verifyingContract: verifyingContract,
};
@ -80,6 +78,6 @@ export function getSignatureRSV2(sig: `0x${string}`) {
export function getSignatureRSV(signature: `0x${string}`) {
const r = signature.slice(0, 66) as `0x${string}`;
const s = `0x${signature.slice(66, 130)}` as `0x${string}`;
const v = hexToBigInt(`0x${signature.slice(130, 132)}`);
const v = Number(hexToBigInt(`0x${signature.slice(130, 132)}`));
return { r, s, v };
}

View file

@ -1,4 +1,4 @@
var randseed = new Array(4); // Xorshift: [x, y, z, w] 32 bit values
const randseed = new Array(4); // Xorshift: [x, y, z, w] 32 bit values
interface BlockiesOpt {
seed: string;
@ -10,12 +10,11 @@ interface BlockiesOpt {
}
export function getBlocky(address: string) {
if (!address || typeof address !== "string" ) {
if (!address || typeof address !== 'string') {
return;
}
console.log("address", address);
var blockiesData = createIcon({
const blockiesData = createIcon({
seed: address?.toLowerCase(),
size: 8,
scale: 4,
@ -25,17 +24,17 @@ export function getBlocky(address: string) {
}
function seedrand(seed: string) {
for (var i = 0; i < randseed.length; i++) {
for (let i = 0; i < randseed.length; i++) {
randseed[i] = 0;
}
for (var i = 0; i < seed.length; i++) {
for (let i = 0; i < seed.length; i++) {
randseed[i % 4] = (randseed[i % 4] << 5) - randseed[i % 4] + seed.charCodeAt(i);
}
}
function rand() {
// based on Java's String.hashCode(), expanded to 4 32bit values
var t = randseed[0] ^ (randseed[0] << 11);
const t = randseed[0] ^ (randseed[0] << 11);
randseed[0] = randseed[1];
randseed[1] = randseed[2];
@ -47,36 +46,36 @@ function rand() {
function createColor() {
//saturation is the whole color spectrum
var h = Math.floor(rand() * 360);
const h = Math.floor(rand() * 360);
//saturation goes from 40 to 100, it avoids greyish colors
var s = rand() * 60 + 40 + "%";
const s = rand() * 60 + 40 + '%';
//lightness can be anything from 0 to 100, but probabilities are a bell curve around 50%
var l = (rand() + rand() + rand() + rand()) * 25 + "%";
const l = (rand() + rand() + rand() + rand()) * 25 + '%';
var color = "hsl(" + h + "," + s + "," + l + ")";
const color = 'hsl(' + h + ',' + s + ',' + l + ')';
return color;
}
function createImageData(size: number) {
var width = size; // Only support square icons for now
var height = size;
const width = size; // Only support square icons for now
const height = size;
var dataWidth = Math.ceil(width / 2);
var mirrorWidth = width - dataWidth;
const dataWidth = Math.ceil(width / 2);
const mirrorWidth = width - dataWidth;
var data = [];
for (var y = 0; y < height; y++) {
var row = [];
for (var x = 0; x < dataWidth; x++) {
const data = [];
for (let y = 0; y < height; y++) {
let row = [];
for (let x = 0; x < dataWidth; x++) {
// this makes foreground and background color to have a 43% (1/2.3) probability
// spot color has 13% chance
row[x] = Math.floor(rand() * 2.3);
}
var r = row.slice(0, mirrorWidth);
const r = row.slice(0, mirrorWidth);
r.reverse();
row = row.concat(r);
for (var i = 0; i < row.length; i++) {
for (let i = 0; i < row.length; i++) {
data.push(row[i]);
}
}
@ -84,50 +83,49 @@ function createImageData(size: number) {
return data;
}
function buildOpts(opts: any) {
var newOpts: any = {};
newOpts.seed = opts.seed || Math.floor(Math.random() * Math.pow(10, 16)).toString(16);
function buildOpts(opts: Partial<BlockiesOpt>): BlockiesOpt {
const seed = opts.seed || Math.floor(Math.random() * Math.pow(10, 16)).toString(16);
seedrand(seed);
seedrand(newOpts.seed);
newOpts.size = opts.size || 8;
newOpts.scale = opts.scale || 4;
newOpts.color = opts.color || createColor();
newOpts.bgcolor = opts.bgcolor || createColor();
newOpts.spotcolor = opts.spotcolor || createColor();
return newOpts;
return {
seed,
size: opts.size || 8,
scale: opts.scale || 4,
color: opts.color || createColor(),
bgcolor: opts.bgcolor || createColor(),
spotcolor: opts.spotcolor || createColor(),
};
}
function renderIcon(opts: BlockiesOpt, canvas: HTMLCanvasElement) {
opts = buildOpts(opts || {});
var imageData = createImageData(opts.size);
var width = Math.sqrt(imageData.length);
function renderIcon(opts: Partial<BlockiesOpt>, canvas: HTMLCanvasElement) {
const fullOpts = buildOpts(opts);
const imageData = createImageData(fullOpts.size);
const width = Math.sqrt(imageData.length);
canvas.width = canvas.height = opts.size * opts.scale;
canvas.width = canvas.height = fullOpts.size * fullOpts.scale;
var cc = canvas.getContext("2d")!;
cc.fillStyle = opts.bgcolor!;
const cc = canvas.getContext('2d')!;
cc.fillStyle = fullOpts.bgcolor!;
cc.fillRect(0, 0, canvas.width, canvas.height);
cc.fillStyle = opts.color!;
cc.fillStyle = fullOpts.color!;
for (var i = 0; i < imageData.length; i++) {
for (let i = 0; i < imageData.length; i++) {
// if data is 0, leave the background
if (imageData[i]) {
var row = Math.floor(i / width);
var col = i % width;
const row = Math.floor(i / width);
const col = i % width;
// if data is 2, choose spot color, if 1 choose foreground
cc.fillStyle = imageData[i] == 1 ? opts.color! : opts.spotcolor!;
cc.fillStyle = imageData[i] == 1 ? fullOpts.color! : fullOpts.spotcolor!;
cc.fillRect(col * opts.scale, row * opts.scale, opts.scale, opts.scale);
cc.fillRect(col * fullOpts.scale, row * fullOpts.scale, fullOpts.scale, fullOpts.scale);
}
}
return canvas;
}
export function createIcon(opts: BlockiesOpt) {
var canvas = document.createElement("canvas");
const canvas = document.createElement('canvas');
renderIcon(opts, canvas);

View file

@ -1,18 +1,17 @@
import { formatUnits } from 'viem'
import { formatUnits } from 'viem';
export function getAddressShortName(address: string) {
if (!address) {
return "";
return '';
}
const addressBegin = address.substring(0, 6);
const addressEnd = address.substring(address.length - 4, address.length);
return addressBegin + "..." + addressEnd;
return addressBegin + '...' + addressEnd;
}
export function compactNumber(number: number) {
return Intl.NumberFormat("en-US", {
notation: "compact",
return Intl.NumberFormat('en-US', {
notation: 'compact',
// minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(number);
@ -21,44 +20,41 @@ export function compactNumber(number: number) {
export function formatBigNumber(number: bigint, decimals: number, digits: number = 5) {
let bigIntNumber = number;
if (!bigIntNumber) {
bigIntNumber = BigInt(0)
bigIntNumber = BigInt(0);
}
const formattedNumber = Number(formatUnits(bigIntNumber, decimals))
const formattedNumber = Number(formatUnits(bigIntNumber, decimals));
if (formattedNumber === 0) {
return "0"
return '0';
}
return formattedNumber.toFixed(digits)
return formattedNumber.toFixed(digits);
}
export function bigInt2Number(number: bigint, decimals: number) {
let bigIntNumber = number;
if (!bigIntNumber) {
bigIntNumber = BigInt(0)
bigIntNumber = BigInt(0);
}
const formattedNumber = Number(formatUnits(bigIntNumber, decimals))
return formattedNumber
const formattedNumber = Number(formatUnits(bigIntNumber, decimals));
return formattedNumber;
}
export function InsertCommaNumber(number: any) {
export function InsertCommaNumber(number: number) {
if (!number) {
return 0;
}
const formattedWithOptions = number.toLocaleString("en-US");
const formattedWithOptions = number.toLocaleString('en-US');
return formattedWithOptions;
}
export function formatBigIntDivision(nominator: bigint, denominator: bigint, digits: number = 2) {
export function formatBigIntDivision(nominator: bigint, denominator: bigint, _digits: number = 2) {
if (!nominator) {
return 0;
}
let display = nominator.toString();
const display = nominator.toString();
const decimal = (Number(denominator) / 10).toString().length;
let [integer, fraction] = [display.slice(0, display.length - decimal), display.slice(display.length - decimal)];
const [integer, fraction] = [display.slice(0, display.length - decimal), display.slice(display.length - decimal)];
// output type number
return Number(integer + "." + fraction);
return Number(integer + '.' + fraction);
}

View file

@ -1,23 +1,20 @@
export function info(text: string, data: any = null ){
export function info(text: string, data: unknown = null) {
if (data) {
console.log(`%c ${text}`, 'color: #17a2b8', data);
// console.log(`%c ${text}`, 'color: #17a2b8', data);
} else {
console.log(`%c ${text}`, 'color: #17a2b8');
// console.log(`%c ${text}`, 'color: #17a2b8');
}
}
export function contract(text: string, data: any = null ){
export function contract(text: string, data: unknown = null) {
if (data) {
console.log(`%c ${text}`, 'color: #8732a8', data);
// console.log(`%c ${text}`, 'color: #8732a8', data);
} else {
console.log(`%c ${text}`, 'color: #8732a8');
// console.log(`%c ${text}`, 'color: #8732a8');
}
}
export default {
info,
contract
}
contract,
};

File diff suppressed because it is too large Load diff

View file

@ -1,28 +1,24 @@
<template>
<chart-js
:snatchedPositions="snatchPositions.map((obj) => obj.id)"
:positions="activePositions"
:dark="darkTheme"
></chart-js>
<ChartJs :snatchedPositions="snatchPositions.map(obj => obj.id)" :positions="activePositions" :dark="darkTheme"></ChartJs>
</template>
<script setup lang="ts">
import ChartJs from "@/components/chart/ChartJs.vue";
import {bigInt2Number, formatBigIntDivision} from "@/utils/helper";
import { computed, ref } from "vue";
import { useStatCollection } from "@/composables/useStatCollection";
import { useStake } from "@/composables/useStake";
import { usePositions, type Position } from "@/composables/usePositions";
import { useDark } from "@/composables/useDark";
import ChartJs from '@/components/chart/ChartJs.vue';
import { bigInt2Number, formatBigIntDivision } from '@/utils/helper';
import { computed, ref } from 'vue';
import { useStatCollection } from '@/composables/useStatCollection';
import { useStake } from '@/composables/useStake';
import { usePositions, type Position } from '@/composables/usePositions';
import { useDark } from '@/composables/useDark';
const { darkTheme } = useDark();
const { activePositions, myActivePositions, tresholdValue, myClosedPositions, createRandomPosition } = usePositions();
const { activePositions } = usePositions();
const ignoreOwner = ref(false);
const taxRate = ref<number>(1.0);
const minStakeAmount = computed(() => {
console.log("minStake", minStake.value);
// console.log("minStake", minStake.value);
return formatBigIntDivision(minStake.value, 10n ** 18n);
});
@ -45,7 +41,7 @@ const snatchPositions = computed(() => {
bigInt2Number(statCollection.outstandingStake, 18) +
stake.stakingAmountNumber -
bigInt2Number(statCollection.kraikenTotalSupply, 18) * 0.2;
console.log("difference", difference);
// console.log("difference", difference);
//Division ohne Rest, um zu schauen wie viele Positionen gesnatched werden könnten
const snatchAblePositionsCount = Math.floor(difference / minStakeAmount.value);
@ -64,5 +60,4 @@ const snatchPositions = computed(() => {
return [];
});
</script>

View file

@ -1,7 +1,5 @@
<script setup lang="ts">
</script>
<script setup lang="ts"></script>
<template>
<main>
</main>
<main></main>
</template>

View file

@ -7,8 +7,8 @@
<div class="error-box" v-if="error">
{{ error }}
</div>
<f-input type="password" @input="error = ''" v-model="inputPassword" label="Password" @keyup.enter="login"></f-input>
<f-button size="large" @click="login">Login</f-button>
<FInput type="password" @input="error = ''" v-model="inputPassword" label="Password" @keyup.enter="login"></FInput>
<FButton size="large" @click="login">Login</FButton>
</div>
</div>
</div>
@ -16,22 +16,22 @@
</template>
<script setup lang="ts">
import FButton from "@/components/fcomponents/FButton.vue";
import FInput from "@/components/fcomponents/FInput.vue";
import { onMounted, ref } from "vue";
import {useRouter} from "vue-router"
import FButton from '@/components/fcomponents/FButton.vue';
import FInput from '@/components/fcomponents/FInput.vue';
import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
const router = useRouter();
const error = ref("")
const passwords = ref(["lobsterDao", "test123", "lobster-x010syqe?412!"]);
const inputPassword = ref("")
const error = ref('');
const passwords = ref(['lobsterDao', 'test123', 'lobster-x010syqe?412!']);
const inputPassword = ref('');
function login() {
if (passwords.value.includes(inputPassword.value)) {
localStorage.setItem('authentificated', "true");
router.push("/")
localStorage.setItem('authentificated', 'true');
router.push('/');
return true;
} else {
error.value = "wrong password"
error.value = 'wrong password';
// Token nach erfolgreichem Login speichern
// Beim Logout Token entfernen
return false;
@ -39,10 +39,10 @@ function login(){
}
onMounted(() => {
if(localStorage.getItem('authentificated') === "true"){
router.push("/")
if (localStorage.getItem('authentificated') === 'true') {
router.push('/');
}
})
});
</script>
<style lang="sass">

View file

@ -5,52 +5,44 @@
Staking Dashboard
<IconInfo size="20px">
<template #text>
Stake your $KRK and claim owner slots. Owner slots give a return on every token buy that
increases the liquidity of KrAIken. Owner slots are limited to 20,000 slots in total. To enable
everyone and anyone to join staking is protected by a Harberger Tax mechanism.
Stake your $KRK and claim owner slots. Owner slots give a return on every token buy that increases the liquidity of KrAIken.
Owner slots are limited to 20,000 slots in total. To enable everyone and anyone to join staking is protected by a Harberger
Tax mechanism.
</template>
</IconInfo>
</h3>
<div class="stake-view-body">
<chart-complete></chart-complete>
<ChartComplete></ChartComplete>
<div class="hold-stake-wrapper">
<f-card class="inner-border">
<template v-if="!isChainSupported">
Chain not supported
</template>
<FCard class="inner-border">
<template v-if="!isChainSupported"> Chain not supported </template>
<template v-else-if="status !== 'connected'">
<f-button @click="showPanel = true" size="large" block
>Connect Wallet</f-button
>
<FButton @click="showPanel = true" size="large" block>Connect Wallet</FButton>
</template>
<template v-else>
<stake-holder ></stake-holder>
<StakeHolder></StakeHolder>
</template>
</f-card>
</FCard>
</div>
</div>
</div>
<div class="statistics-wrapper">
<h3>Statistics</h3>
<div class="statistics-outputs-wrapper">
<stats-output headline="Average Slot Tax" :price="`${averageTaxRate.toFixed(2)} %`"></stats-output>
<stats-output
<StatsOutput headline="Average Slot Tax" :price="`${averageTaxRate.toFixed(2)} %`"></StatsOutput>
<StatsOutput
headline="Claimed Owner Slots"
:price="`${InsertCommaNumber(stats.claimedSlots)} / ${InsertCommaNumber(stats.maxSlots)}`"
></stats-output>
<stats-output
headline="Total Supply Change / 7d"
:price="`+ ${stats.totalSupplyChange7d} $KRK`"
></stats-output>
<stats-output headline="Inflation / 7d" :price="`+${stats.inflation7d}%`"></stats-output>
></StatsOutput>
<StatsOutput headline="Total Supply Change / 7d" :price="`+ ${stats.totalSupplyChange7d} $KRK`"></StatsOutput>
<StatsOutput headline="Inflation / 7d" :price="`+${stats.inflation7d}%`"></StatsOutput>
</div>
</div>
<div class="active-positions-wrapper">
<h3>Active Positions</h3>
<div class="active-positions-list">
<collapse-active
<CollapseActive
v-for="position in myActivePositions"
:taxRate="position.taxRatePercentage"
:amount="position.amount"
@ -58,7 +50,7 @@
:id="position.positionId"
:position="position"
:key="position.id"
></collapse-active>
></CollapseActive>
</div>
</div>
<!-- <f-button @click="getGraphData">graphql test</f-button>
@ -69,44 +61,44 @@
</template>
<script setup lang="ts">
import StakeHolder from "@/components/StakeHolder.vue";
import ChartComplete from "@/components/chart/ChartComplete.vue";
import StatsOutput from "@/components/StatsOutput.vue";
import CollapseActive from "@/components/collapse/CollapseActive.vue";
import { onMounted, computed, inject } from "vue";
import { useStatCollection } from "@/composables/useStatCollection";
import { useChains, useAccount } from "@wagmi/vue";
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";
import StakeHolder from '@/components/StakeHolder.vue';
import ChartComplete from '@/components/chart/ChartComplete.vue';
import StatsOutput from '@/components/StatsOutput.vue';
import CollapseActive from '@/components/collapse/CollapseActive.vue';
import { onMounted, computed, inject } from 'vue';
import { useStatCollection } from '@/composables/useStatCollection';
import { useChains, useAccount } from '@wagmi/vue';
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";
import { usePositions } from '@/composables/usePositions';
const { status } = useAccount();
const showPanel = inject("showPanel");
const showPanel = inject('showPanel');
import { compactNumber, InsertCommaNumber } from "@/utils/helper";
import { InsertCommaNumber } from '@/utils/helper';
const { myActivePositions, tresholdValue, activePositions } = usePositions();
const stats = useStatCollection();
const wallet = useWallet();
const chains = useChains();
function calculateAverageTaxRate(data: any): number {
console.log("data", data);
function calculateAverageTaxRate(data: Array<{ taxRate: number | string }>): number {
// console.log("data", data);
if (data.length === 0) {
return 0;
}
const totalTaxRate = data.reduce((sum: any, entry: any) => sum + parseFloat(entry.taxRate), 0);
const totalTaxRate = data.reduce((sum: number, entry: { taxRate: number | string }) => sum + parseFloat(String(entry.taxRate)), 0);
const averageTaxRate = totalTaxRate / data.length;
return averageTaxRate * 100;
}
const averageTaxRate = computed(() => calculateAverageTaxRate(activePositions.value));
const supportedChainIds = computed(() => chains.value.map((chain) => chain.id));
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 () => {});

View file

@ -1,21 +1,21 @@
import { http, createConfig, createStorage } from "@wagmi/vue";
import { baseSepolia } from "@wagmi/vue/chains";
import { coinbaseWallet, walletConnect } from "@wagmi/vue/connectors";
import { defineChain } from "viem";
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 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 },
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: "" },
default: { name: 'Local Explorer', url: '' },
},
testnet: true,
});
@ -26,19 +26,19 @@ export const config = createConfig({
connectors: [
walletConnect({
projectId: "d8e5ecb0353c02e21d4c0867d4473ac5",
projectId: 'd8e5ecb0353c02e21d4c0867d4473ac5',
metadata: {
name: "Kraiken",
description: "Connect your wallet with Kraiken",
url: "https://kraiken.eth.limo",
icons: [""],
name: 'Kraiken',
description: 'Connect your wallet with Kraiken',
url: 'https://kraiken.eth.limo',
icons: [''],
},
}),
coinbaseWallet({
appName: "Kraiken",
appName: 'Kraiken',
darkMode: true,
preference: {
options: "all",
options: 'all',
telemetry: false,
},
}),
@ -49,6 +49,6 @@ export const config = createConfig({
},
});
if (typeof window !== "undefined" && config.state.chainId !== kraikenLocalFork.id) {
config.setState((state) => ({ ...state, chainId: kraikenLocalFork.id }));
if (typeof window !== 'undefined' && config.state.chainId !== kraikenLocalFork.id) {
config.setState(state => ({ ...state, chainId: kraikenLocalFork.id }));
}