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 #!/usr/bin/env sh
cd kraiken-lib || exit 1 set -e
npx lint-staged
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": { "devDependencies": {
"@playwright/test": "^1.55.1", "@playwright/test": "^1.55.1",
"playwright-mcp": "^0.0.12" "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" /> /// <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", "build-only": "vite build",
"type-check": "vue-tsc --build", "type-check": "vue-tsc --build",
"subtree": "git subtree push --prefix dist origin gh-pages", "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": { "dependencies": {
"@tanstack/vue-query": "^5.64.2", "@tanstack/vue-query": "^5.64.2",
@ -31,13 +36,23 @@
}, },
"devDependencies": { "devDependencies": {
"@iconify/vue": "^4.3.0", "@iconify/vue": "^4.3.0",
"@stylistic/eslint-plugin": "^2.8.0",
"@tsconfig/node22": "^22.0.0", "@tsconfig/node22": "^22.0.0",
"@types/node": "^22.10.7", "@types/node": "^22.10.7",
"@typescript-eslint/eslint-plugin": "^8.45.0",
"@typescript-eslint/parser": "^8.45.0",
"@vitejs/plugin-vue": "^5.2.1", "@vitejs/plugin-vue": "^5.2.1",
"@vue/eslint-config-typescript": "^14.6.0",
"@vue/tsconfig": "^0.7.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", "gh-pages": "^6.1.1",
"husky": "^9.1.7",
"jsdom": "^27.0.0", "jsdom": "^27.0.0",
"lint-staged": "^16.2.3",
"npm-run-all2": "^7.0.2", "npm-run-all2": "^7.0.2",
"prettier": "^3.6.2",
"typescript": "~5.7.3", "typescript": "~5.7.3",
"vite": "^6.0.11", "vite": "^6.0.11",
"vite-plugin-vue-devtools": "^7.7.0", "vite-plugin-vue-devtools": "^7.7.0",

View file

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

View file

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

View file

@ -1,20 +1,20 @@
<template> <template>
<div class="stats-output" :styles="styles"> <div class="stats-output" :styles="styles">
<f-card> <FCard>
<h6>{{ props.headline }}</h6> <h6>{{ props.headline }}</h6>
<f-output :name="props.name" :price="props.price"> <FOutput :name="props.name" :price="props.price">
<template #price> <template #price>
<slot name="price"></slot> <slot name="price"></slot>
</template> </template>
</f-output> </FOutput>
</f-card> </FCard>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from "vue"; import { computed } from 'vue';
import FCard from "@/components/fcomponents/FCard.vue" import FCard from '@/components/fcomponents/FCard.vue';
import FOutput from "@/components/fcomponents/FOutput.vue" import FOutput from '@/components/fcomponents/FOutput.vue';
interface Props { interface Props {
name?: string; name?: string;
headline: string; headline: string;
@ -26,7 +26,10 @@ interface Styles {
width?: string; width?: string;
} }
const props = withDefaults(defineProps<Props>(), {}); const props = withDefaults(defineProps<Props>(), {
name: '',
width: undefined,
});
const styles = computed(() => { const styles = computed(() => {
const returnObject: Styles = {}; 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,30 +1,24 @@
<template> <template>
<chart-js <ChartJs :snatchedPositions="snatchPositions.map(obj => obj.id)" :positions="activePositions" :dark="darkTheme"></ChartJs>
:snatchedPositions="snatchPositions.map((obj) => obj.id)"
:positions="activePositions"
:dark="darkTheme"
></chart-js>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import ChartJs from "@/components/chart/ChartJs.vue"; import ChartJs from '@/components/chart/ChartJs.vue';
import {bigInt2Number, formatBigIntDivision} from "@/utils/helper"; import { bigInt2Number, formatBigIntDivision } from '@/utils/helper';
import { computed, ref } from "vue"; import { computed, ref } from 'vue';
import { useStatCollection } from "@/composables/useStatCollection"; import { useStatCollection } from '@/composables/useStatCollection';
import { useStake } from "@/composables/useStake"; import { useStake } from '@/composables/useStake';
import { usePositions, type Position } from "@/composables/usePositions"; import { usePositions, type Position } from '@/composables/usePositions';
import { useDark } from "@/composables/useDark"; import { useDark } from '@/composables/useDark';
const { darkTheme } = useDark(); const { darkTheme } = useDark();
const { activePositions, myActivePositions, tresholdValue, myClosedPositions, createRandomPosition } = usePositions(); const { activePositions } = usePositions();
const ignoreOwner = ref(false); const ignoreOwner = ref(false);
const taxRate = ref<number>(1.0); const taxRate = ref<number>(1.0);
const minStakeAmount = computed(() => { const minStakeAmount = computed(() => {
console.log("minStake", minStake.value); return formatBigIntDivision(minStake.value, 10n ** 18n);
return formatBigIntDivision(minStake.value, 10n ** 18n);
}); });
const stakeAbleHarbAmount = computed(() => statCollection.kraikenTotalSupply / 5n); const stakeAbleHarbAmount = computed(() => statCollection.kraikenTotalSupply / 5n);
@ -34,24 +28,23 @@ const minStake = computed(() => stakeAbleHarbAmount.value / 600n);
const stake = useStake(); const stake = useStake();
const statCollection = useStatCollection(); const statCollection = useStatCollection();
const snatchPositions = computed(() => { const snatchPositions = computed(() => {
if ( if (
bigInt2Number(statCollection.outstandingStake, 18) + stake.stakingAmountNumber <= bigInt2Number(statCollection.outstandingStake, 18) + stake.stakingAmountNumber <=
bigInt2Number(statCollection.kraikenTotalSupply, 18) * 0.2 bigInt2Number(statCollection.kraikenTotalSupply, 18) * 0.2
) { ) {
return []; return [];
} }
//Differenz aus outstandingSupply und totalSupply bestimmen, wie viel HARB kann zum Snatch verwendet werden //Differenz aus outstandingSupply und totalSupply bestimmen, wie viel HARB kann zum Snatch verwendet werden
const difference = const difference =
bigInt2Number(statCollection.outstandingStake, 18) + bigInt2Number(statCollection.outstandingStake, 18) +
stake.stakingAmountNumber - stake.stakingAmountNumber -
bigInt2Number(statCollection.kraikenTotalSupply, 18) * 0.2; bigInt2Number(statCollection.kraikenTotalSupply, 18) * 0.2;
console.log("difference", difference);
//Division ohne Rest, um zu schauen wie viele Positionen gesnatched werden könnten //Division ohne Rest, um zu schauen wie viele Positionen gesnatched werden könnten
const snatchAblePositionsCount = Math.floor(difference / minStakeAmount.value); const snatchAblePositionsCount = Math.floor(difference / minStakeAmount.value);
//wenn mehr als 0 Positionen gesnatched werden könnten, wird geschaut wie viele Positionen in Frage kommen //wenn mehr als 0 Positionen gesnatched werden könnten, wird geschaut wie viele Positionen in Frage kommen
if (snatchAblePositionsCount > 0) { if (snatchAblePositionsCount > 0) {
const snatchAblePositions = activePositions.value.filter((obj: Position) => { const snatchAblePositions = activePositions.value.filter((obj: Position) => {
if (ignoreOwner.value) { if (ignoreOwner.value) {
return obj.taxRatePercentage < taxRate.value; return obj.taxRatePercentage < taxRate.value;
@ -60,9 +53,8 @@ if (snatchAblePositionsCount > 0) {
}); });
const slicedArray = snatchAblePositions.slice(0, snatchAblePositionsCount); const slicedArray = snatchAblePositions.slice(0, snatchAblePositionsCount);
return slicedArray; return slicedArray;
} }
return []; return [];
}); });
</script> </script>

View file

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

View file

@ -1,11 +1,7 @@
<template> <template>
<button class="chart-button"> <button class="chart-button">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path <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" />
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 <path
class="stroke" class="stroke"
d="M16.6191 10.5273L16.6191 15.7266C16.6191 16.0679 16.3424 16.3447 16.001 16.3447L10.8018 16.3447" 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> <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> <template v-slot:header>
<div class="collapse-header"> <div class="collapse-header">
<div class="collapse-header-row1"> <div class="collapse-header-row1">
<div><span class="subheader2">Tax</span> {{ props.taxRate }} %</div> <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"> <div class="position-id">
<span class="subheader2">ID</span> <span class="number-small">{{ props.id }}</span> <span class="subheader2">ID</span> <span class="number-small">{{ props.id }}</span>
</div> </div>
@ -17,7 +17,7 @@
</div> </div>
</div> </div>
<div class="tags-list"> <div class="tags-list">
<f-tag v-if="tag">{{ tag }}</f-tag> <FTag v-if="tag">{{ tag }}</FTag>
</div> </div>
</div> </div>
<!-- <div class="collapse-amount"> <!-- <div class="collapse-amount">
@ -44,66 +44,47 @@
</div> </div>
<div class="collapsed-body--actions"> <div class="collapsed-body--actions">
<div :class="{ 'collapse-menu-open': showTaxMenu }"> <div :class="{ 'collapse-menu-open': showTaxMenu }">
<f-button size="small" dense block outlined v-if="adjustTaxRate.state === 'SignTransaction'" <FButton size="small" dense block outlined v-if="adjustTaxRate.state === 'SignTransaction'">Sign Transaction ...</FButton>
>Sign Transaction ...</f-button <FButton size="small" dense outlined block v-else-if="adjustTaxRate.state === 'Waiting'" @click="unstakePosition"
>Waiting ...</FButton
> >
<f-button <FButton size="small" dense block v-else-if="adjustTaxRate.state === 'Action' && !showTaxMenu" @click="showTaxMenu = true"
size="small" >Adjust Tax Rate</FButton
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
> >
<template v-else> <template v-else>
<div class="collapse-menu-input"> <div class="collapse-menu-input">
<f-select :items="filteredTaxRates" v-model="newTaxRate"> </f-select> <FSelect :items="filteredTaxRates" v-model="newTaxRate"> </FSelect>
</div> </div>
<div> <div>
<f-button size="small" dense @click="changeTax(props.id, newTaxRate)">Confirm</f-button> <FButton size="small" dense @click="changeTax(props.id, newTaxRate)">Confirm</FButton>
<f-button size="small" dense outlined @click="showTaxMenu = false">Cancel</f-button> <FButton size="small" dense outlined @click="showTaxMenu = false">Cancel</FButton>
</div> </div>
</template> </template>
</div> </div>
<div></div> <div></div>
<div> <div>
<f-button size="small" dense block outlined v-if="unstake.state === 'SignTransaction'" <FButton size="small" dense block outlined v-if="unstake.state === 'SignTransaction'">Sign Transaction ...</FButton>
>Sign Transaction ...</f-button <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>
<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
>
</div> </div>
</div> </div>
</f-collapse> </FCollapse>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import FButton from "@/components/fcomponents/FButton.vue"; import FButton from '@/components/fcomponents/FButton.vue';
import FTag from "@/components/fcomponents/FTag.vue"; import FTag from '@/components/fcomponents/FTag.vue';
import FSelect from "@/components/fcomponents/FSelect.vue"; import FSelect from '@/components/fcomponents/FSelect.vue';
import FCollapse from "@/components/fcomponents/FCollapse.vue"; import FCollapse from '@/components/fcomponents/FCollapse.vue';
import { compactNumber, formatBigNumber } from "@/utils/helper"; import { compactNumber, formatBigNumber } from '@/utils/helper';
import { useUnstake } from "@/composables/useUnstake"; import { useUnstake } from '@/composables/useUnstake';
import { useAdjustTaxRate } from "@/composables/useAdjustTaxRates"; import { useAdjustTaxRate } from '@/composables/useAdjustTaxRates';
import { computed, ref, onMounted } from "vue"; import { computed, ref, onMounted } from 'vue';
import { getTaxDue, payTax } from "@/contracts/stake"; import { getTaxDue, payTax } from '@/contracts/stake';
import { type Position, loadPositions } from "@/composables/usePositions"; import { type Position, loadPositions } from '@/composables/usePositions';
import { useStatCollection } from "@/composables/useStatCollection"; import { useStatCollection } from '@/composables/useStatCollection';
import { formatUnits } from "viem"; import { formatUnits } from 'viem';
const unstake = useUnstake(); const unstake = useUnstake();
const adjustTaxRate = useAdjustTaxRate(); const adjustTaxRate = useAdjustTaxRate();
@ -126,9 +107,9 @@ const loading = ref<boolean>(false);
const tag = computed(() => { const tag = computed(() => {
if (props.taxRate < props.treshold) { if (props.taxRate < props.treshold) {
return "Low Tax!"; return 'Low Tax!';
} }
return ""; return '';
}); });
const total = computed(() => props.amount + profit.value! + -taxPaidGes.value!); const total = computed(() => props.amount + profit.value! + -taxPaidGes.value!);
@ -139,48 +120,37 @@ async function changeTax(id: bigint, newTaxRate: number) {
} }
async function unstakePosition() { async function unstakePosition() {
console.log(props.id);
await unstake.exitPosition(props.id); await unstake.exitPosition(props.id);
loading.value = true; loading.value = true;
await new Promise((resolve) => setTimeout(resolve, 5000)); await new Promise(resolve => setTimeout(resolve, 5000));
console.log("loadPositions begin");
await loadPositions(); await loadPositions();
console.log("loadPositions end");
loading.value = false; loading.value = false;
} }
async function loadActivePositionData() { async function loadActivePositionData() {
console.log("loadActivePositionData", props.position);
//loadTaxDue //loadTaxDue
taxDue.value = await getTaxDue(props.id); taxDue.value = await getTaxDue(props.id);
console.log("taxDue", taxDue.value);
taxPaidGes.value = formatBigNumber(taxDue.value + BigInt(props.position.taxPaid), 18); taxPaidGes.value = formatBigNumber(taxDue.value + BigInt(props.position.taxPaid), 18);
console.log("loadActivePositionData", taxPaidGes.value);
//loadTotalSupply //loadTotalSupply
const multiplier = const multiplier = Number(formatUnits(props.position.totalSupplyInit, 18)) / Number(formatUnits(statCollection.kraikenTotalSupply, 18));
Number(formatUnits(props.position.totalSupplyInit, 18)) /
Number(formatUnits(statCollection.kraikenTotalSupply, 18));
console.log("props.position.totalSupplyInit", props.position.totalSupplyInit);
console.log("multiplier", multiplier);
profit.value = profit.value =
Number(formatUnits(statCollection.kraikenTotalSupply, 18)) * multiplier - Number(formatUnits(statCollection.kraikenTotalSupply, 18)) * multiplier - Number(formatUnits(statCollection.kraikenTotalSupply, 18));
Number(formatUnits(statCollection.kraikenTotalSupply, 18));
} }
onMounted(() => { 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) { if (taxRate) {
newTaxRate.value = taxRate.year; 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> </script>
<style lang="sass"> <style lang="sass">

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,14 +1,7 @@
<template> <template>
<div class="f-tabs" ref="tabsRef"> <div class="f-tabs" ref="tabsRef">
<div class="f-tabs__header" ref="headerRef"> <div class="f-tabs__header" ref="headerRef">
<div <div class="f-tab" ref="tabsRef1" :id="`f-tab-${tab.uid}`" v-for="tab in tabs" :key="tab.uid" @click="setActive(tab)">
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> <h5>{{ tab.label }}</h5>
</div> </div>
</div> </div>
@ -19,40 +12,49 @@
</template> </template>
<script setup lang="ts"> <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({ defineOptions({
inheritAttrs: false, inheritAttrs: false,
}); });
const props = defineProps({ interface Props {
modelValue: { modelValue?: string | null;
type: String, }
required: false, const props = withDefaults(defineProps<Props>(), {
default: null, 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 tabsRef = ref<HTMLElement>();
const tabsRef1 = ref<HTMLElement>(); const tabsRef1 = ref<HTMLElement>();
const headerRef = ref<HTMLElement>(); const headerRef = ref<HTMLElement>();
const tabsArray = ref<Array<HTMLElement>>(); const _tabsArray = ref<Array<HTMLElement>>();
const tabsComponents = ref<Array<any>>(); const _tabsComponents = ref<Array<unknown>>();
const content = ref<any>(); const _content = ref<unknown>();
const target = ref<any>(); const _target = ref<unknown>();
const slots = ref<any>(); const _slots = ref<unknown>();
const value = ref<any>(); const value = ref<unknown>();
const tabsTest = ref<any>(); const tabsTest = ref<unknown>();
const tabs = ref<Array<any>>([]); const tabs = ref<Array<Tab>>([]);
provide("value", value); provide('value', value);
provide("addTab", (tab: any) => { provide('addTab', (tab: Tab) => {
tabs.value.push(tab); tabs.value.push(tab);
}); });
provide("updateTab", (tab: any) => { provide('updateTab', (tab: Tab) => {
let changedTabIndex = tabs.value.findIndex((obj) => obj.uid === tab.uid); const changedTabIndex = tabs.value.findIndex(obj => obj.uid === tab.uid);
if (changedTabIndex > -1) { if (changedTabIndex > -1) {
tabs.value[changedTabIndex] = tab; tabs.value[changedTabIndex] = tab;
} }
@ -61,28 +63,33 @@ provide("updateTab", (tab: any) => {
onMounted(() => { onMounted(() => {
nextTick(() => { nextTick(() => {
if(props.modelValue){ 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); setActive(tab);
}
} else { } else {
setActive(tabs.value[0]); const firstTab = tabs.value[0];
if (firstTab) {
setActive(firstTab);
}
} }
}); });
}); });
function setActive(tab: any) { function setActive(tab: Tab) {
nextTick(() => { nextTick(() => {
const tabElement: any = headerRef.value?.querySelector(`#f-tab-${tab.uid}`); const tabElement = headerRef.value?.querySelector(`#f-tab-${tab.uid}`) as HTMLElement | null;
var array = Array.prototype.slice.call(tabsRef.value?.children[0].children); const array = Array.prototype.slice.call(tabsRef.value?.children[0].children);
array.forEach((element: HTMLElement) => { 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; value.value = tab.name;
emit("update:modelValue", tab.name); emit('update:modelValue', tab.name);
}); });
} }
</script> </script>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -8,15 +8,19 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { inject, watchEffect } from "vue"; import { inject } from 'vue';
const props = defineProps({ interface Props {
modelValue: Boolean, 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> </script>
<style lang="sass"> <style lang="sass">

View file

@ -13,104 +13,32 @@
style="enable-background: new 0 0 496 496" style="enable-background: new 0 0 496 496"
xml:space="preserve" xml:space="preserve"
> >
<rect <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" />
x="152.994" <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" />
y="58.921" <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" />
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 <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" />
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 <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" />
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 <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" />
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 <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" />
x="409.054" <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" />
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 <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 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" 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="240" width="16" height="72" />
<rect <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" />
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 y="240" width="72" height="16" />
<rect <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" />
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="240" y="424" width="16" height="72" />
<rect <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" />
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="424" y="240" width="72" height="16" />
<rect <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" />
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>
<svg <svg
version="1.1" version="1.1"
@ -137,14 +65,21 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const emit = defineEmits(["update:modelValue"]); interface Emits {
(event: 'update:modelValue', value: boolean): void;
}
const emit = defineEmits<Emits>();
const props = defineProps<{ const props = defineProps<{
modelValue: boolean; modelValue: boolean;
}>(); }>();
function toggleTheme(event: any) { function toggleTheme(event: Event) {
emit("update:modelValue", event.target.checked); const target = event.target as HTMLInputElement | null;
if (target) {
emit('update:modelValue', target.checked);
}
} }
</script> </script>

View file

@ -1,95 +1,95 @@
import { describe, it, expect, vi, beforeEach } from "vitest"; import { describe, it, expect, vi, beforeEach } from 'vitest';
import { defineAsyncComponent } from "vue"; import { mount } from '@vue/test-utils';
import { mount, flushPromises } from "@vue/test-utils"; import { defineComponent } from 'vue';
import SocialBadge from "./socialButton.vue"; import SocialBadge from './SocialButton.vue';
function mockIconComponent(name: string) {
const mockIconComponent = (name:string) => ({ return {
default: { __esModule: true,
default: defineComponent({
name, name,
template: `<svg class='${name.toLowerCase()}'></svg>`, template: `<svg class='${name.toLowerCase()}'></svg>`,
}, }),
}); };
}
vi.mock("../components/icons/IconDiscord.vue", () => mockIconComponent("IconDiscord")); vi.mock('@/components/icons/IconDiscord.vue', () => mockIconComponent('IconDiscord'));
vi.mock("../components/icons/IconTwitter.vue", () => mockIconComponent("IconTwitter")); vi.mock('@/components/icons/IconTwitter.vue', () => mockIconComponent('IconTwitter'));
vi.mock("../components/icons/IconTelegram.vue", () => mockIconComponent("IconTelegram")); vi.mock('@/components/icons/IconTelegram.vue', () => mockIconComponent('IconTelegram'));
describe('SocialBadge.vue', () => {
describe("SocialBadge.vue", () => {
let wrapper; let wrapper;
beforeEach(() => { beforeEach(() => {
wrapper = null; wrapper = null;
}); });
it("renders the correct link", () => { it('renders the correct link', () => {
wrapper = mount(SocialBadge, { wrapper = mount(SocialBadge, {
props: { 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.exists()).toBe(true);
expect(link.attributes("href")).toBe("https://example.com"); expect(link.attributes('href')).toBe('https://example.com');
expect(link.attributes("target")).toBe("_blank"); 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, { wrapper = mount(SocialBadge, {
props: { props: {
dark: false, dark: false,
}, },
}); });
const badge = wrapper.find(".social-badge"); const badge = wrapper.find('.social-badge');
expect(badge.attributes("style")).toContain("color: black"); expect(badge.attributes('style')).toContain('color: black');
expect(badge.attributes("style")).toContain("border-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, { wrapper = mount(SocialBadge, {
props: { props: {
dark: true, dark: true,
}, },
}); });
const badge = wrapper.find(".social-badge"); const badge = wrapper.find('.social-badge');
expect(badge.attributes("style")).toContain("color: white"); expect(badge.attributes('style')).toContain('color: white');
expect(badge.attributes("style")).toContain("border-color: white"); expect(badge.attributes('style')).toContain('border-color: white');
}); });
// it("renders the correct icon based on the type prop", async () => { // it("renders the correct icon based on the type prop", async () => {
// wrapper = mount(SocialBadge, { // wrapper = mount(SocialBadge, {
// props: { // props: {
// type: "discord", // type: "discord",
// }, // },
// }); // });
// await flushPromises(); // await flushPromises();
// const current = wrapper.getCurrentComponent()?.setupState?.img.__asyncResolved // const current = wrapper.getCurrentComponent()?.setupState?.img.__asyncResolved
// console.log("current", current.default.name); // console.log("current", current.default.name);
// expect(current.default.name).toBe("IconDiscord"); // expect(current.default.name).toBe("IconDiscord");
// // expect(icon.exists()).toBe(true); // // 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, { wrapper = mount(SocialBadge, {
props: { props: {
type: "unsupported", type: 'unsupported',
}, },
}); });
await wrapper.vm.$nextTick(); 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); 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); wrapper = mount(SocialBadge);
expect(wrapper.exists()).toBe(true); 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 { describe, it, expect, vi, beforeEach } from 'vitest';
import { ref, nextTick } from 'vue' import { ref, nextTick, type Ref } from 'vue';
import { useSnatchSelection } from '../useSnatchSelection' import { useSnatchSelection } from '../useSnatchSelection';
import { usePositions } from '../usePositions' import { usePositions, type Position } from '../usePositions';
import { useStake } from '../useStake' import { useStake } from '../useStake';
import { useWallet } from '../useWallet' import { useWallet } from '../useWallet';
import { useStatCollection } from '../useStatCollection' import { useStatCollection } from '../useStatCollection';
import { useAdjustTaxRate } from '../useAdjustTaxRates' 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 // Mock all composables
vi.mock('../usePositions', () => ({ vi.mock('../usePositions', () => ({
usePositions: vi.fn() usePositions: vi.fn(),
})) }));
vi.mock('../useStake', () => ({ vi.mock('../useStake', () => ({
useStake: vi.fn() useStake: vi.fn(),
})) }));
vi.mock('../useWallet', () => ({ vi.mock('../useWallet', () => ({
useWallet: vi.fn() useWallet: vi.fn(),
})) }));
vi.mock('../useStatCollection', () => ({ vi.mock('../useStatCollection', () => ({
useStatCollection: vi.fn() useStatCollection: vi.fn(),
})) }));
vi.mock('../useAdjustTaxRates', () => ({ vi.mock('../useAdjustTaxRates', () => ({
useAdjustTaxRate: vi.fn() useAdjustTaxRate: vi.fn(),
})) }));
vi.mock('kraiken-lib/staking', () => ({ vi.mock('kraiken-lib/staking', () => ({
calculateSnatchShortfall: vi.fn((outstandingStake, stakingShares, stakeTotalSupply) => { calculateSnatchShortfall: vi.fn((outstandingStake, stakingShares, _stakeTotalSupply) => {
return stakingShares > outstandingStake ? 0n : outstandingStake - stakingShares return stakingShares > outstandingStake ? 0n : outstandingStake - stakingShares;
}) }),
})) }));
vi.mock('kraiken-lib/snatch', () => ({ vi.mock('kraiken-lib/snatch', () => ({
selectSnatchPositions: vi.fn((candidates, options) => { selectSnatchPositions: vi.fn((candidates, options) => {
if (candidates.length === 0) { if (candidates.length === 0) {
return { selected: [], remainingShortfall: options.shortfallShares } return { selected: [], remainingShortfall: options.shortfallShares };
} }
return { return {
selected: candidates, selected: candidates,
remainingShortfall: 0n, 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', () => { describe('useSnatchSelection', () => {
beforeEach(() => { beforeEach(() => {
// Reset all mocks // Reset all mocks
vi.clearAllMocks() vi.clearAllMocks();
// Setup default mock values with proper refs // Setup default mock values with proper refs
vi.mocked(usePositions).mockReturnValue({ vi.mocked(usePositions).mockReturnValue({
activePositions: ref([]) activePositions: ref([]),
} as any) } as MockPositionsReturn);
vi.mocked(useStake).mockReturnValue({ vi.mocked(useStake).mockReturnValue({
stakingAmountShares: 0n, stakingAmountShares: 0n,
taxRate: 1.0 taxRate: 1.0,
} as any) } as MockStakeReturn);
vi.mocked(useWallet).mockReturnValue({ vi.mocked(useWallet).mockReturnValue({
account: { address: '0x123' } account: { address: '0x123' },
} as any) } as MockWalletReturn);
// Mock with realistic values for local computation // Mock with realistic values for local computation
// stakeTotalSupply is typically 10^(18+7) = 10^25 from contract // stakeTotalSupply is typically 10^(18+7) = 10^25 from contract
@ -73,40 +96,40 @@ describe('useSnatchSelection', () => {
vi.mocked(useStatCollection).mockReturnValue({ vi.mocked(useStatCollection).mockReturnValue({
stakeTotalSupply: 10000000000000000000000000n, // 10^25 stakeTotalSupply: 10000000000000000000000000n, // 10^25
kraikenTotalSupply: 10000000000000000000000000n, // 10^25 kraikenTotalSupply: 10000000000000000000000000n, // 10^25
outstandingStake: 500n outstandingStake: 500n,
} as any) } as MockStatCollectionReturn);
vi.mocked(useAdjustTaxRate).mockReturnValue({ vi.mocked(useAdjustTaxRate).mockReturnValue({
taxRates: [{ year: 1 }] taxRates: [{ year: 1 }],
} as any) } as MockAdjustTaxRateReturn);
}) });
it('should initialize with empty snatchable positions', () => { it('should initialize with empty snatchable positions', () => {
const { snatchablePositions } = useSnatchSelection() const { snatchablePositions } = useSnatchSelection();
expect(snatchablePositions.value).toEqual([]) expect(snatchablePositions.value).toEqual([]);
}) });
it('should handle no active positions', () => { it('should handle no active positions', () => {
const { snatchablePositions, floorTax } = useSnatchSelection() const { snatchablePositions, floorTax } = useSnatchSelection();
expect(snatchablePositions.value).toEqual([]) expect(snatchablePositions.value).toEqual([]);
expect(floorTax.value).toBe(1) expect(floorTax.value).toBe(1);
}) });
it('should handle no shortfall', () => { it('should handle no shortfall', () => {
vi.mocked(useStatCollection).mockReturnValue({ vi.mocked(useStatCollection).mockReturnValue({
stakeTotalSupply: 1000n, stakeTotalSupply: 1000n,
outstandingStake: 100n outstandingStake: 100n,
}) });
vi.mocked(useStake).mockReturnValue({ vi.mocked(useStake).mockReturnValue({
stakingAmountShares: 900n, stakingAmountShares: 900n,
taxRate: 1.0 taxRate: 1.0,
}) });
const { snatchablePositions, openPositionsAvailable } = useSnatchSelection() const { snatchablePositions, openPositionsAvailable } = useSnatchSelection();
expect(snatchablePositions.value).toEqual([]) expect(snatchablePositions.value).toEqual([]);
expect(openPositionsAvailable.value).toBe(true) expect(openPositionsAvailable.value).toBe(true);
}) });
it('should filter out positions with higher tax rate', async () => { it('should filter out positions with higher tax rate', async () => {
vi.mocked(usePositions).mockReturnValue({ vi.mocked(usePositions).mockReturnValue({
@ -117,23 +140,23 @@ describe('useSnatchSelection', () => {
harbDeposit: 100n, harbDeposit: 100n,
taxRate: 2.0, taxRate: 2.0,
taxRateIndex: 1, taxRateIndex: 1,
iAmOwner: false iAmOwner: false,
} },
]) ]) as Ref<Partial<Position>[]>,
} as any) } as MockPositionsReturn);
vi.mocked(useStake).mockReturnValue({ vi.mocked(useStake).mockReturnValue({
stakingAmountShares: 100n, stakingAmountShares: 100n,
taxRate: 1.0 taxRate: 1.0,
} as any) } as MockStakeReturn);
const { snatchablePositions } = useSnatchSelection() const { snatchablePositions } = useSnatchSelection();
// Wait for watchEffect to run // 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 () => { it('should filter out owned positions by default', async () => {
vi.mocked(usePositions).mockReturnValue({ vi.mocked(usePositions).mockReturnValue({
@ -144,15 +167,15 @@ describe('useSnatchSelection', () => {
harbDeposit: 100n, harbDeposit: 100n,
taxRate: 0.5, taxRate: 0.5,
taxRateIndex: 1, taxRateIndex: 1,
iAmOwner: true iAmOwner: true,
} },
]) ]) as Ref<Partial<Position>[]>,
} as any) } as MockPositionsReturn);
const { snatchablePositions } = useSnatchSelection() const { snatchablePositions } = useSnatchSelection();
await new Promise(resolve => setTimeout(resolve, 0)) await new Promise(resolve => setTimeout(resolve, 0));
expect(snatchablePositions.value).toEqual([]) expect(snatchablePositions.value).toEqual([]);
}) });
it('should include owned positions when demo mode is enabled', async () => { it('should include owned positions when demo mode is enabled', async () => {
const position = { const position = {
@ -161,32 +184,32 @@ describe('useSnatchSelection', () => {
harbDeposit: 100n, harbDeposit: 100n,
taxRate: 0.005, // 0.5% tax rate (less than maxTaxRate) taxRate: 0.005, // 0.5% tax rate (less than maxTaxRate)
taxRateIndex: 1, taxRateIndex: 1,
iAmOwner: true iAmOwner: true,
} };
vi.mocked(usePositions).mockReturnValue({ vi.mocked(usePositions).mockReturnValue({
activePositions: ref([position]) activePositions: ref([position]),
} as any) } as MockPositionsReturn);
vi.mocked(useStake).mockReturnValue({ vi.mocked(useStake).mockReturnValue({
stakingAmountShares: 100n, stakingAmountShares: 100n,
taxRate: 1.0 // Will be converted to 0.01 (1%) decimal taxRate: 1.0, // Will be converted to 0.01 (1%) decimal
} as any) } as MockStakeReturn);
// Need outstandingStake > stakingAmountShares to create shortfall // Need outstandingStake > stakingAmountShares to create shortfall
vi.mocked(useStatCollection).mockReturnValue({ vi.mocked(useStatCollection).mockReturnValue({
stakeTotalSupply: 10000000000000000000000000n, stakeTotalSupply: 10000000000000000000000000n,
kraikenTotalSupply: 10000000000000000000000000n, kraikenTotalSupply: 10000000000000000000000000n,
outstandingStake: 500n outstandingStake: 500n,
} as any) } as MockStatCollectionReturn);
const { snatchablePositions } = useSnatchSelection(true) const { snatchablePositions } = useSnatchSelection(true);
// Wait for watchEffect to run (no longer async) // 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 () => { it('should handle partial fills', async () => {
const position1 = { const position1 = {
@ -195,8 +218,8 @@ describe('useSnatchSelection', () => {
harbDeposit: 100n, harbDeposit: 100n,
taxRate: 0.005, // 0.5% tax rate taxRate: 0.005, // 0.5% tax rate
taxRateIndex: 1, taxRateIndex: 1,
iAmOwner: false iAmOwner: false,
} };
const position2 = { const position2 = {
positionId: 2n, positionId: 2n,
@ -204,32 +227,32 @@ describe('useSnatchSelection', () => {
harbDeposit: 200n, harbDeposit: 200n,
taxRate: 0.006, // 0.6% tax rate taxRate: 0.006, // 0.6% tax rate
taxRateIndex: 2, taxRateIndex: 2,
iAmOwner: false iAmOwner: false,
} };
vi.mocked(usePositions).mockReturnValue({ vi.mocked(usePositions).mockReturnValue({
activePositions: ref([position1, position2]) activePositions: ref([position1, position2]) as Ref<Position[]>,
} as any) } as MockPositionsReturn);
vi.mocked(useStake).mockReturnValue({ vi.mocked(useStake).mockReturnValue({
stakingAmountShares: 150n, stakingAmountShares: 150n,
taxRate: 1.0 // Will be converted to 0.01 (1%) decimal taxRate: 1.0, // Will be converted to 0.01 (1%) decimal
} as any) } as MockStakeReturn);
// Need outstandingStake > stakingAmountShares to create shortfall // Need outstandingStake > stakingAmountShares to create shortfall
vi.mocked(useStatCollection).mockReturnValue({ vi.mocked(useStatCollection).mockReturnValue({
stakeTotalSupply: 10000000000000000000000000n, stakeTotalSupply: 10000000000000000000000000n,
kraikenTotalSupply: 10000000000000000000000000n, kraikenTotalSupply: 10000000000000000000000000n,
outstandingStake: 500n outstandingStake: 500n,
} as any) } as MockStatCollectionReturn);
const { snatchablePositions } = useSnatchSelection() const { snatchablePositions } = useSnatchSelection();
// Wait for watchEffect to run // 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 () => { it('should update floor tax based on selected positions', async () => {
const position = { const position = {
@ -238,40 +261,36 @@ describe('useSnatchSelection', () => {
harbDeposit: 100n, harbDeposit: 100n,
taxRate: 0.005, // 0.5% tax rate taxRate: 0.005, // 0.5% tax rate
taxRateIndex: 1, taxRateIndex: 1,
iAmOwner: false iAmOwner: false,
} };
vi.mocked(usePositions).mockReturnValue({ vi.mocked(usePositions).mockReturnValue({
activePositions: ref([position]) activePositions: ref([position]),
} as any) } as MockPositionsReturn);
vi.mocked(useStake).mockReturnValue({ vi.mocked(useStake).mockReturnValue({
stakingAmountShares: 100n, stakingAmountShares: 100n,
taxRate: 1.0 // Will be converted to 0.01 (1%) decimal taxRate: 1.0, // Will be converted to 0.01 (1%) decimal
} as any) } as MockStakeReturn);
// Need outstandingStake > stakingAmountShares to create shortfall // Need outstandingStake > stakingAmountShares to create shortfall
vi.mocked(useStatCollection).mockReturnValue({ vi.mocked(useStatCollection).mockReturnValue({
stakeTotalSupply: 10000000000000000000000000n, stakeTotalSupply: 10000000000000000000000000n,
kraikenTotalSupply: 10000000000000000000000000n, kraikenTotalSupply: 10000000000000000000000000n,
outstandingStake: 500n outstandingStake: 500n,
} as any) } as MockStatCollectionReturn);
vi.mocked(useAdjustTaxRate).mockReturnValue({ vi.mocked(useAdjustTaxRate).mockReturnValue({
taxRates: [ taxRates: [{ year: 1 }, { year: 2 }, { year: 3 }],
{ year: 1 }, } as MockAdjustTaxRateReturn);
{ year: 2 },
{ year: 3 }
]
} as any)
const { floorTax } = useSnatchSelection() const { floorTax } = useSnatchSelection();
// Wait for watchEffect to run // Wait for watchEffect to run
await nextTick() await nextTick();
// Floor tax should be taxRates[maxSelectedTaxRateIndex + 1] // Floor tax should be taxRates[maxSelectedTaxRateIndex + 1]
// Position has taxRateIndex: 1, so nextIndex = 2, taxRates[2] = { year: 3 } // 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 { ref, reactive, computed, type ComputedRef } from 'vue';
import * as StakeContract from "@/contracts/stake"; import * as StakeContract from '@/contracts/stake';
import { waitForTransactionReceipt } from "@wagmi/core"; import { waitForTransactionReceipt, type Config } from '@wagmi/core';
import { config } from "@/wagmi"; import { config } from '@/wagmi';
import { compactNumber, formatBigIntDivision } from "@/utils/helper"; // import { compactNumber, formatBigIntDivision } from "@/utils/helper";
import { useContractToast } from "./useContractToast"; import { useContractToast } from './useContractToast';
import { TAX_RATE_OPTIONS } from "kraiken-lib/taxRates"; import { TAX_RATE_OPTIONS } from 'kraiken-lib/taxRates';
const contractToast = useContractToast(); const contractToast = useContractToast();
enum State { enum State {
SignTransaction = "SignTransaction", SignTransaction = 'SignTransaction',
Waiting = "Waiting", Waiting = 'Waiting',
Action = "Action", Action = 'Action',
} }
export const taxRates = TAX_RATE_OPTIONS.map(({ index, year, daily }) => ({ export const taxRates = TAX_RATE_OPTIONS.map(({ index, year, daily }) => ({
@ -24,7 +24,6 @@ export function useAdjustTaxRate() {
const loading = ref(); const loading = ref();
const waiting = ref(); const waiting = ref();
const state: ComputedRef<State> = computed(() => { const state: ComputedRef<State> = computed(() => {
if (loading.value) { if (loading.value) {
return State.SignTransaction; return State.SignTransaction;
@ -37,31 +36,25 @@ export function useAdjustTaxRate() {
async function changeTax(positionId: bigint, taxRate: number) { async function changeTax(positionId: bigint, taxRate: number) {
try { try {
console.log("changeTax", { positionId, taxRate }); // console.log("changeTax", { positionId, taxRate });
loading.value = true; 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); const hash = await StakeContract.changeTax(positionId, index);
console.log("hash", hash); // console.log("hash", hash);
loading.value = false; loading.value = false;
waiting.value = true; waiting.value = true;
const data = await waitForTransactionReceipt(config as any, { await waitForTransactionReceipt(config as Config, {
hash: hash, hash: hash,
}); });
contractToast.showSuccessToast( contractToast.showSuccessToast(taxRate.toString(), 'Success!', 'You adjusted your position tax to', '', '%');
taxRate.toString(),
"Success!",
"You adjusted your position tax to",
"",
"%"
);
waiting.value = false; waiting.value = false;
} catch (error: any) { } catch (error: unknown) {
console.error("error", error); // console.error("error", error);
console.log(JSON.stringify(error, (_, v) => (typeof v === "bigint" ? v.toString() : v))); // 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 { } finally {
loading.value = false; loading.value = false;
waiting.value = false; waiting.value = false;

View file

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

View file

@ -1,15 +1,13 @@
import { ref, onMounted, onUnmounted, reactive, computed } from "vue"; import { ref, reactive, computed } from 'vue';
import { type ComputedRef } from "vue"; import { type ComputedRef } from 'vue';
import { config } from "@/wagmi"; import { config } from '@/wagmi';
import { AbiEncodingArrayLengthMismatchError, type WatchEventReturnType } from "viem"; // import axios from "axios";
import axios from "axios"; import { waitForTransactionReceipt, watchAccount, type Config } from '@wagmi/core';
import { getAccount, watchContractEvent, readContract, waitForTransactionReceipt, watchAccount } from "@wagmi/core"; import { type WatchAccountReturnType } from '@wagmi/core';
import { type WatchAccountReturnType } from "@wagmi/core"; import * as HarbContract from '@/contracts/harb';
import * as HarbContract from "@/contracts/harb";
// import HarbJson from "@/assets/contracts/harb.json"; // import HarbJson from "@/assets/contracts/harb.json";
import { type Abi, type Address } from "viem"; import { useWallet } from './useWallet';
import { useWallet } from "./useWallet"; // import { compactNumber, formatBigIntDivision } from "@/utils/helper";
import { compactNumber, formatBigIntDivision } from "@/utils/helper";
const wallet = useWallet(); const wallet = useWallet();
@ -19,15 +17,14 @@ const ubiDue = HarbContract.ubiDue;
let unwatch: WatchAccountReturnType; let unwatch: WatchAccountReturnType;
enum ClaimState { enum ClaimState {
NothingToClaim = "NothingToClaim", NothingToClaim = 'NothingToClaim',
StakeAble = "StakeAble", StakeAble = 'StakeAble',
SignTransaction = "SignTransaction", SignTransaction = 'SignTransaction',
Waiting = "Waiting", Waiting = 'Waiting',
NotEnoughApproval = "NotEnoughApproval", NotEnoughApproval = 'NotEnoughApproval',
} }
export function useClaim() { export function useClaim() {
const state: ComputedRef<ClaimState> = computed(() => { const state: ComputedRef<ClaimState> = computed(() => {
if (loading.value) { if (loading.value) {
return ClaimState.SignTransaction; return ClaimState.SignTransaction;
@ -46,34 +43,29 @@ export function useClaim() {
loading.value = true; loading.value = true;
const hash = await HarbContract.claimUbi(address); const hash = await HarbContract.claimUbi(address);
console.log("hash", hash); // console.log("hash", hash);
loading.value = false; loading.value = false;
waiting.value = true; waiting.value = true;
const data = await waitForTransactionReceipt(config as any, { await waitForTransactionReceipt(config as Config, {
hash: hash, hash: hash,
}); });
console.log("data.logs", data.logs); } catch (_error) {
// console.error("error", error);
} catch (error) {
console.error("error", error);
} finally { } finally {
loading.value = false; loading.value = false;
waiting.value = false; waiting.value = false;
} }
} }
onMounted(async () => {});
if (!unwatch) { if (!unwatch) {
console.log("useClaim function"); // console.log("useClaim function");
unwatch = watchAccount(config as any, { unwatch = watchAccount(config as Config, {
async onChange(data) { async onChange(data) {
console.log("watchAccount", data); // console.log("watchAccount", data);
if (data.address) { if (data.address) {
await HarbContract.setHarbContract(); 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) { export default function useClickOutside(component: Ref<HTMLElement | null>, callback: () => void) {
if (!component) return;
if (!component) return const listener = (event: MouseEvent) => {
const listener = (event: any) => { if (event.target !== component.value && event.composedPath().includes(component.value as EventTarget)) {
return;
if (event.target !== component.value && event.composedPath().includes(component.value)) {
return
} }
if (typeof callback === 'function') { if (typeof callback === 'function') {
callback() callback();
} }
} };
onMounted(() => { window.addEventListener('click', listener) }) onMounted(() => {
onBeforeUnmount(() => {window.removeEventListener('click', listener)}) 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 ToastNotification from '@/components/ToastNotification.vue';
import { POSITION, useToast } from "vue-toastification"; import { POSITION, useToast } from 'vue-toastification';
import { reactive } from "vue"; import { reactive } from 'vue';
const toast = useToast(); const toast = useToast();
export function useContractToast() { export function useContractToast() {
function showFailToast(name?: string) { function showFailToast(name?: string) {
console.log("name", name); // console.log("name", name);
if (name === "UserRejectedRequestError") { if (name === 'UserRejectedRequestError') {
// //
} else { } else {
showSuccessToast( showSuccessToast(
"", '',
"Failed!", 'Failed!',
"Your transaction didnt go through. Please try again.", 'Your transaction didnt go through. Please try again.',
"If the issue persists, please send a message in #helpdesk on our Discord.", 'If the issue persists, please send a message in #helpdesk on our Discord.',
"", '',
"error" 'error'
); );
} }
} }
function showSuccessToast( function showSuccessToast(value: string, header: string, subheader: string, info: string, unit: string, type: string = 'info') {
value: string,
header: string,
subheader: string,
info: string,
unit: string,
type: string = "info"
) {
// Define the content object with the component, props and listeners // Define the content object with the component, props and listeners
const content = { const content = {
component: Toast, component: ToastNotification,
// Any prop can be passed, but don't expect them to be reactive // Any prop can be passed, but don't expect them to be reactive
props: { props: {
value: value, value: value,
@ -51,7 +44,7 @@ export function useContractToast() {
position: POSITION.TOP_RIGHT, position: POSITION.TOP_RIGHT,
icon: false, icon: false,
closeOnClick: false, closeOnClick: false,
toastClassName: "modal-overlay", toastClassName: 'modal-overlay',
closeButton: false, closeButton: false,
hideProgressBar: true, hideProgressBar: true,
timeout: 10000, 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" // by convention, composable function names start with "use"
const darkTheme = ref(false) const darkTheme = ref(false);
export function useDark() { export function useDark() {
onMounted(() => { onMounted(() => {
if(localStorage.getItem("theme") === "dark") { if (localStorage.getItem('theme') === 'dark') {
document.documentElement.classList.add("dark") document.documentElement.classList.add('dark');
darkTheme.value = true darkTheme.value = true;
} else { } else {
darkTheme.value = false darkTheme.value = false;
} }
});
})
watch( watch(
() => darkTheme.value, () => darkTheme.value,
(newData) => { newData => {
document.documentElement.removeAttribute("data-theme"); document.documentElement.removeAttribute('data-theme');
localStorage.removeItem("theme") localStorage.removeItem('theme');
if (newData) { if (newData) {
// import('@/assets/sass/elementplus-dark.scss'); // import('@/assets/sass/elementplus-dark.scss');
document.documentElement.classList.add("dark") document.documentElement.classList.add('dark');
document.documentElement.setAttribute("data-theme", "dark"); document.documentElement.setAttribute('data-theme', 'dark');
localStorage.setItem("theme", "dark"); localStorage.setItem('theme', 'dark');
} else { } else {
// import('@/assets/sass/elementplus-light.scss'); // import('@/assets/sass/elementplus-light.scss');
document.documentElement.classList.remove("dark") document.documentElement.classList.remove('dark');
document.documentElement.setAttribute("data-theme", "default"); document.documentElement.setAttribute('data-theme', 'default');
localStorage.setItem("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" // by convention, composable function names start with "use"
export function useMobile() { export function useMobile() {
@ -18,12 +18,12 @@ export function useMobile() {
} }
onMounted(async () => { onMounted(async () => {
window.addEventListener("resize", handleWindowSizeChange); window.addEventListener('resize', handleWindowSizeChange);
handleWindowSizeChange(); handleWindowSizeChange();
}); });
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener("resize", handleWindowSizeChange); window.removeEventListener('resize', handleWindowSizeChange);
}); });
return isMobile; return isMobile;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,45 +1,43 @@
import { ref, onMounted, onUnmounted, reactive, computed } from "vue"; import { ref, reactive, computed } from 'vue';
import { type Ref } from "vue"; import { getAccount, getBalance, watchAccount, watchChainId, type Config } from '@wagmi/core';
import { getAccount, getBalance, watchAccount, watchChainId } from "@wagmi/core"; import { type WatchAccountReturnType, type GetAccountReturnType, type GetBalanceReturnType } from '@wagmi/core';
import { type WatchAccountReturnType, type GetAccountReturnType, type GetBalanceReturnType } from "@wagmi/core"; import { config } from '@/wagmi';
import { config } from "@/wagmi"; import { getAllowance, HarbContract, getNonce } from '@/contracts/harb';
import { getAllowance, HarbContract, getNonce } from "@/contracts/harb"; import logger from '@/utils/logger';
import logger from "@/utils/logger"; import { setHarbContract } from '@/contracts/harb';
import { setHarbContract } from "@/contracts/harb"; import { setStakeContract } from '@/contracts/stake';
import { setStakeContract } from "@/contracts/stake"; import { chainsData, DEFAULT_CHAIN_ID } from '@/config';
import {chainsData, DEFAULT_CHAIN_ID} from "@/config"
const balance = ref<GetBalanceReturnType>({ const balance = ref<GetBalanceReturnType>({
value: 0n, value: 0n,
decimals: 0, decimals: 0,
symbol: "", symbol: '',
formatted: "" formatted: '',
}); });
const account = ref<GetAccountReturnType>(getAccount(config as any)); const account = ref<GetAccountReturnType>(getAccount(config as Config));
if (!account.value.chainId) { 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); const selectedChainId = computed(() => account.value.chainId ?? DEFAULT_CHAIN_ID);
export const chainData = computed(() => { 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 unwatch: WatchAccountReturnType | null = null;
let unwatchChain: any = null; let unwatchChain: WatchAccountReturnType | null = null;
export function useWallet() { export function useWallet() {
async function loadBalance() { async function loadBalance() {
logger.contract("loadBalance") logger.contract('loadBalance');
if (account.value.address) { 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, address: account.value.address,
token: HarbContract.contractAddress, token: HarbContract.contractAddress,
}); });
console.log("balance.value", balance.value); // console.log("balance.value", balance.value);
return balance.value; return balance.value;
} else { } else {
@ -48,20 +46,20 @@ export function useWallet() {
} }
if (!unwatch) { if (!unwatch) {
console.log("useWallet function"); // console.log("useWallet function");
unwatch = watchAccount(config as any, { unwatch = watchAccount(config as Config, {
async onChange(data) { async onChange(data) {
console.log("watchaccount-useWallet", data); // console.log("watchaccount-useWallet", data);
if(!data.address) { if (!data.address) {
logger.info(`disconnected`); logger.info(`disconnected`);
balance.value = { balance.value = {
value: 0n, value: 0n,
decimals: 0, decimals: 0,
symbol: "", symbol: '',
formatted: "" formatted: '',
} };
} else if (account.value.address !== data.address || account.value.chainId !== data.chainId) { } else if (account.value.address !== data.address || account.value.chainId !== data.chainId) {
logger.info(`Account changed!:`, data.address); logger.info(`Account changed!:`, data.address);
account.value = data; account.value = data;
@ -69,26 +67,23 @@ export function useWallet() {
await getAllowance(); await getAllowance();
// await loadPositions(); // await loadPositions();
await getNonce(); await getNonce();
setHarbContract() setHarbContract();
setStakeContract() setStakeContract();
} }
}, },
}); });
} }
//funzt nicht mehr-> library Änderung? //funzt nicht mehr-> library Änderung?
if(!unwatchChain){ if (!unwatchChain) {
console.log("unwatchChain"); // console.log("unwatchChain");
unwatchChain = watchChainId(config as any, {
async onChange(chainId) {
console.log("chainId123", chainId);
unwatchChain = watchChainId(config as Config, {
async onChange(_chainId) {
await loadBalance(); await loadBalance();
await getAllowance(); await getAllowance();
await getNonce(); 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 env = import.meta.env;
const LOCAL_PONDER_URL = env.VITE_PONDER_BASE_SEPOLIA_LOCAL_FORK ?? "http://127.0.0.1:42069/graphql"; 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_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_RPC_URL = env.VITE_LOCAL_RPC_URL ?? '/rpc/anvil';
const localContracts = (deploymentsLocal as any)?.contracts ?? {}; interface DeploymentContracts {
const localInfra = (deploymentsLocal as any)?.infrastructure ?? {}; Kraiken?: string;
const LOCAL_KRAIKEN = (env.VITE_KRAIKEN_ADDRESS ?? localContracts.Kraiken ?? "").trim(); Stake?: string;
const LOCAL_STAKE = (env.VITE_STAKE_ADDRESS ?? localContracts.Stake ?? "").trim(); LiquidityManager?: string;
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 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 { function detectDefaultChainId(): number {
const envValue = import.meta.env.VITE_DEFAULT_CHAIN_ID; 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(); 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; return 31337;
} }
} }
@ -40,57 +50,58 @@ export const chainsData = [
// local base sepolia fork // local base sepolia fork
id: 31337, id: 31337,
graphql: LOCAL_PONDER_URL, graphql: LOCAL_PONDER_URL,
path: "local", path: 'local',
stake: LOCAL_STAKE, stake: LOCAL_STAKE,
harb: LOCAL_KRAIKEN, harb: LOCAL_KRAIKEN,
uniswap: "", uniswap: '',
cheats: { cheats: {
weth: LOCAL_WETH, weth: LOCAL_WETH,
swapRouter: LOCAL_ROUTER, swapRouter: LOCAL_ROUTER,
liquidityManager: LOCAL_LM, liquidityManager: LOCAL_LM,
txnBot: LOCAL_TXNBOT_URL, txnBot: LOCAL_TXNBOT_URL,
rpc: LOCAL_RPC_URL, rpc: LOCAL_RPC_URL,
} },
}, },
{ {
// sepolia // sepolia
id: 11155111, id: 11155111,
graphql: import.meta.env.VITE_PONDER_SEPOLIA ?? "", graphql: import.meta.env.VITE_PONDER_SEPOLIA ?? '',
path: "sepolia", path: 'sepolia',
stake: "0xCd21a41a137BCAf8743E47D048F57D92398f7Da9", stake: '0xCd21a41a137BCAf8743E47D048F57D92398f7Da9',
harb: "0x087F256D11fe533b0c7d372e44Ee0F9e47C89dF9", harb: '0x087F256D11fe533b0c7d372e44Ee0F9e47C89dF9',
uniswap: "https://app.uniswap.org/swap?chain=mainnet&inputCurrency=NATIVE", uniswap: 'https://app.uniswap.org/swap?chain=mainnet&inputCurrency=NATIVE',
cheats: null cheats: null,
}, { },
{
// base-sepolia (local dev default) // base-sepolia (local dev default)
id: 84532, id: 84532,
graphql: import.meta.env.VITE_PONDER_BASE_SEPOLIA_LOCAL_FORK ?? LOCAL_PONDER_URL, graphql: import.meta.env.VITE_PONDER_BASE_SEPOLIA_LOCAL_FORK ?? LOCAL_PONDER_URL,
path: "sepoliabase", path: 'sepoliabase',
stake: "0xe28020BCdEeAf2779dd47c670A8eFC2973316EE2", stake: '0xe28020BCdEeAf2779dd47c670A8eFC2973316EE2',
harb: "0x22c264Ecf8D4E49D1E3CabD8DD39b7C4Ab51C1B8", harb: '0x22c264Ecf8D4E49D1E3CabD8DD39b7C4Ab51C1B8',
uniswap: "https://app.uniswap.org/swap?chain=mainnet&inputCurrency=NATIVE", uniswap: 'https://app.uniswap.org/swap?chain=mainnet&inputCurrency=NATIVE',
cheats: { cheats: {
weth: "0x4200000000000000000000000000000000000006", weth: '0x4200000000000000000000000000000000000006',
swapRouter: "0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4", swapRouter: '0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4',
txnBot: import.meta.env.VITE_TXNBOT_BASE_SEPOLIA_LOCAL_FORK ?? LOCAL_TXNBOT_URL txnBot: import.meta.env.VITE_TXNBOT_BASE_SEPOLIA_LOCAL_FORK ?? LOCAL_TXNBOT_URL,
} },
}, },
{ {
// base mainnet // base mainnet
id: 8453, id: 8453,
graphql: import.meta.env.VITE_PONDER_BASE ?? "", graphql: import.meta.env.VITE_PONDER_BASE ?? '',
path: "base", path: 'base',
stake: "0xed70707fab05d973ad41eae8d17e2bcd36192cfc", stake: '0xed70707fab05d973ad41eae8d17e2bcd36192cfc',
harb: "0x45caa5929f6ee038039984205bdecf968b954820", harb: '0x45caa5929f6ee038039984205bdecf968b954820',
uniswap: "https://app.uniswap.org/swap?chain=mainnet&inputCurrency=NATIVE", uniswap: 'https://app.uniswap.org/swap?chain=mainnet&inputCurrency=NATIVE',
cheats: { cheats: {
weth: "0x4200000000000000000000000000000000000006", weth: '0x4200000000000000000000000000000000000006',
swapRouter: "", swapRouter: '',
txnBot: import.meta.env.VITE_TXNBOT_BASE ?? "" txnBot: import.meta.env.VITE_TXNBOT_BASE ?? '',
} },
}, },
]; ];
export function getChain(id:number){ 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 { ref } from 'vue';
import { config } from "@/wagmi"; import { config } from '@/wagmi';
import { type WatchEventReturnType } from "viem"; import { getAccount, readContract, writeContract, waitForTransactionReceipt, getChainId } from '@wagmi/core';
import { import type { Config } from '@wagmi/core';
getAccount, import { KraikenAbi } from 'kraiken-lib';
watchContractEvent, import { type Abi, type Address, type Hash } from 'viem';
readContract, import { StakeContract } from '@/contracts/stake';
writeContract, import { getChain } from '@/config';
waitForTransactionReceipt, import logger from '@/utils/logger';
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";
// const chain1 = useChain(); // const chain1 = useChain();
// console.log("chain1", chain1); // console.log("chain1", chain1);
interface Contract { interface Contract {
abi: Abi; abi: Abi;
contractAddress: Address; contractAddress: Address;
@ -33,19 +24,19 @@ export const totalSupply = ref(0n);
// export const HarbContract = await getHarbJson() // export const HarbContract = await getHarbJson()
export let HarbContract = getHarbJson(); export let HarbContract = getHarbJson();
function getHarbJson(){ function getHarbJson() {
console.log("getHarbJson"); // console.log("getHarbJson");
const chainId = getChainId(config as any); const chainId = getChainId(config as Config);
console.log("chainId", chainId); // console.log("chainId", chainId);
const chain = getChain(chainId) const chain = getChain(chainId);
return {abi: KraikenAbi as Abi, contractAddress: chain?.harb} as Contract; return { abi: KraikenAbi as Abi, contractAddress: chain?.harb } as Contract;
} }
export function setHarbContract(){ export function setHarbContract() {
console.log("setHarbContract"); // console.log("setHarbContract");
HarbContract = getHarbJson(); HarbContract = getHarbJson();
} }
@ -55,18 +46,17 @@ export function setHarbContract(){
// }); // });
export async function getAllowance() { 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) { if (!account.address) {
return 0n; return 0n;
} }
const result = await readContract(config as any, { const result = await readContract(config as Config, {
abi: HarbContract.abi, abi: HarbContract.abi,
address: HarbContract.contractAddress, address: HarbContract.contractAddress,
functionName: "allowance", functionName: 'allowance',
args: [account.address, StakeContract.contractAddress], args: [account.address, StakeContract.contractAddress],
}); });
allowance.value = result; allowance.value = result;
@ -74,31 +64,31 @@ export async function getAllowance() {
} }
export async function getMinStake() { 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, abi: HarbContract.abi,
address: HarbContract.contractAddress, address: HarbContract.contractAddress,
functionName: "minStake", functionName: 'minStake',
args: [], args: [],
}) as bigint; })) as bigint;
allowance.value = result; allowance.value = result;
return result; return result;
} }
export async function getNonce() { 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) { if (!account.address) {
return 0n; 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, abi: HarbContract.abi,
address: HarbContract.contractAddress, address: HarbContract.contractAddress,
functionName: "nonces", functionName: 'nonces',
args: [account.address], args: [account.address],
}); });
nonce.value = result; nonce.value = result;
@ -107,13 +97,12 @@ export async function getNonce() {
} }
export async function getName() { export async function getName() {
logger.contract("getName"); logger.contract('getName');
const result = await readContract(config as Config, {
const result = await readContract(config as any, {
abi: HarbContract.abi, abi: HarbContract.abi,
address: HarbContract.contractAddress, address: HarbContract.contractAddress,
functionName: "name", functionName: 'name',
args: [], args: [],
}); });
name.value = result; name.value = result;
@ -121,48 +110,45 @@ export async function getName() {
return result as string; return result as string;
} }
export async function approve(amount: bigint): Promise<Hash> {
export async function approve(amount: bigint): Promise<any> { const account = getAccount(config as Config);
const account = getAccount(config as any);
if (!account.address) { 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, abi: HarbContract.abi,
address: HarbContract.contractAddress, address: HarbContract.contractAddress,
functionName: "approve", functionName: 'approve',
args: [StakeContract.contractAddress, amount], args: [StakeContract.contractAddress, amount],
}); });
console.log("result", result); // console.log("result", result);
const transactionReceipt = waitForTransactionReceipt(config as any, { await waitForTransactionReceipt(config as Config, {
hash: result, hash: result,
}); });
console.log("transactionReceipt", transactionReceipt); // console.log("transactionReceipt", transactionReceipt);
return transactionReceipt; return result;
} }
//claim //claim
export async function claimUbi(address: Address): Promise<any> { export async function claimUbi(address: Address): Promise<Hash> {
const result = await writeContract(config as any, { const result = await writeContract(config as Config, {
abi: HarbContract.abi, abi: HarbContract.abi,
address: HarbContract.contractAddress, address: HarbContract.contractAddress,
functionName: "claimUbi", functionName: 'claimUbi',
args: [address], args: [address],
}); });
return result; return result;
} }
export async function getTotalSupply() { export async function getTotalSupply() {
logger.contract("getTotalSupply"); logger.contract('getTotalSupply');
const result = await readContract(config as Config, {
const result = await readContract(config as any, {
abi: HarbContract.abi, abi: HarbContract.abi,
address: HarbContract.contractAddress, address: HarbContract.contractAddress,
functionName: "totalSupply", functionName: 'totalSupply',
args: [], args: [],
}); });
totalSupply.value = result as bigint; totalSupply.value = result as bigint;

View file

@ -1,10 +1,11 @@
import { ref } from "vue"; import { ref } from 'vue';
import { config } from "@/wagmi"; import { config } from '@/wagmi';
import { readContract, writeContract, getChainId } from "@wagmi/core"; import { readContract, writeContract, getChainId } from '@wagmi/core';
import { StakeAbi } from "kraiken-lib"; import type { Config } from '@wagmi/core';
import { type Abi, type Address } from "viem"; import { StakeAbi } from 'kraiken-lib';
import {getChain} from "@/config" import { type Abi, type Address, type Hash, type Hex } from 'viem';
import logger from "@/utils/logger"; import { getChain } from '@/config';
import logger from '@/utils/logger';
const TAX_FLOOR_DURATION = 60 * 60 * 24 * 3; const TAX_FLOOR_DURATION = 60 * 60 * 24 * 3;
interface Contract { interface Contract {
@ -12,51 +13,43 @@ interface Contract {
contractAddress: Address; contractAddress: Address;
} }
export const minStake = ref(); export const minStake = ref();
export const totalSupply = ref(0n); 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 Config);
// console.log("chainId", chainId);
function getStakeJson(){ const chain = getChain(chainId);
const chainId = getChainId(config as any);
console.log("chainId", chainId);
const chain = getChain(chainId) return { abi: StakeAbi as Abi, contractAddress: chain?.stake } as Contract;
return {abi: StakeAbi as Abi, contractAddress: chain?.stake} as Contract;
} }
export function setStakeContract(){ export function setStakeContract() {
logger.contract("setStakeContract") logger.contract('setStakeContract');
StakeContract = getStakeJson(); StakeContract = getStakeJson();
} }
export async function snatchService(assets: bigint, receiver: Address, taxRate: number, positionsToSnatch: Array<bigint>) {
// console.log("StakeContract", StakeContract);
export async function snatchService( const result = await writeContract(config as Config, {
assets: BigInt,
receiver: Address,
taxRate: Number,
positionsToSnatch: Array<BigInt>
) {
console.log("StakeContract", StakeContract);
const result = await writeContract(config as any, {
abi: StakeContract.abi, abi: StakeContract.abi,
address: StakeContract.contractAddress, address: StakeContract.contractAddress,
functionName: "snatch", functionName: 'snatch',
args: [assets, receiver, taxRate, positionsToSnatch], args: [assets, receiver, taxRate, positionsToSnatch],
}); });
return result; return result;
} }
export async function exitPosition(positionId: bigint): Promise<any> { export async function exitPosition(positionId: bigint): Promise<Hash> {
const result = await writeContract(config as any, { const result = await writeContract(config as Config, {
abi: StakeContract.abi, abi: StakeContract.abi,
address: StakeContract.contractAddress, address: StakeContract.contractAddress,
functionName: "exitPosition", functionName: 'exitPosition',
args: [positionId], args: [positionId],
}); });
@ -64,11 +57,11 @@ export async function exitPosition(positionId: bigint): Promise<any> {
} }
//changeTax //changeTax
export async function changeTax(positionId: bigint, taxRate: number): Promise<any> { export async function changeTax(positionId: bigint, taxRate: number): Promise<Hash> {
const result = await writeContract(config as any, { const result = await writeContract(config as Config, {
abi: StakeContract.abi, abi: StakeContract.abi,
address: StakeContract.contractAddress, address: StakeContract.contractAddress,
functionName: "changeTax", functionName: 'changeTax',
args: [positionId, taxRate], args: [positionId, taxRate],
}); });
@ -79,48 +72,48 @@ export async function changeTax(positionId: bigint, taxRate: number): Promise<an
* snatch/stake with permit * snatch/stake with permit
*/ */
export async function permitAndSnatch( export async function permitAndSnatch(
assets: BigInt, assets: bigint,
receiver: Address, receiver: Address,
taxRate: number, taxRate: number,
positionsToSnatch: Array<BigInt>, positionsToSnatch: Array<bigint>,
deadline: BigInt, deadline: bigint,
v: any, v: number,
r: any, r: Hex,
s: any 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, abi: StakeContract.abi,
address: StakeContract.contractAddress, address: StakeContract.contractAddress,
functionName: "permitAndSnatch", functionName: 'permitAndSnatch',
args: [assets, receiver, taxRate, positionsToSnatch, deadline, v, r, s], args: [assets, receiver, taxRate, positionsToSnatch, deadline, v, r, s],
}); });
return result; return result;
} }
export async function getTotalSupply() { export async function getTotalSupply() {
logger.contract("getTotalSupply") logger.contract('getTotalSupply');
await setStakeContract(); await setStakeContract();
const result = await readContract(config as any, { const result = await readContract(config as Config, {
abi: StakeContract.abi, abi: StakeContract.abi,
address: StakeContract.contractAddress, address: StakeContract.contractAddress,
functionName: "totalSupply", functionName: 'totalSupply',
args: [], args: [],
}); });
console.log("result", result); // console.log("result", result);
totalSupply.value = result as bigint; totalSupply.value = result as bigint;
return result; return result;
} }
export async function getOutstandingSupply() { 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, abi: StakeContract.abi,
address: StakeContract.contractAddress, address: StakeContract.contractAddress,
functionName: "outstandingStake", functionName: 'outstandingStake',
args: [], args: [],
}); });
@ -129,35 +122,35 @@ export async function getOutstandingSupply() {
} }
export async function getTaxDue(positionID: bigint) { export async function getTaxDue(positionID: bigint) {
logger.contract("getTaxDue") logger.contract('getTaxDue');
const result = await readContract(config as any, { const result = await readContract(config as Config, {
abi: StakeContract.abi, abi: StakeContract.abi,
address: StakeContract.contractAddress, address: StakeContract.contractAddress,
functionName: "taxDue", functionName: 'taxDue',
args: [positionID, TAX_FLOOR_DURATION], args: [positionID, TAX_FLOOR_DURATION],
}); });
return result as bigint; return result as bigint;
} }
export async function payTax(positionID: 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, abi: StakeContract.abi,
address: StakeContract.contractAddress, address: StakeContract.contractAddress,
functionName: "payTax", functionName: 'payTax',
args: [positionID], args: [positionID],
}); });
return result; return result;
} }
export async function assetsToShares(asset: bigint) { 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, abi: StakeContract.abi,
address: StakeContract.contractAddress, address: StakeContract.contractAddress,
functionName: "assetsToShares", functionName: 'assetsToShares',
args: [asset], args: [asset],
}); });
return result as bigint; 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 { export default {
beforeMount(el: any, binding: any) { beforeMount(el: ClickOutsideElement, binding: DirectiveBinding<(event: MouseEvent) => void>) {
el.clickOutsideEvent = function (event: any) { el.clickOutsideEvent = function (event: MouseEvent) {
// Check if the clicked element is neither the element // Check if the clicked element is neither the element
// to which the directive is applied nor its child // 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 // Invoke the provided method
binding.value(event); 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 // 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

@ -2,4 +2,4 @@
<main> <main>
<slot></slot> <slot></slot>
</main> </main>
</template> </template>

View file

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

View file

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

View file

@ -1,42 +1,41 @@
import { createRouter, createWebHashHistory } from "vue-router"; import { createRouter, createWebHashHistory } from 'vue-router';
import HomeView from "../views/HomeView.vue";
import { authGuard } from './authGuard'; import { authGuard } from './authGuard';
const router = createRouter({ const router = createRouter({
history: createWebHashHistory(), history: createWebHashHistory(),
routes: [ routes: [
{ {
path: "/", path: '/',
name: "home", name: 'home',
redirect: "/stake", redirect: '/stake',
}, },
{ {
path: "/stake", path: '/stake',
name: "stake", name: 'stake',
meta: { meta: {
title: "Stake", title: 'Stake',
group: "navbar", group: 'navbar',
layout: 'NavbarLayout' layout: 'NavbarLayout',
}, },
beforeEnter: authGuard, beforeEnter: authGuard,
component: () => import("../views/StakeView.vue"), component: () => import('../views/StakeView.vue'),
}, },
{ {
path: "/login", path: '/login',
name: "login", name: 'login',
meta: { meta: {
layout: 'DefaultLayout' layout: 'DefaultLayout',
, }, },
component: () => import("../views/LoginView.vue"), component: () => import('../views/LoginView.vue'),
}, },
{ {
path: "/cheats", path: '/cheats',
name: "cheats", name: 'cheats',
meta: { meta: {
title: "Cheats", title: 'Cheats',
layout: 'NavbarLayout' 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( export function createPermitObject(
verifyingContract: Address, verifyingContract: Address,
fromAddress: Address, fromAddress: Address,
spender: Address, spender: Address,
nonce: BigInt, nonce: bigint,
deadline: BigInt, deadline: bigint,
value: BigInt, value: bigint,
chain: number, chain: number,
domainName: string domainName: string
) { ) {
@ -19,45 +18,44 @@ export function createPermitObject(
value: value, value: value,
}; };
const domainType = [ const domainType = [
{ name: "name", type: "string" }, { name: 'name', type: 'string' },
{ name: "version", type: "string" }, { name: 'version', type: 'string' },
{ name: "chainId", type: "uint256" }, { name: 'chainId', type: 'uint256' },
{ name: "verifyingContract", type: "address" }, { name: 'verifyingContract', type: 'address' },
]; ];
const primaryType: "EIP712Domain" | "Permit" = "Permit"; const primaryType: 'EIP712Domain' | 'Permit' = 'Permit';
const types = { const types = {
EIP712Domain: domainType, EIP712Domain: domainType,
Permit: [ Permit: [
{ {
name: "owner", name: 'owner',
type: "address", type: 'address',
}, },
{ {
name: "spender", name: 'spender',
type: "address", type: 'address',
}, },
{ {
name: "value", name: 'value',
type: "uint256", type: 'uint256',
}, },
{ {
name: "nonce", name: 'nonce',
type: "uint256", type: 'uint256',
}, },
{ {
name: "deadline", name: 'deadline',
type: "uint256", type: 'uint256',
}, },
], ],
}; };
const domain: TypedDataDomain | undefined = { const domain: TypedDataDomain | undefined = {
name: domainName, name: domainName,
version: "1", version: '1',
chainId: chain, chainId: chain,
verifyingContract: verifyingContract, verifyingContract: verifyingContract,
}; };
@ -80,6 +78,6 @@ export function getSignatureRSV2(sig: `0x${string}`) {
export function getSignatureRSV(signature: `0x${string}`) { export function getSignatureRSV(signature: `0x${string}`) {
const r = signature.slice(0, 66) as `0x${string}`; const r = signature.slice(0, 66) as `0x${string}`;
const s = `0x${signature.slice(66, 130)}` 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 }; 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 { interface BlockiesOpt {
seed: string; seed: string;
@ -10,12 +10,11 @@ interface BlockiesOpt {
} }
export function getBlocky(address: string) { export function getBlocky(address: string) {
if (!address || typeof address !== "string" ) { if (!address || typeof address !== 'string') {
return; return;
} }
console.log("address", address);
var blockiesData = createIcon({ const blockiesData = createIcon({
seed: address?.toLowerCase(), seed: address?.toLowerCase(),
size: 8, size: 8,
scale: 4, scale: 4,
@ -25,17 +24,17 @@ export function getBlocky(address: string) {
} }
function seedrand(seed: string) { function seedrand(seed: string) {
for (var i = 0; i < randseed.length; i++) { for (let i = 0; i < randseed.length; i++) {
randseed[i] = 0; 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); randseed[i % 4] = (randseed[i % 4] << 5) - randseed[i % 4] + seed.charCodeAt(i);
} }
} }
function rand() { function rand() {
// based on Java's String.hashCode(), expanded to 4 32bit values // 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[0] = randseed[1];
randseed[1] = randseed[2]; randseed[1] = randseed[2];
@ -47,36 +46,36 @@ function rand() {
function createColor() { function createColor() {
//saturation is the whole color spectrum //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 //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% //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; return color;
} }
function createImageData(size: number) { function createImageData(size: number) {
var width = size; // Only support square icons for now const width = size; // Only support square icons for now
var height = size; const height = size;
var dataWidth = Math.ceil(width / 2); const dataWidth = Math.ceil(width / 2);
var mirrorWidth = width - dataWidth; const mirrorWidth = width - dataWidth;
var data = []; const data = [];
for (var y = 0; y < height; y++) { for (let y = 0; y < height; y++) {
var row = []; let row = [];
for (var x = 0; x < dataWidth; x++) { for (let x = 0; x < dataWidth; x++) {
// this makes foreground and background color to have a 43% (1/2.3) probability // this makes foreground and background color to have a 43% (1/2.3) probability
// spot color has 13% chance // spot color has 13% chance
row[x] = Math.floor(rand() * 2.3); row[x] = Math.floor(rand() * 2.3);
} }
var r = row.slice(0, mirrorWidth); const r = row.slice(0, mirrorWidth);
r.reverse(); r.reverse();
row = row.concat(r); row = row.concat(r);
for (var i = 0; i < row.length; i++) { for (let i = 0; i < row.length; i++) {
data.push(row[i]); data.push(row[i]);
} }
} }
@ -84,50 +83,49 @@ function createImageData(size: number) {
return data; return data;
} }
function buildOpts(opts: any) { function buildOpts(opts: Partial<BlockiesOpt>): BlockiesOpt {
var newOpts: any = {}; const seed = opts.seed || Math.floor(Math.random() * Math.pow(10, 16)).toString(16);
newOpts.seed = opts.seed || Math.floor(Math.random() * Math.pow(10, 16)).toString(16); seedrand(seed);
seedrand(newOpts.seed); return {
seed,
newOpts.size = opts.size || 8; size: opts.size || 8,
newOpts.scale = opts.scale || 4; scale: opts.scale || 4,
newOpts.color = opts.color || createColor(); color: opts.color || createColor(),
newOpts.bgcolor = opts.bgcolor || createColor(); bgcolor: opts.bgcolor || createColor(),
newOpts.spotcolor = opts.spotcolor || createColor(); spotcolor: opts.spotcolor || createColor(),
};
return newOpts;
} }
function renderIcon(opts: BlockiesOpt, canvas: HTMLCanvasElement) { function renderIcon(opts: Partial<BlockiesOpt>, canvas: HTMLCanvasElement) {
opts = buildOpts(opts || {}); const fullOpts = buildOpts(opts);
var imageData = createImageData(opts.size); const imageData = createImageData(fullOpts.size);
var width = Math.sqrt(imageData.length); 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")!; const cc = canvas.getContext('2d')!;
cc.fillStyle = opts.bgcolor!; cc.fillStyle = fullOpts.bgcolor!;
cc.fillRect(0, 0, canvas.width, canvas.height); 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 data is 0, leave the background
if (imageData[i]) { if (imageData[i]) {
var row = Math.floor(i / width); const row = Math.floor(i / width);
var col = i % width; const col = i % width;
// if data is 2, choose spot color, if 1 choose foreground // 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; return canvas;
} }
export function createIcon(opts: BlockiesOpt) { export function createIcon(opts: BlockiesOpt) {
var canvas = document.createElement("canvas"); const canvas = document.createElement('canvas');
renderIcon(opts, canvas); renderIcon(opts, canvas);

View file

@ -1,18 +1,17 @@
import { formatUnits } from 'viem' import { formatUnits } from 'viem';
export function getAddressShortName(address: string) { export function getAddressShortName(address: string) {
if (!address) { if (!address) {
return ""; return '';
} }
const addressBegin = address.substring(0, 6); const addressBegin = address.substring(0, 6);
const addressEnd = address.substring(address.length - 4, address.length); const addressEnd = address.substring(address.length - 4, address.length);
return addressBegin + "..." + addressEnd; return addressBegin + '...' + addressEnd;
} }
export function compactNumber(number: number) { export function compactNumber(number: number) {
return Intl.NumberFormat("en-US", { return Intl.NumberFormat('en-US', {
notation: "compact", notation: 'compact',
// minimumFractionDigits: 2, // minimumFractionDigits: 2,
maximumFractionDigits: 2, maximumFractionDigits: 2,
}).format(number); }).format(number);
@ -20,45 +19,42 @@ export function compactNumber(number: number) {
export function formatBigNumber(number: bigint, decimals: number, digits: number = 5) { export function formatBigNumber(number: bigint, decimals: number, digits: number = 5) {
let bigIntNumber = number; let bigIntNumber = number;
if(!bigIntNumber){ if (!bigIntNumber) {
bigIntNumber = BigInt(0) bigIntNumber = BigInt(0);
} }
const formattedNumber = Number(formatUnits(bigIntNumber, decimals)) const formattedNumber = Number(formatUnits(bigIntNumber, decimals));
if(formattedNumber === 0){ if (formattedNumber === 0) {
return "0" return '0';
} }
return formattedNumber.toFixed(digits) return formattedNumber.toFixed(digits);
} }
export function bigInt2Number(number: bigint, decimals: number) { export function bigInt2Number(number: bigint, decimals: number) {
let bigIntNumber = number; let bigIntNumber = number;
if(!bigIntNumber){ if (!bigIntNumber) {
bigIntNumber = BigInt(0) bigIntNumber = BigInt(0);
} }
const formattedNumber = Number(formatUnits(bigIntNumber, decimals)) const formattedNumber = Number(formatUnits(bigIntNumber, decimals));
return formattedNumber return formattedNumber;
} }
export function InsertCommaNumber(number: any) { export function InsertCommaNumber(number: number) {
if (!number) { if (!number) {
return 0; return 0;
} }
const formattedWithOptions = number.toLocaleString("en-US"); const formattedWithOptions = number.toLocaleString('en-US');
return formattedWithOptions; return formattedWithOptions;
} }
export function formatBigIntDivision(nominator: bigint, denominator: bigint, digits: number = 2) { export function formatBigIntDivision(nominator: bigint, denominator: bigint, _digits: number = 2) {
if (!nominator) { if (!nominator) {
return 0; return 0;
} }
let display = nominator.toString(); const display = nominator.toString();
const decimal = (Number(denominator) / 10).toString().length; 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 // 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){ if (data) {
console.log(`%c ${text}`, 'color: #17a2b8', data); // console.log(`%c ${text}`, 'color: #17a2b8', data);
} else {
} 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){ if (data) {
console.log(`%c ${text}`, 'color: #8732a8', data); // console.log(`%c ${text}`, 'color: #8732a8', data);
} else {
} else{ // console.log(`%c ${text}`, 'color: #8732a8');
console.log(`%c ${text}`, 'color: #8732a8');
} }
} }
export default { export default {
info, info,
contract contract,
} };

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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