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

resolves #47

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

View file

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

View file

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

View file

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

9
web-app/.prettierrc Normal file
View file

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

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

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

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

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

2094
web-app/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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>
<chart-js
:snatchedPositions="snatchPositions.map((obj) => obj.id)"
:positions="activePositions"
:dark="darkTheme"
></chart-js>
<ChartJs :snatchedPositions="snatchPositions.map(obj => obj.id)" :positions="activePositions" :dark="darkTheme"></ChartJs>
</template>
<script setup lang="ts">
import ChartJs from "@/components/chart/ChartJs.vue";
import {bigInt2Number, formatBigIntDivision} from "@/utils/helper";
import { computed, ref } from "vue";
import { useStatCollection } from "@/composables/useStatCollection";
import { useStake } from "@/composables/useStake";
import { usePositions, type Position } from "@/composables/usePositions";
import { useDark } from "@/composables/useDark";
import ChartJs from '@/components/chart/ChartJs.vue';
import { bigInt2Number, formatBigIntDivision } from '@/utils/helper';
import { computed, ref } from 'vue';
import { useStatCollection } from '@/composables/useStatCollection';
import { useStake } from '@/composables/useStake';
import { usePositions, type Position } from '@/composables/usePositions';
import { useDark } from '@/composables/useDark';
const { darkTheme } = useDark();
const { activePositions, myActivePositions, tresholdValue, myClosedPositions, createRandomPosition } = usePositions();
const { activePositions } = usePositions();
const ignoreOwner = ref(false);
const taxRate = ref<number>(1.0);
const minStakeAmount = computed(() => {
console.log("minStake", minStake.value);
return formatBigIntDivision(minStake.value, 10n ** 18n);
return formatBigIntDivision(minStake.value, 10n ** 18n);
});
const stakeAbleHarbAmount = computed(() => statCollection.kraikenTotalSupply / 5n);
@ -34,35 +28,33 @@ const minStake = computed(() => stakeAbleHarbAmount.value / 600n);
const stake = useStake();
const statCollection = useStatCollection();
const snatchPositions = computed(() => {
if (
if (
bigInt2Number(statCollection.outstandingStake, 18) + stake.stakingAmountNumber <=
bigInt2Number(statCollection.kraikenTotalSupply, 18) * 0.2
) {
) {
return [];
}
//Differenz aus outstandingSupply und totalSupply bestimmen, wie viel HARB kann zum Snatch verwendet werden
const difference =
}
//Differenz aus outstandingSupply und totalSupply bestimmen, wie viel HARB kann zum Snatch verwendet werden
const difference =
bigInt2Number(statCollection.outstandingStake, 18) +
stake.stakingAmountNumber -
bigInt2Number(statCollection.kraikenTotalSupply, 18) * 0.2;
console.log("difference", difference);
//Division ohne Rest, um zu schauen wie viele Positionen gesnatched werden könnten
const snatchAblePositionsCount = Math.floor(difference / minStakeAmount.value);
//Division ohne Rest, um zu schauen wie viele Positionen gesnatched werden könnten
const snatchAblePositionsCount = Math.floor(difference / minStakeAmount.value);
//wenn mehr als 0 Positionen gesnatched werden könnten, wird geschaut wie viele Positionen in Frage kommen
if (snatchAblePositionsCount > 0) {
//wenn mehr als 0 Positionen gesnatched werden könnten, wird geschaut wie viele Positionen in Frage kommen
if (snatchAblePositionsCount > 0) {
const snatchAblePositions = activePositions.value.filter((obj: Position) => {
if (ignoreOwner.value) {
return obj.taxRatePercentage < taxRate.value;
}
return obj.taxRatePercentage < taxRate.value && !obj.iAmOwner;
if (ignoreOwner.value) {
return obj.taxRatePercentage < taxRate.value;
}
return obj.taxRatePercentage < taxRate.value && !obj.iAmOwner;
});
const slicedArray = snatchAblePositions.slice(0, snatchAblePositionsCount);
return slicedArray;
}
}
return [];
return [];
});
</script>

View file

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

View file

@ -1,28 +1,24 @@
<template>
<button class="chart-button">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
class="stroke"
d="M10.8027 1.00195H16.002C16.3433 1.00195 16.6201 1.27869 16.6201 1.62006V6.81927"
stroke-width="1.85431"
/>
<path
class="stroke"
d="M16.6191 10.5273L16.6191 15.7266C16.6191 16.0679 16.3424 16.3447 16.001 16.3447L10.8018 16.3447"
stroke-width="1.85431"
/>
<path
class="stroke"
d="M7.0918 16.3457L1.89258 16.3457C1.55121 16.3457 1.27448 16.069 1.27448 15.7276L1.27448 10.5284"
stroke-width="1.85431"
/>
<path
class="stroke"
d="M1.27539 6.81836L1.27539 1.61914C1.27539 1.27777 1.55213 1.00104 1.8935 1.00104L7.09271 1.00104"
stroke-width="1.85431"
/>
</svg>
</button>
<button class="chart-button">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path class="stroke" d="M10.8027 1.00195H16.002C16.3433 1.00195 16.6201 1.27869 16.6201 1.62006V6.81927" stroke-width="1.85431" />
<path
class="stroke"
d="M16.6191 10.5273L16.6191 15.7266C16.6191 16.0679 16.3424 16.3447 16.001 16.3447L10.8018 16.3447"
stroke-width="1.85431"
/>
<path
class="stroke"
d="M7.0918 16.3457L1.89258 16.3457C1.55121 16.3457 1.27448 16.069 1.27448 15.7276L1.27448 10.5284"
stroke-width="1.85431"
/>
<path
class="stroke"
d="M1.27539 6.81836L1.27539 1.61914C1.27539 1.27777 1.55213 1.00104 1.8935 1.00104L7.09271 1.00104"
stroke-width="1.85431"
/>
</svg>
</button>
</template>
<style lang="sass">

View file

@ -1,20 +1,20 @@
<template>
<button class="chart-button">
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="path-1-inside-1_3412_52485" fill="white">
<path
d="M12.9797 15.2305C11.7286 16.2335 10.2045 16.8366 8.60589 16.9613C7.00726 17.086 5.4081 16.7265 4.01664 15.9296C2.62518 15.1327 1.50588 13.9353 0.804464 12.4934C0.103046 11.0515 -0.14799 9.43173 0.0840395 7.84511C0.316069 6.2585 1.02041 4.77849 2.10537 3.59778C3.19033 2.41708 4.60564 1.59038 6.16702 1.22532C7.72841 0.860254 9.36354 0.973741 10.8595 1.551C12.3555 2.12826 13.643 3.14256 14.5545 4.46182L13.109 5.46048C12.3981 4.4315 11.3938 3.64038 10.227 3.19013C9.0602 2.73989 7.78485 2.65137 6.56702 2.93611C5.34918 3.22084 4.24529 3.86565 3.39905 4.78656C2.55282 5.70747 2.00345 6.86183 1.82248 8.09935C1.6415 9.33686 1.8373 10.6002 2.38439 11.7249C2.93147 12.8495 3.80449 13.7835 4.88979 14.405C5.97508 15.0266 7.22237 15.307 8.46926 15.2097C9.71615 15.1124 10.9049 14.642 11.8807 13.8597L12.9797 15.2305Z"
/>
</mask>
<path
d="M12.9797 15.2305C11.7286 16.2335 10.2045 16.8366 8.60589 16.9613C7.00726 17.086 5.4081 16.7265 4.01664 15.9296C2.62518 15.1327 1.50588 13.9353 0.804464 12.4934C0.103046 11.0515 -0.14799 9.43173 0.0840395 7.84511C0.316069 6.2585 1.02041 4.77849 2.10537 3.59778C3.19033 2.41708 4.60564 1.59038 6.16702 1.22532C7.72841 0.860254 9.36354 0.973741 10.8595 1.551C12.3555 2.12826 13.643 3.14256 14.5545 4.46182L13.109 5.46048C12.3981 4.4315 11.3938 3.64038 10.227 3.19013C9.0602 2.73989 7.78485 2.65137 6.56702 2.93611C5.34918 3.22084 4.24529 3.86565 3.39905 4.78656C2.55282 5.70747 2.00345 6.86183 1.82248 8.09935C1.6415 9.33686 1.8373 10.6002 2.38439 11.7249C2.93147 12.8495 3.80449 13.7835 4.88979 14.405C5.97508 15.0266 7.22237 15.307 8.46926 15.2097C9.71615 15.1124 10.9049 14.642 11.8807 13.8597L12.9797 15.2305Z"
stroke="white"
stroke-width="3.54886"
mask="url(#path-1-inside-1_3412_52485)"
/>
<path d="M16.0005 6.89955L13.89 0.404049L9.32 5.47956L16.0005 6.89955Z" fill="white" />
</svg>
</button>
<button class="chart-button">
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="path-1-inside-1_3412_52485" fill="white">
<path
d="M12.9797 15.2305C11.7286 16.2335 10.2045 16.8366 8.60589 16.9613C7.00726 17.086 5.4081 16.7265 4.01664 15.9296C2.62518 15.1327 1.50588 13.9353 0.804464 12.4934C0.103046 11.0515 -0.14799 9.43173 0.0840395 7.84511C0.316069 6.2585 1.02041 4.77849 2.10537 3.59778C3.19033 2.41708 4.60564 1.59038 6.16702 1.22532C7.72841 0.860254 9.36354 0.973741 10.8595 1.551C12.3555 2.12826 13.643 3.14256 14.5545 4.46182L13.109 5.46048C12.3981 4.4315 11.3938 3.64038 10.227 3.19013C9.0602 2.73989 7.78485 2.65137 6.56702 2.93611C5.34918 3.22084 4.24529 3.86565 3.39905 4.78656C2.55282 5.70747 2.00345 6.86183 1.82248 8.09935C1.6415 9.33686 1.8373 10.6002 2.38439 11.7249C2.93147 12.8495 3.80449 13.7835 4.88979 14.405C5.97508 15.0266 7.22237 15.307 8.46926 15.2097C9.71615 15.1124 10.9049 14.642 11.8807 13.8597L12.9797 15.2305Z"
/>
</mask>
<path
d="M12.9797 15.2305C11.7286 16.2335 10.2045 16.8366 8.60589 16.9613C7.00726 17.086 5.4081 16.7265 4.01664 15.9296C2.62518 15.1327 1.50588 13.9353 0.804464 12.4934C0.103046 11.0515 -0.14799 9.43173 0.0840395 7.84511C0.316069 6.2585 1.02041 4.77849 2.10537 3.59778C3.19033 2.41708 4.60564 1.59038 6.16702 1.22532C7.72841 0.860254 9.36354 0.973741 10.8595 1.551C12.3555 2.12826 13.643 3.14256 14.5545 4.46182L13.109 5.46048C12.3981 4.4315 11.3938 3.64038 10.227 3.19013C9.0602 2.73989 7.78485 2.65137 6.56702 2.93611C5.34918 3.22084 4.24529 3.86565 3.39905 4.78656C2.55282 5.70747 2.00345 6.86183 1.82248 8.09935C1.6415 9.33686 1.8373 10.6002 2.38439 11.7249C2.93147 12.8495 3.80449 13.7835 4.88979 14.405C5.97508 15.0266 7.22237 15.307 8.46926 15.2097C9.71615 15.1124 10.9049 14.642 11.8807 13.8597L12.9797 15.2305Z"
stroke="white"
stroke-width="3.54886"
mask="url(#path-1-inside-1_3412_52485)"
/>
<path d="M16.0005 6.89955L13.89 0.404049L9.32 5.47956L16.0005 6.89955Z" fill="white" />
</svg>
</button>
</template>
<style lang="sass">

View file

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

View file

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

View file

@ -1,48 +1,49 @@
<template>
<button class="f-btn" :class="classObject" :style="styleObject">
<slot></slot>
</button>
<button class="f-btn" :class="classObject" :style="styleObject">
<slot></slot>
</button>
</template>
<script setup lang="ts">
interface Props {
size?: string;
dense?: boolean;
disabled?: boolean;
invert?: boolean;
block?: boolean;
outlined?: boolean;
bgColor?: string;
light?: boolean;
dark?: boolean;
size?: string;
dense?: boolean;
disabled?: boolean;
invert?: boolean;
block?: boolean;
outlined?: boolean;
bgColor?: string;
light?: boolean;
dark?: boolean;
}
import { computed } from "vue";
import { computed } from 'vue';
const props = withDefaults(defineProps<Props>(), {
size: "medium",
size: 'medium',
bgColor: '',
});
const classObject = computed(() => ({
"f-btn--tiny": props.size === "tiny",
"f-btn--small": props.size === "small",
"f-btn--medium": props.size === "medium",
"f-btn--large": props.size === "large",
"f-btn--dense": props.dense,
"f-btn--disabled": props.disabled,
"f-btn--invert": props.invert,
"f-btn--block": props.block,
"f-btn--outlined": props.outlined,
"f-btn--light": props.light,
"f-btn--dark": props.dark,
'f-btn--tiny': props.size === 'tiny',
'f-btn--small': props.size === 'small',
'f-btn--medium': props.size === 'medium',
'f-btn--large': props.size === 'large',
'f-btn--dense': props.dense,
'f-btn--disabled': props.disabled,
'f-btn--invert': props.invert,
'f-btn--block': props.block,
'f-btn--outlined': props.outlined,
'f-btn--light': props.light,
'f-btn--dark': props.dark,
}));
const styleObject = computed(() => {
const returnObject: any = {};
if (props.bgColor) {
returnObject["background-color"] = props.bgColor;
}
return returnObject;
const returnObject: Record<string, string> = {};
if (props.bgColor) {
returnObject['background-color'] = props.bgColor;
}
return returnObject;
});
</script>

View file

@ -1,40 +1,45 @@
<template>
<div class="f-card" ref="fCard" :style="computedStyles">
<div v-if="props.title" class="f-card__title">
<h5>
{{ props.title }}
</h5>
</div>
<div class="f-card__body">
<slot></slot>
</div>
</div>
<div class="f-card" ref="fCard" :style="computedStyles">
<div v-if="props.title" class="f-card__title">
<h5>
{{ props.title }}
</h5>
</div>
<div class="f-card__body">
<slot></slot>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, computed } from "vue";
import { onMounted, ref, computed } from 'vue';
const fCard = ref();
interface Props {
bgColor?: string;
borderWidth?: number;
boxShadow?: string;
title?: string;
bgColor?: string;
borderWidth?: number;
boxShadow?: string;
title?: string;
}
const props = withDefaults(defineProps<Props>(), {});
const props = withDefaults(defineProps<Props>(), {
bgColor: '',
borderWidth: undefined,
boxShadow: '',
title: '',
});
const computedStyles = computed(() => ({
"border-width": props.borderWidth,
"background-color": props.bgColor,
"box-shadow": props.boxShadow,
'border-width': props.borderWidth,
'background-color': props.bgColor,
'box-shadow': props.boxShadow,
}));
onMounted(() => {
// if (props.bgcolor) {
// fCard.value.style["background-color"] = props.bgcolor;
// }
// if (props.bgcolor) {
// fCard.value.style["background-color"] = props.bgcolor;
// }
});
</script>

View file

@ -1,50 +1,55 @@
<template>
<div class="f-collapse">
<div class="f-collapse-wrapper" :class="{loading: props.loading}">
<template v-if="loading">
<f-loader></f-loader>
</template>
<div class="f-collapse-inner">
<slot name="header"></slot>
<!-- <img class="toggle-collapse" src="../assets/expand-less.svg?url" alt="expand less" /> -->
<Icon v-if="isShow" class="toggle-collapse" icon="mdi:chevron-down" @click="openClose"></Icon>
<Icon v-else icon="mdi:chevron-up" class="toggle-collapse" @click="openClose"></Icon>
<!-- <img
<div class="f-collapse">
<div class="f-collapse-wrapper" :class="{ loading: props.loading }">
<template v-if="loading">
<FLoader></FLoader>
</template>
<div class="f-collapse-inner">
<slot name="header"></slot>
<!-- <img class="toggle-collapse" src="../assets/expand-less.svg?url" alt="expand less" /> -->
<Icon v-if="isShow" class="toggle-collapse" icon="mdi:chevron-down" @click="openClose"></Icon>
<Icon v-else icon="mdi:chevron-up" class="toggle-collapse" @click="openClose"></Icon>
<!-- <img
class="toggle-collapse"
src="../assets/expand-more.svg?url"
alt="expand more"
v-else
/> -->
</div>
<Transition name="collapse">
<div v-if="isShow" class="collapsableDiv">
<slot></slot>
</div>
</Transition>
</div>
<Transition name="collapse">
<div v-if="isShow" class="collapsableDiv">
<slot></slot>
</div>
</div>
</Transition>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import {Icon} from "@iconify/vue";
import FLoader from "@/components/fcomponents/FLoader.vue";
import { ref } from 'vue';
import { Icon } from '@iconify/vue';
import FLoader from '@/components/fcomponents/FLoader.vue';
const emit = defineEmits(["collapse:opened", "collapse:closed"]);
interface Emits {
(event: 'collapse:opened'): void;
(event: 'collapse:closed'): void;
}
const emit = defineEmits<Emits>();
const props = defineProps<{
loading?: boolean;
loading?: boolean;
}>();
const isShow = ref(false);
const isShow = ref<boolean>(false);
const openClose = () => {
isShow.value = !isShow.value;
if(isShow.value){
emit("collapse:opened")
} else{
emit("collapse:closed")
}
isShow.value = !isShow.value;
if (isShow.value) {
emit('collapse:opened');
} else {
emit('collapse:closed');
}
};
</script>
<style lang="sass">
@ -92,6 +97,4 @@ const openClose = () => {
to
max-height: 0px
overflow: hidden
</style>

View file

@ -1,48 +1,47 @@
<template>
<div class="f-input" :class="classObject">
<div class="f-input-label subheader2">
<label v-if="props.label" :for="name">{{ props.label }}</label>
<icon>
<template v-slot:text v-if="slots.info">
<slot name="info"></slot>
</template>
</icon>
</div>
<div class="f-input__wrapper" ref="inputWrapper" @click="setFocus">
<input
:disabled="props.disabled"
:readonly="props.readonly"
:type="props.type"
:name="name"
:id="name"
@input="updateModelValue"
:value="props.modelValue"
/>
<div class="f-input--suffix" v-if="slots.suffix">
<slot name="suffix" >test </slot>
</div>
</div>
<div class="f-input--details" v-if="slots.details">
<slot name="details"> </slot>
</div>
</div>
<div class="f-input" :class="classObject">
<div class="f-input-label subheader2">
<label v-if="props.label" :for="name">{{ props.label }}</label>
<Icon>
<template v-slot:text v-if="slots.info">
<slot name="info"></slot>
</template>
</Icon>
</div>
<div class="f-input__wrapper" ref="inputWrapper" @click="setFocus">
<input
:disabled="props.disabled"
:readonly="props.readonly"
:type="props.type"
:name="name"
:id="name"
@input="updateModelValue"
:value="props.modelValue"
/>
<div class="f-input--suffix" v-if="slots.suffix">
<slot name="suffix">test </slot>
</div>
</div>
<div class="f-input--details" v-if="slots.details">
<slot name="details"> </slot>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, getCurrentInstance, useSlots, ref } from "vue";
import useClickOutside from "@/composables/useClickOutside"
import { computed, getCurrentInstance, useSlots, ref } from 'vue';
import useClickOutside from '@/composables/useClickOutside';
import Icon from "@/components/icons/IconInfo.vue";
import Icon from '@/components/icons/IconInfo.vue';
interface Props {
size?: string;
info?: string;
label?: string;
disabled?: boolean;
modelValue?: any;
type?: string;
readonly?: boolean
size?: string;
info?: string;
label?: string;
disabled?: boolean;
modelValue?: string | number;
type?: string;
readonly?: boolean;
}
const slots = useSlots();
@ -53,40 +52,47 @@ const instance = getCurrentInstance();
const name = `f-input-${instance!.uid}`;
const props = withDefaults(defineProps<Props>(), {
size: "normal",
disabled: false,
readonly: false,
type: "string",
size: 'normal',
disabled: false,
readonly: false,
type: 'string',
info: '',
label: '',
modelValue: '',
});
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) {
emit("update:modelValue", $event.target.value);
function updateModelValue($event: Event) {
const target = $event.target as HTMLInputElement;
emit('update:modelValue', target.value);
}
const classObject = computed(() => ({
"f-input--normal": props.size === "normal",
"f-input--big": props.size === "big",
"f-input--disabled": props.disabled,
'f-input--normal': props.size === 'normal',
'f-input--big': props.size === 'big',
'f-input--disabled': props.disabled,
}));
function removeFocus(){
const target = inputWrapper.value as HTMLElement
target.classList.remove("f-input__wrapper--focused")
function removeFocus() {
const target = inputWrapper.value as HTMLElement;
target.classList.remove('f-input__wrapper--focused');
}
function setFocus(event:MouseEvent){
console.log("setFocus");
if(props.disabled){
return
}
const target = inputWrapper.value as HTMLElement
target.classList.add("f-input__wrapper--focused")
function setFocus(_event: MouseEvent) {
// console.log("setFocus");
if (props.disabled) {
return;
}
const target = inputWrapper.value as HTMLElement;
target.classList.add('f-input__wrapper--focused');
}
</script>
<style lang="sass">
@ -109,7 +115,7 @@ function setFocus(event:MouseEvent){
background-color: #2D2D2D
&.f-input__wrapper--focused
outline: none
border: 1px solid #7550AE
border: 1px solid #7550AE
box-shadow: 0 0 5px #7550AE
input
border: 0
@ -131,5 +137,4 @@ function setFocus(event:MouseEvent){
display: flex
align-items: center
margin-right: 10px
</style>

View file

@ -1,12 +1,12 @@
<template>
<div class="cube">
<div class="side"></div>
<div class="side"></div>
<div class="side"></div>
<div class="side"></div>
<div class="side"></div>
<div class="side"></div>
</div>
<div class="cube">
<div class="side"></div>
<div class="side"></div>
<div class="side"></div>
<div class="side"></div>
<div class="side"></div>
<div class="side"></div>
</div>
</template>
<style lang="sass">

View file

@ -1,37 +1,38 @@
<template>
<div
class="output-wrapper"
:class="{
'no-name': !props.name,
'output--variant-1': props.variant === 1,
'output--variant-2': props.variant === 2,
'output--variant-3': props.variant === 3,
}"
>
<div class="output-left">
<div class="output-text">{{ props.name }}</div>
<div class="output-price">
<slot name="price">
{{ props.price }}
</slot>
</div>
</div>
<div class="output-right" v-if="slots.end">
<slot name="end"></slot>
</div>
</div>
<div
class="output-wrapper"
:class="{
'no-name': !props.name,
'output--variant-1': props.variant === 1,
'output--variant-2': props.variant === 2,
'output--variant-3': props.variant === 3,
}"
>
<div class="output-left">
<div class="output-text">{{ props.name }}</div>
<div class="output-price">
<slot name="price">
{{ props.price }}
</slot>
</div>
</div>
<div class="output-right" v-if="slots.end">
<slot name="end"></slot>
</div>
</div>
</template>
<script setup lang="ts">
import { useSlots } from "vue";
const props = defineProps({
name: String,
price: [String, Number],
variant: {
type: Number,
required: false,
default: 1,
},
import { useSlots, withDefaults } from 'vue';
interface Props {
name?: string;
price?: string | number;
variant?: number;
}
const props = withDefaults(defineProps<Props>(), {
variant: 1,
name: '',
price: '',
});
const slots = useSlots();

View file

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

View file

@ -1,148 +1,158 @@
<template>
<div>
<div class="slider-wrapper">
<div class="disabled-slider" :style="{ 'flex-basis': minPercentage + '%' }">
<div class="dot"></div>
</div>
<div class="range-slider">
<input
class="range-slider__range"
type="range"
ref="sliderInput"
:style="{
background: `linear-gradient(90deg, ${settings.fill} ${percentageDot}%, ${settings.background} ${percentageDot + 0.1}%)`,
}"
:value="props.modelValue"
:min="props.min"
:max="props.max"
@input="updateModelValue"
/>
<div
class="testbla"
@mousemove="testMove"
:style="{
left: percentageDot + '%',
}"
></div>
</div>
</div>
</div>
<div>
<div class="slider-wrapper">
<div class="disabled-slider" :style="{ 'flex-basis': minPercentage + '%' }">
<div class="dot"></div>
</div>
<div class="range-slider">
<input
class="range-slider__range"
type="range"
ref="sliderInput"
:style="{
background: `linear-gradient(90deg, ${settings.fill} ${percentageDot}%, ${settings.background} ${percentageDot + 0.1}%)`,
}"
:value="props.modelValue"
:min="props.min"
:max="props.max"
@input="updateModelValue"
/>
<div
class="testbla"
@mousemove="testMove"
:style="{
left: percentageDot + '%',
}"
></div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, ref, computed } from "vue";
import { onMounted, onUnmounted, ref, computed } from 'vue';
const sliderInput = ref();
const sliderInput = ref<HTMLInputElement | null>(null);
const emit = defineEmits(["update:modelValue"]);
interface Emits {
(event: 'update:modelValue', value: number): void;
}
const emit = defineEmits<Emits>();
const props = defineProps<{
modelValue: number;
min: number;
max: number;
modelValue: number;
min: number;
max: number;
}>();
const minPercentage = computed(() => {
if (!props.min || !props.max) {
return 0n;
}
return (props.min * 100) / props.max;
if (!props.min || !props.max) {
return 0;
}
return (props.min * 100) / props.max;
});
function updateModelValue(event: any) {
emit("update:modelValue", event.target.valueAsNumber);
function updateModelValue(event: Event) {
emit('update:modelValue', (event.target as HTMLInputElement).valueAsNumber);
}
const sliderValue = computed({
// getter
get() {
return props.modelValue || props.min;
},
// setter
set(newValue) {
emit("update:modelValue", newValue);
},
// getter
get() {
return Number.isFinite(props.modelValue) ? props.modelValue : props.min;
},
// setter
set(newValue: number) {
emit('update:modelValue', newValue);
},
});
const percentageDot = computed(() => {
let percentage = (100 * (sliderValue.value - props.min)) / (props.max - props.min);
if(percentage < 20){
percentage = percentage + 1;
} else if(percentage > 50){
percentage = percentage - 2;
} else if(percentage > 80){
percentage = percentage - 3;
}
return percentage
const range = props.max - props.min;
if (range <= 0) {
return 0;
}
let percentage = (100 * (sliderValue.value - props.min)) / range;
if (percentage < 20) {
percentage = percentage + 1;
} else if (percentage > 50) {
percentage = percentage - 2;
} else if (percentage > 80) {
percentage = percentage - 3;
}
return percentage;
});
const settings = {
fill: "#7550AE",
background: "#000000",
fill: '#7550AE',
background: '#000000',
};
onMounted(() => {
sliderInput.value.addEventListener("input", setSliderValue);
if (sliderInput.value) {
sliderInput.value.addEventListener('input', setSliderValue);
}
});
function setSliderValue(event: any){
sliderValue.value = event.target.value;
function setSliderValue(event: Event) {
const target = event.target as HTMLInputElement | null;
if (target) {
sliderValue.value = target.valueAsNumber;
}
}
function testMove(event: any) {
}
function testMove(_event: unknown) {}
onUnmounted(() => {
console.log("sliderInput.value", sliderInput.value);
if(sliderInput.value){
sliderInput.value.removeEventListener("input", setSliderValue);
}
})
if (sliderInput.value) {
sliderInput.value.removeEventListener('input', setSliderValue);
}
});
</script>
<style lang="scss">
.slider-wrapper {
display: flex;
display: flex;
}
.testbla {
position: absolute;
height: 25px;
width: 25px;
background-color: var(--color-based-blue);
user-select: none;
top: 50%;
transform: translate(-50%, -50%);
right: 50%;
border-radius: 25px;
pointer-events: none;
z-index: 10;
position: absolute;
height: 25px;
width: 25px;
background-color: var(--color-based-blue);
user-select: none;
top: 50%;
transform: translate(-50%, -50%);
right: 50%;
border-radius: 25px;
pointer-events: none;
z-index: 10;
}
.disabled-slider {
width: 60px;
height: 7px;
background-color: var(--color-border-main);
/* margin-top: auto; */
align-self: center;
margin-top: 2px;
position: relative;
z-index: 3;
pointer-events: none;
width: 60px;
height: 7px;
background-color: var(--color-border-main);
/* margin-top: auto; */
align-self: center;
margin-top: 2px;
position: relative;
z-index: 3;
pointer-events: none;
}
.dot {
border-radius: 50px;
height: 15px;
width: 15px;
background-color: var(--color-border-main);
position: absolute;
right: -14px;
/* bottom: 50%; */
top: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
border-radius: 50px;
height: 15px;
width: 15px;
background-color: var(--color-border-main);
position: absolute;
right: -14px;
/* bottom: 50%; */
top: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
}
// Base Colors
$shade-10: #4682b4 !default;
@ -164,96 +174,96 @@ $range-label-color: $shade-10 !default;
$range-label-width: 60px !default;
.range-slider {
width: $range-width;
position: relative;
accent-color: #7550AE;
width: $range-width;
position: relative;
accent-color: #7550ae;
}
.range-slider__range {
// width: calc(100% - (#{$range-label-width + 13px}));
width: 100%;
height: $range-track-height;
border-radius: 5px;
background: $range-track-color;
outline: none;
padding: 0;
margin: 0;
// width: calc(100% - (#{$range-label-width + 13px}));
width: 100%;
height: $range-track-height;
border-radius: 5px;
background: $range-track-color;
outline: none;
padding: 0;
margin: 0;
// Range Handle
&::-webkit-slider-thumb {
appearance: none;
width: 30px;
height: 30px;
opacity: 0;
padding: 5px;
border-radius: 50%;
cursor: pointer;
}
// Range Handle
&::-webkit-slider-thumb {
appearance: none;
width: 30px;
height: 30px;
opacity: 0;
padding: 5px;
border-radius: 50%;
cursor: pointer;
}
&:active::-webkit-slider-thumb {
background: $range-handle-color-hover;
}
&:active::-webkit-slider-thumb {
background: $range-handle-color-hover;
}
&::-moz-range-thumb {
width: $range-handle-size;
height: $range-handle-size;
border: 0;
border-radius: 50%;
background: $range-handle-color;
cursor: pointer;
transition: background 0.15s ease-in-out;
&::-moz-range-thumb {
width: $range-handle-size;
height: $range-handle-size;
border: 0;
border-radius: 50%;
background: $range-handle-color;
cursor: pointer;
transition: background 0.15s ease-in-out;
&:hover {
background: $range-handle-color-hover;
}
}
&:hover {
background: $range-handle-color-hover;
}
}
&:active::-moz-range-thumb {
background: $range-handle-color-hover;
}
&:active::-moz-range-thumb {
background: $range-handle-color-hover;
}
// Focus state
&:focus {
&::-webkit-slider-thumb {
background-color: transparent;
}
}
// Focus state
&:focus {
&::-webkit-slider-thumb {
background-color: transparent;
}
}
}
// Range Label
.range-slider__value {
display: inline-block;
position: relative;
width: $range-label-width;
color: $shade-0;
line-height: 20px;
text-align: center;
border-radius: 3px;
background: $range-label-color;
padding: 5px 10px;
margin-left: 8px;
display: inline-block;
position: relative;
width: $range-label-width;
color: $shade-0;
line-height: 20px;
text-align: center;
border-radius: 3px;
background: $range-label-color;
padding: 5px 10px;
margin-left: 8px;
&:after {
position: absolute;
top: 8px;
left: -7px;
width: 0;
height: 0;
border-top: 7px solid transparent;
border-right: 7px solid $range-label-color;
border-bottom: 7px solid transparent;
content: "";
}
&:after {
position: absolute;
top: 8px;
left: -7px;
width: 0;
height: 0;
border-top: 7px solid transparent;
border-right: 7px solid $range-label-color;
border-bottom: 7px solid transparent;
content: '';
}
}
// Firefox Overrides
::-moz-range-track {
background: $range-track-color;
border: 0;
background: $range-track-color;
border: 0;
}
input::-moz-focus-inner,
input::-moz-focus-outer {
border: 0;
border: 0;
}
</style>

View file

@ -1,7 +1,7 @@
<template>
<div class="f-tag">
<slot></slot>
</div>
<div class="f-tag">
<slot></slot>
</div>
</template>
<style lang="sass">

View file

@ -1,34 +1,41 @@
<template>
<section v-show="value === props.name" ref="tab" role="tabpanel" tabindex="-1">
<slot></slot>
</section>
<section v-show="value === props.name" ref="tab" role="tabpanel" tabindex="-1">
<slot></slot>
</section>
</template>
<script setup lang="ts">
import { onMounted, onBeforeMount, getCurrentInstance, ref, computed, watch, inject } from "vue";
const instance = ref<any>();
import { onMounted, onBeforeMount, getCurrentInstance, ref, watch, inject, type ComponentInternalInstance } from 'vue';
const isActive = ref<any>(null);
interface Tab {
uid: number;
name: string;
label: string;
}
const value = inject("value");
var updateTab: any = inject("updateTab");
const instance = ref<ComponentInternalInstance | null>();
const isActive = ref<unknown>(null);
const value = inject('value');
const updateTab: (tab: Tab) => void = inject('updateTab') as (tab: Tab) => void;
defineExpose({
isActive,
isActive,
});
onBeforeMount(() => {});
onMounted(() => {
instance.value = getCurrentInstance();
instance.value = getCurrentInstance();
var addTab: any = inject("addTab");
addTab({
uid: instance.value!.uid,
name: props.name,
label: props.label,
});
const addTab = inject('addTab') as (tab: Tab) => void;
addTab({
uid: instance.value?.uid ?? 0,
name: props.name,
label: props.label,
});
// resolved = {"test123": 123, "blub": "abc"}
// resolved = {"test123": 123, "blub": "abc"}
});
// const test = computed(() => {
@ -38,27 +45,20 @@ onMounted(() => {
// })
//Props
const props = defineProps({
label: {
type: String,
required: true,
default: null,
},
name: {
type: [String],
required: true,
default: null,
},
});
interface Props {
label: string;
name: string;
}
const props = defineProps<Props>();
watch(props, (newValue, oldValue) => {
console.log("newValue", newValue);
console.log("instance", instance.value);
watch(props, (_newValue, _oldValue) => {
// console.log("newValue", newValue);
// console.log("instance", instance.value);
updateTab({
uid: instance.value!.uid,
name: props.name,
label: props.label,
});
updateTab({
uid: instance.value?.uid ?? 0,
name: props.name,
label: props.label,
});
});
</script>

View file

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

View file

@ -41,10 +41,9 @@ describe('FTabs.vue', () => {
await wrapper.vm.$nextTick();
const sections = wrapper.findAll('section');
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');
});
it('switches to the correct tab on click', async () => {
@ -66,7 +65,7 @@ describe('FTabs.vue', () => {
await tabs[1].trigger('click');
const sections = wrapper.findAll('section');
expect(sections[0].attributes('style')).toContain('display: none');
expect(sections[0].attributes('style')).toContain('display: none');
expect(sections[1].attributes('style')).not.toContain('display: none');
});
});

File diff suppressed because one or more lines are too long

View file

@ -1,29 +1,30 @@
<template>
<svg width="23" height="18" viewBox="0 0 23 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2590_494)">
<path
ref="svgPath"
d="M19.419 3.40166C16.7691 1.45664 14.2483 1.51053 14.2483 1.51053L13.9906 1.79865C17.1188 2.73512 18.5723 4.08593 18.5723 4.08593C16.6585 3.05941 14.7817 2.55501 13.0336 2.35694C11.7088 2.21276 10.4391 2.24892 9.3167 2.39287C9.20634 2.39287 9.11433 2.41083 9.00397 2.42879C8.35994 2.48292 6.79584 2.71692 4.82702 3.56357C4.14628 3.86966 3.74131 4.08593 3.74131 4.08593C3.74131 4.08593 5.2687 2.66303 8.58065 1.72656L8.39664 1.51053C8.39664 1.51053 5.87579 1.4564 3.22598 3.40166C3.22598 3.40166 0.576172 8.10242 0.576172 13.9018C0.576172 13.9018 2.12191 16.5134 6.18851 16.6393C6.18851 16.6393 6.86925 15.8289 7.42129 15.1446C5.08444 14.4601 4.20109 13.0195 4.20109 13.0195C4.20109 13.0195 4.3851 13.1454 4.71642 13.3256C4.73477 13.3435 4.75313 13.3615 4.79007 13.3797C4.84537 13.4156 4.90043 13.4338 4.95573 13.4697C5.41576 13.7219 5.87579 13.92 6.29911 14.0821C7.05351 14.3703 7.95521 14.6584 9.00397 14.8565C10.3841 15.1087 12.0032 15.1987 13.7697 14.8744C14.6344 14.7303 15.5178 14.4783 16.4378 14.0999C17.0819 13.8656 17.7996 13.5236 18.554 13.0372C18.554 13.0372 17.6339 14.514 15.2234 15.1805C15.7754 15.865 16.4378 16.6393 16.4378 16.6393C20.5047 16.5131 22.0688 13.9018 22.0688 13.9018C22.0688 8.10242 19.419 3.40166 19.419 3.40166ZM7.8818 12.2267C6.85138 12.2267 6.00498 11.3262 6.00498 10.2276C6.00498 9.12894 6.83303 8.22841 7.8818 8.22841C8.93056 8.22841 9.77697 9.12894 9.75861 10.2276C9.75861 11.3262 8.93056 12.2267 7.8818 12.2267ZM14.598 12.2267C13.5675 12.2267 12.7211 11.3262 12.7211 10.2276C12.7211 9.12894 13.5492 8.22841 14.598 8.22841C15.6467 8.22841 16.4748 9.12894 16.4748 10.2276C16.4748 11.3262 15.647 12.2267 14.598 12.2267Z"
:fill="props.color"
/>
</g>
<defs>
<clipPath id="clip0_2590_494">
<rect width="22" height="17" fill="white" transform="translate(0.333984 0.58667)" />
</clipPath>
</defs>
</svg>
<svg width="23" height="18" viewBox="0 0 23 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2590_494)">
<path
ref="svgPath"
d="M19.419 3.40166C16.7691 1.45664 14.2483 1.51053 14.2483 1.51053L13.9906 1.79865C17.1188 2.73512 18.5723 4.08593 18.5723 4.08593C16.6585 3.05941 14.7817 2.55501 13.0336 2.35694C11.7088 2.21276 10.4391 2.24892 9.3167 2.39287C9.20634 2.39287 9.11433 2.41083 9.00397 2.42879C8.35994 2.48292 6.79584 2.71692 4.82702 3.56357C4.14628 3.86966 3.74131 4.08593 3.74131 4.08593C3.74131 4.08593 5.2687 2.66303 8.58065 1.72656L8.39664 1.51053C8.39664 1.51053 5.87579 1.4564 3.22598 3.40166C3.22598 3.40166 0.576172 8.10242 0.576172 13.9018C0.576172 13.9018 2.12191 16.5134 6.18851 16.6393C6.18851 16.6393 6.86925 15.8289 7.42129 15.1446C5.08444 14.4601 4.20109 13.0195 4.20109 13.0195C4.20109 13.0195 4.3851 13.1454 4.71642 13.3256C4.73477 13.3435 4.75313 13.3615 4.79007 13.3797C4.84537 13.4156 4.90043 13.4338 4.95573 13.4697C5.41576 13.7219 5.87579 13.92 6.29911 14.0821C7.05351 14.3703 7.95521 14.6584 9.00397 14.8565C10.3841 15.1087 12.0032 15.1987 13.7697 14.8744C14.6344 14.7303 15.5178 14.4783 16.4378 14.0999C17.0819 13.8656 17.7996 13.5236 18.554 13.0372C18.554 13.0372 17.6339 14.514 15.2234 15.1805C15.7754 15.865 16.4378 16.6393 16.4378 16.6393C20.5047 16.5131 22.0688 13.9018 22.0688 13.9018C22.0688 8.10242 19.419 3.40166 19.419 3.40166ZM7.8818 12.2267C6.85138 12.2267 6.00498 11.3262 6.00498 10.2276C6.00498 9.12894 6.83303 8.22841 7.8818 8.22841C8.93056 8.22841 9.77697 9.12894 9.75861 10.2276C9.75861 11.3262 8.93056 12.2267 7.8818 12.2267ZM14.598 12.2267C13.5675 12.2267 12.7211 11.3262 12.7211 10.2276C12.7211 9.12894 13.5492 8.22841 14.598 8.22841C15.6467 8.22841 16.4748 9.12894 16.4748 10.2276C16.4748 11.3262 15.647 12.2267 14.598 12.2267Z"
:fill="props.color"
/>
</g>
<defs>
<clipPath id="clip0_2590_494">
<rect width="22" height="17" fill="white" transform="translate(0.333984 0.58667)" />
</clipPath>
</defs>
</svg>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import { ref } from 'vue';
const svgPath = ref();
interface Props {
color?: string;
color?: string;
}
const props = withDefaults(defineProps<Props>(), {});
const props = withDefaults(defineProps<Props>(), {
color: 'currentColor',
});
</script>

View file

@ -1,8 +1,8 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<!-- Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc. -->
<path
d="M96 0C43 0 0 43 0 96L0 416c0 53 43 96 96 96l288 0 32 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l0-64c17.7 0 32-14.3 32-32l0-320c0-17.7-14.3-32-32-32L384 0 96 0zm0 384l256 0 0 64L96 448c-17.7 0-32-14.3-32-32s14.3-32 32-32zm32-240c0-8.8 7.2-16 16-16l192 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-192 0c-8.8 0-16-7.2-16-16zm16 48l192 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-192 0c-8.8 0-16-7.2-16-16s7.2-16 16-16z"
/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<!-- Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc. -->
<path
d="M96 0C43 0 0 43 0 96L0 416c0 53 43 96 96 96l288 0 32 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l0-64c17.7 0 32-14.3 32-32l0-320c0-17.7-14.3-32-32-32L384 0 96 0zm0 384l256 0 0 64L96 448c-17.7 0-32-14.3-32-32s14.3-32 32-32zm32-240c0-8.8 7.2-16 16-16l192 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-192 0c-8.8 0-16-7.2-16-16zm16 48l192 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-192 0c-8.8 0-16-7.2-16-16s7.2-16 16-16z"
/>
</svg>
</template>

View file

@ -1,7 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<path
d="M575.8 255.5c0 18-15 32.1-32 32.1h-32l.7 160.2c0 2.7-.2 5.4-.5 8.1V472c0 22.1-17.9 40-40 40H456c-1.1 0-2.2 0-3.3-.1c-1.4 .1-2.8 .1-4.2 .1H416 392c-22.1 0-40-17.9-40-40V448 384c0-17.7-14.3-32-32-32H256c-17.7 0-32 14.3-32 32v64 24c0 22.1-17.9 40-40 40H160 128.1c-1.5 0-3-.1-4.5-.2c-1.2 .1-2.4 .2-3.6 .2H104c-22.1 0-40-17.9-40-40V360c0-.9 0-1.9 .1-2.8V287.6H32c-18 0-32-14-32-32.1c0-9 3-17 10-24L266.4 8c7-7 15-8 22-8s15 2 21 7L564.8 231.5c8 7 12 15 11 24z"
/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<path
d="M575.8 255.5c0 18-15 32.1-32 32.1h-32l.7 160.2c0 2.7-.2 5.4-.5 8.1V472c0 22.1-17.9 40-40 40H456c-1.1 0-2.2 0-3.3-.1c-1.4 .1-2.8 .1-4.2 .1H416 392c-22.1 0-40-17.9-40-40V448 384c0-17.7-14.3-32-32-32H256c-17.7 0-32 14.3-32 32v64 24c0 22.1-17.9 40-40 40H160 128.1c-1.5 0-3-.1-4.5-.2c-1.2 .1-2.4 .2-3.6 .2H104c-22.1 0-40-17.9-40-40V360c0-.9 0-1.9 .1-2.8V287.6H32c-18 0-32-14-32-32.1c0-9 3-17 10-24L266.4 8c7-7 15-8 22-8s15 2 21 7L564.8 231.5c8 7 12 15 11 24z"
/>
</svg>
</template>

View file

@ -1,42 +1,39 @@
<template>
<tippy v-if="slots.text" theme="my-theme" trigger="click">
<div class="info-icon">
<svg :width="props.size" :height="props.size" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M6.2 6.26416C6.2 5.84995 6.53579 5.51416 6.95 5.51416C7.36421 5.51416 7.7 5.84995 7.7 6.26416V9.76416C7.7 10.1784 7.36421 10.5142 6.95 10.5142C6.53579 10.5142 6.2 10.1784 6.2 9.76416V6.26416Z"
/>
<path
d="M7 3.01416C7.55228 3.01416 8 3.46188 8 4.01416C8 4.56644 7.55228 5.01416 7 5.01416C6.44772 5.01416 6 4.56644 6 4.01416C6 3.46188 6.44772 3.01416 7 3.01416Z"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M14 7.01416C14 10.8802 10.866 14.0142 7 14.0142C3.13401 14.0142 0 10.8802 0 7.01416C0 3.14817 3.13401 0.0141602 7 0.0141602C10.866 0.0141602 14 3.14817 14 7.01416ZM1.5 7.01416C1.5 10.0517 3.96243 12.5142 7 12.5142C10.0376 12.5142 12.5 10.0517 12.5 7.01416C12.5 3.97659 10.0376 1.51416 7 1.51416C3.96243 1.51416 1.5 3.97659 1.5 7.01416Z"
/>
</svg>
</div>
<template #content>
<slot name="text"></slot>
</template>
</tippy>
<Tippy v-if="slots.text" theme="my-theme" trigger="click">
<div class="info-icon">
<svg :width="props.size" :height="props.size" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M6.2 6.26416C6.2 5.84995 6.53579 5.51416 6.95 5.51416C7.36421 5.51416 7.7 5.84995 7.7 6.26416V9.76416C7.7 10.1784 7.36421 10.5142 6.95 10.5142C6.53579 10.5142 6.2 10.1784 6.2 9.76416V6.26416Z"
/>
<path
d="M7 3.01416C7.55228 3.01416 8 3.46188 8 4.01416C8 4.56644 7.55228 5.01416 7 5.01416C6.44772 5.01416 6 4.56644 6 4.01416C6 3.46188 6.44772 3.01416 7 3.01416Z"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M14 7.01416C14 10.8802 10.866 14.0142 7 14.0142C3.13401 14.0142 0 10.8802 0 7.01416C0 3.14817 3.13401 0.0141602 7 0.0141602C10.866 0.0141602 14 3.14817 14 7.01416ZM1.5 7.01416C1.5 10.0517 3.96243 12.5142 7 12.5142C10.0376 12.5142 12.5 10.0517 12.5 7.01416C12.5 3.97659 10.0376 1.51416 7 1.51416C3.96243 1.51416 1.5 3.97659 1.5 7.01416Z"
/>
</svg>
</div>
<template #content>
<slot name="text"></slot>
</template>
</Tippy>
</template>
<script setup lang="ts">
import { useSlots, ref, onMounted } from "vue";
import { Tippy } from "vue-tippy";
import "tippy.js/dist/tippy.css"; // optional for styling
import { useSlots } from 'vue';
import { Tippy } from 'vue-tippy';
import 'tippy.js/dist/tippy.css'; // optional for styling
const slots = useSlots();
interface Props {
size?: string;
size?: string;
}
const props = withDefaults(defineProps<Props>(), {
size: "15px",
size: '15px',
});
</script>
<style lang="sass">
@ -49,8 +46,8 @@ const props = withDefaults(defineProps<Props>(), {
fill: var(--color-font)
.tippy-arrow
color: rgba(15, 15, 15, 0.9) !important
.tippy-box[data-theme~='my-theme']
background-color: rgba(15, 15, 15, 0.9)
.tippy-box[data-theme~='my-theme']
background-color: rgba(15, 15, 15, 0.9)
height: fit-content
color: white
font-size: 12px
@ -58,5 +55,4 @@ const props = withDefaults(defineProps<Props>(), {
border-radius: var(--border-radius)
// @media (min-width: 768px)
// max-width: 400px
</style>

View file

@ -1,9 +1,9 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" class="login-icon" viewBox="0 0 512 512">
<path
d="M352 96l64 0c17.7 0 32 14.3 32 32l0 256c0 17.7-14.3 32-32 32l-64 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l64 0c53 0 96-43 96-96l0-256c0-53-43-96-96-96l-64 0c-17.7 0-32 14.3-32 32s14.3 32 32 32zm-9.4 182.6c12.5-12.5 12.5-32.8 0-45.3l-128-128c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L242.7 224 32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l210.7 0-73.4 73.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l128-128z"
/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="login-icon" viewBox="0 0 512 512">
<path
d="M352 96l64 0c17.7 0 32 14.3 32 32l0 256c0 17.7-14.3 32-32 32l-64 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l64 0c53 0 96-43 96-96l0-256c0-53-43-96-96-96l-64 0c-17.7 0-32 14.3-32 32s14.3 32 32 32zm-9.4 182.6c12.5-12.5 12.5-32.8 0-45.3l-128-128c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L242.7 224 32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l210.7 0-73.4 73.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l128-128z"
/>
</svg>
</template>
<style lang="sass">

View file

@ -1,33 +1,34 @@
<template>
<svg width="20" height="16" viewBox="0 0 20 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2590_499)">
<path
ref="svgPath"
d="M17.058 0.932576L1.74462 6.83727C0.699293 7.25659 0.705631 7.83977 1.55407 8.09947L5.48317 9.32593L6.83449 13.7733C7.01217 14.2637 6.92458 14.4583 7.43975 14.4583C7.83718 14.4583 8.01274 14.2765 8.2346 14.0608L10.1441 12.2041L14.1166 15.1392C14.8477 15.5426 15.3754 15.3336 15.5575 14.4606L18.1654 2.17133C18.4324 1.10065 17.7574 0.615059 17.058 0.932576Z"
:fill="props.color"
/>
<path
d="M7.45616 13.2757L6.16016 9.01067L16.1361 3.09253L8.76406 10.3509L7.45616 13.2757Z"
:fill="(props.color === 'black' ? 'white' : 'black')"
/>
</g>
<defs>
<clipPath id="clip0_2590_499">
<rect width="18.125" height="14.5" fill="white" transform="translate(0.9375 0.83667)" />
</clipPath>
</defs>
</svg>
<svg width="20" height="16" viewBox="0 0 20 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2590_499)">
<path
ref="svgPath"
d="M17.058 0.932576L1.74462 6.83727C0.699293 7.25659 0.705631 7.83977 1.55407 8.09947L5.48317 9.32593L6.83449 13.7733C7.01217 14.2637 6.92458 14.4583 7.43975 14.4583C7.83718 14.4583 8.01274 14.2765 8.2346 14.0608L10.1441 12.2041L14.1166 15.1392C14.8477 15.5426 15.3754 15.3336 15.5575 14.4606L18.1654 2.17133C18.4324 1.10065 17.7574 0.615059 17.058 0.932576Z"
:fill="props.color"
/>
<path
d="M7.45616 13.2757L6.16016 9.01067L16.1361 3.09253L8.76406 10.3509L7.45616 13.2757Z"
:fill="props.color === 'black' ? 'white' : 'black'"
/>
</g>
<defs>
<clipPath id="clip0_2590_499">
<rect width="18.125" height="14.5" fill="white" transform="translate(0.9375 0.83667)" />
</clipPath>
</defs>
</svg>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import { ref } from 'vue';
const svgPath = ref();
interface Props {
color?: string;
color?: string;
}
const props = withDefaults(defineProps<Props>(), {});
const props = withDefaults(defineProps<Props>(), {
color: 'currentColor',
});
</script>

View file

@ -1,30 +1,31 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="20px"
height="18px"
viewBox="0 0 19 18"
version="1.1"
>
<g id="surface1">
<path
ref="svgPath"
d="M 14.945312 0 L 17.859375 0 L 11.464844 7.636719 L 18.9375 18 L 13.070312 18 L 8.480469 11.703125 L 3.222656 18 L 0.308594 18 L 7.085938 9.832031 L -0.0703125 0 L 5.941406 0 L 10.089844 5.753906 Z M 13.925781 16.207031 L 15.542969 16.207031 L 5.09375 1.726562 L 3.355469 1.726562 Z M 13.925781 16.207031 "
:fill="props.color"
/>
</g>
</svg>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="20px"
height="18px"
viewBox="0 0 19 18"
version="1.1"
>
<g id="surface1">
<path
ref="svgPath"
d="M 14.945312 0 L 17.859375 0 L 11.464844 7.636719 L 18.9375 18 L 13.070312 18 L 8.480469 11.703125 L 3.222656 18 L 0.308594 18 L 7.085938 9.832031 L -0.0703125 0 L 5.941406 0 L 10.089844 5.753906 Z M 13.925781 16.207031 L 15.542969 16.207031 L 5.09375 1.726562 L 3.355469 1.726562 Z M 13.925781 16.207031 "
:fill="props.color"
/>
</g>
</svg>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import { ref } from 'vue';
const svgPath = ref();
interface Props {
color?: string;
color?: string;
}
const props = withDefaults(defineProps<Props>(), {});
const props = withDefaults(defineProps<Props>(), {
color: 'currentColor',
});
</script>

View file

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

View file

@ -1,113 +1,103 @@
<template>
<div class="connect-wallet-wrapper">
<template v-if="status === 'connected'">
<div class="connected-header">
<div class="connected-header-avatar">
<img :src="getBlocky(address!)" alt="avatar" />
</div>
<div class="connected-header-address" :title="address">
{{ getAddressShortName(address!) }}
</div>
<div class="connected-header-logout" @click="() => disconnect()">
<img src="@/assets/logout.svg" alt="Logout" />
</div>
</div>
<h6 class="connected-tokens-headline">Your Tokens</h6>
<div class="connected-tokens">
<f-output name="$KRK" :price="harbAmount" :variant="3">
<template #end>
<f-button size="small" dense
><a :href="chain.chainData?.uniswap" target="_blank">Buy</a></f-button
>
</template>
</f-output>
<f-output name="Staked $KRK" :price="compactNumber(stakedAmount)" :variant="3">
<template #end>
<f-button size="small" dense @click="stakeLink">Stake</f-button>
</template>
</f-output>
</div>
</template>
<template v-else-if="status === 'disconnected'">
<h6 class="connect-wallet-headline low-mid-text">Connect your wallet</h6>
<div class="connectors-wrapper">
<div
class="connectors-element card"
:class="[[connector.id]]"
v-for="connector in connectors"
:key="connector.id"
@click="connectWallet(connector, chainId)"
>
<div class="connector-icon">
<img :src="loadConnectorImage(connector)" :alt="`${connector.name} Logo`" />
</div>
<div class="connector-name">
{{ connector.name }}
</div>
</div>
</div>
<div>Are you coming from Binance, Bybit, Kucoin or any other crypto exchange?</div>
<div><u>Follow these instructions first.</u></div>
</template>
<template v-else>
<div>loading</div>
</template>
</div>
<div class="connect-wallet-wrapper">
<template v-if="status === 'connected'">
<div class="connected-header">
<div class="connected-header-avatar">
<img :src="getBlocky(address!)" alt="avatar" />
</div>
<div class="connected-header-address" :title="address">
{{ getAddressShortName(address!) }}
</div>
<div class="connected-header-logout" @click="() => disconnect()">
<img src="@/assets/logout.svg" alt="Logout" />
</div>
</div>
<h6 class="connected-tokens-headline">Your Tokens</h6>
<div class="connected-tokens">
<FOutput name="$KRK" :price="harbAmount" :variant="3">
<template #end>
<FButton size="small" dense><a :href="chain.chainData?.uniswap" target="_blank">Buy</a></FButton>
</template>
</FOutput>
<FOutput name="Staked $KRK" :price="compactNumber(stakedAmount)" :variant="3">
<template #end>
<FButton size="small" dense @click="stakeLink">Stake</FButton>
</template>
</FOutput>
</div>
</template>
<template v-else-if="status === 'disconnected'">
<h6 class="connect-wallet-headline low-mid-text">Connect your wallet</h6>
<div class="connectors-wrapper">
<div
class="connectors-element card"
:class="[[connector.id]]"
v-for="connector in connectors"
:key="connector.id"
@click="connectWallet(connector, chainId)"
>
<div class="connector-icon">
<img :src="loadConnectorImage(connector)" :alt="`${connector.name} Logo`" />
</div>
<div class="connector-name">
{{ connector.name }}
</div>
</div>
</div>
<div>Are you coming from Binance, Bybit, Kucoin or any other crypto exchange?</div>
<div><u>Follow these instructions first.</u></div>
</template>
<template v-else>
<div>loading</div>
</template>
</div>
</template>
<script setup lang="ts">
import { getCurrentInstance, computed } from "vue";
import { useRouter } from "vue-router";
import { getAddressShortName, compactNumber, formatBigIntDivision } from "@/utils/helper";
import { getBlocky } from "@/utils/blockies";
import FButton from "@/components/fcomponents/FButton.vue";
import FOutput from "@/components/fcomponents/FOutput.vue";
import { getCurrentInstance, computed } from 'vue';
import { useRouter } from 'vue-router';
import { getAddressShortName, compactNumber, formatBigIntDivision } from '@/utils/helper';
import { getBlocky } from '@/utils/blockies';
import FButton from '@/components/fcomponents/FButton.vue';
import FOutput from '@/components/fcomponents/FOutput.vue';
// import { usePositions } from "@/composables/usePositions";
// import { useWallet } from "@/composables/useWallet";
import { usePositions } from "@/composables/usePositions";
import { useWallet } from "@/composables/useWallet";
import { useChain } from "@/composables/useChain";
import { usePositions } from '@/composables/usePositions';
import { useWallet } from '@/composables/useWallet';
import { useChain } from '@/composables/useChain';
import {
useAccount,
useDisconnect,
useConnect,
useChainId,
useBalance,
type CreateConnectorFn,
type Connector,
} from "@wagmi/vue";
import { useAccount, useDisconnect, useConnect, useChainId, type CreateConnectorFn, type Connector } from '@wagmi/vue';
const { address, status } = useAccount();
const { disconnect } = useDisconnect();
const { connectors, connect, error } = useConnect();
const { connectors, connect } = useConnect();
const router = useRouter();
const chainId = useChainId();
const { myActivePositions } = usePositions();
const wallet = useWallet();
const chain = useChain();
function loadConnectorImage(connector: Connector) {
if (connector.icon) {
return connector.icon;
} else {
return `./img/connectors/${connector.name}.svg`;
}
if (connector.icon) {
return connector.icon;
} else {
return `./img/connectors/${connector.name}.svg`;
}
}
// const goerliKey = import.meta.env.VITE_GOERLI_KEY;
const harbAmount = computed(() => {
if (wallet.balance?.value) {
return compactNumber(formatBigIntDivision(wallet.balance?.value, 10n ** BigInt(wallet.balance.decimals)));
} else {
return "0";
}
if (wallet.balance?.value) {
return compactNumber(formatBigIntDivision(wallet.balance?.value, 10n ** BigInt(wallet.balance.decimals)));
} else {
return '0';
}
});
const stakedAmount = computed(() =>
myActivePositions.value.reduce(function (a, b) {
return a + b.amount;
}, 0)
myActivePositions.value.reduce(function (a, b) {
return a + b.amount;
}, 0)
);
const instance = getCurrentInstance();
@ -142,20 +132,20 @@ const instance = getCurrentInstance();
// }
function closeModal() {
instance!.parent!.emit("update:modelValue", false);
instance!.parent!.emit('update:modelValue', false);
}
// //special case for metaMask, but I think that is the most used wallet
async function connectWallet(connector: CreateConnectorFn | Connector, chainId: any) {
console.log("connector", connector);
console.log("connector", connector.name);
connect({ connector, chainId });
closeModal();
async function connectWallet(connector: CreateConnectorFn | Connector, chainId: number) {
// console.log("connector", connector);
// console.log("connector", connector.name);
connect({ connector, chainId });
closeModal();
}
function stakeLink() {
router.push("/dashboard#stake");
closeModal();
router.push('/dashboard#stake');
closeModal();
}
</script>

View file

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

View file

@ -1,69 +1,63 @@
<template>
<header>
<div class="navbar">
<div class="navbar-inner navigation-font">
<div class="navbar-left" @click="router.push('/')">
<div class="navbar-title">
<span>K</span>
<span class="big-spacing">r</span>
<span class="small-spacing">A</span>
<span class="big-spacing">I</span>
<span>ken</span>
</div>
</div>
<div class="navbar-center"></div>
<div class="navbar-end">
<nav v-if="!isMobile">
<RouterLink v-for="navbarRoute in navbarRoutes" :key="navbarRoute.name" :to="navbarRoute.path">
{{ navbarRoute.title }}
</RouterLink>
<a href="/#/docs">Docs</a>
</nav>
<template v-if="!isMobile">
<div class="vertical-line"></div>
<network-changer></network-changer>
<div class="vertical-line"></div>
</template>
<connect-button @click="showPanel = true" v-if="!isMobile"></connect-button>
<icon-login @click="showPanel = true" v-else-if="isMobile && !address"></icon-login>
<img
@click="showPanel = true"
v-else-if="isMobile && address"
:src="getBlocky(address)"
alt="avatar"
/>
</div>
</div>
</div>
</header>
<header>
<div class="navbar">
<div class="navbar-inner navigation-font">
<div class="navbar-left" @click="router.push('/')">
<div class="navbar-title">
<span>K</span>
<span class="big-spacing">r</span>
<span class="small-spacing">A</span>
<span class="big-spacing">I</span>
<span>ken</span>
</div>
</div>
<div class="navbar-center"></div>
<div class="navbar-end">
<nav v-if="!isMobile">
<RouterLink v-for="navbarRoute in navbarRoutes" :key="navbarRoute.name" :to="navbarRoute.path">
{{ navbarRoute.title }}
</RouterLink>
<a href="/#/docs">Docs</a>
</nav>
<template v-if="!isMobile">
<div class="vertical-line"></div>
<NetworkChanger></NetworkChanger>
<div class="vertical-line"></div>
</template>
<ConnectButton @click="showPanel = true" v-if="!isMobile"></ConnectButton>
<IconLogin @click="showPanel = true" v-else-if="isMobile && !address"></IconLogin>
<img @click="showPanel = true" v-else-if="isMobile && address" :src="getBlocky(address)" alt="avatar" />
</div>
</div>
</div>
</header>
</template>
<script setup lang="ts">
import { getBlocky } from "@/utils/blockies";
import { RouterLink, useRouter } from "vue-router";
import IconLogin from "@/components/icons/IconLogin.vue";
import ThemeToggle from "@/components/layouts/ThemeToggle.vue";
import NetworkChanger from "@/components/layouts/NetworkChanger.vue";
import ConnectButton from "@/components/layouts/ConnectButton.vue";
import { computed, inject, ref } from "vue";
import { useAccount } from "@wagmi/vue";
import { useDark } from "@/composables/useDark";
const {darkTheme} = useDark();
const {address} = useAccount();
import { getBlocky } from '@/utils/blockies';
import { RouterLink, useRouter } from 'vue-router';
import IconLogin from '@/components/icons/IconLogin.vue';
// import ThemeToggle from "@/components/layouts/ThemeToggle.vue";
import NetworkChanger from '@/components/layouts/NetworkChanger.vue';
import ConnectButton from '@/components/layouts/ConnectButton.vue';
import { inject, ref } from 'vue';
import { useAccount } from '@wagmi/vue';
import { useDark } from '@/composables/useDark';
const { darkTheme: _darkTheme } = useDark();
const { address } = useAccount();
const router = useRouter();
const showPanel = inject<boolean>('showPanel', false);
const isMobile = inject<boolean>('isMobile', false);
const showPanel = inject<boolean>("showPanel", false);
const isMobile = inject<boolean>("isMobile", false);
const dark1 = ref(false)
const _dark1 = ref(false);
const routes = router.getRoutes();
const navbarRoutes = routes.flatMap((obj) => {
if (obj.meta.group === "navbar") {
return { title: obj.meta.title, name: obj.name, path: obj.path };
} else {
return [];
}
const navbarRoutes = routes.flatMap(obj => {
if (obj.meta.group === 'navbar') {
return { title: obj.meta.title, name: obj.name, path: obj.path };
} else {
return [];
}
});
</script>

View file

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

View file

@ -1,22 +1,26 @@
<template>
<div class="slideout-overlay" :class="{ open: props.modelValue }" @click="showPanel = false"></div>
<div :class="{ 'slideout-wrapper': true, open: props.modelValue }">
<div class="slideout-panel">
<slot></slot>
</div>
</div>
<div class="slideout-overlay" :class="{ open: props.modelValue }" @click="showPanel = false"></div>
<div :class="{ 'slideout-wrapper': true, open: props.modelValue }">
<div class="slideout-panel">
<slot></slot>
</div>
</div>
</template>
<script setup lang="ts">
import { inject, watchEffect } from "vue";
import { inject } from 'vue';
const props = defineProps({
modelValue: Boolean,
});
interface Props {
modelValue?: boolean;
}
const props = defineProps<Props>();
defineEmits(["update:modelValue"]);
interface Emits {
(e: 'update:modelValue', value: boolean): void;
}
defineEmits<Emits>();
const showPanel = inject("showPanel");
const showPanel = inject('showPanel');
</script>
<style lang="sass">

View file

@ -1,150 +1,85 @@
<template>
<div class="toggle-dark-light">
<input type="checkbox" id="darkmode-toggle" @click="toggleTheme" :checked="props.modelValue" />
<label for="darkmode-toggle">
<svg
version="1.1"
class="sun"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 496 496"
style="enable-background: new 0 0 496 496"
xml:space="preserve"
>
<rect
x="152.994"
y="58.921"
transform="matrix(0.3827 0.9239 -0.9239 0.3827 168.6176 -118.5145)"
width="40.001"
height="16"
/>
<rect
x="46.9"
y="164.979"
transform="matrix(0.9239 0.3827 -0.3827 0.9239 71.29 -12.4346)"
width="40.001"
height="16"
/>
<rect
x="46.947"
y="315.048"
transform="matrix(0.9239 -0.3827 0.3827 0.9239 -118.531 50.2116)"
width="40.001"
height="16"
/>
<div class="toggle-dark-light">
<input type="checkbox" id="darkmode-toggle" @click="toggleTheme" :checked="props.modelValue" />
<label for="darkmode-toggle">
<svg
version="1.1"
class="sun"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 496 496"
style="enable-background: new 0 0 496 496"
xml:space="preserve"
>
<rect x="152.994" y="58.921" transform="matrix(0.3827 0.9239 -0.9239 0.3827 168.6176 -118.5145)" width="40.001" height="16" />
<rect x="46.9" y="164.979" transform="matrix(0.9239 0.3827 -0.3827 0.9239 71.29 -12.4346)" width="40.001" height="16" />
<rect x="46.947" y="315.048" transform="matrix(0.9239 -0.3827 0.3827 0.9239 -118.531 50.2116)" width="40.001" height="16" />
<rect
x="164.966"
y="409.112"
transform="matrix(-0.9238 -0.3828 0.3828 -0.9238 168.4872 891.7491)"
width="16"
height="39.999"
/>
<rect x="164.966" y="409.112" transform="matrix(-0.9238 -0.3828 0.3828 -0.9238 168.4872 891.7491)" width="16" height="39.999" />
<rect
x="303.031"
y="421.036"
transform="matrix(-0.3827 -0.9239 0.9239 -0.3827 50.2758 891.6655)"
width="40.001"
height="16"
/>
<rect x="303.031" y="421.036" transform="matrix(-0.3827 -0.9239 0.9239 -0.3827 50.2758 891.6655)" width="40.001" height="16" />
<rect
x="409.088"
y="315.018"
transform="matrix(-0.9239 -0.3827 0.3827 -0.9239 701.898 785.6559)"
width="40.001"
height="16"
/>
<rect x="409.088" y="315.018" transform="matrix(-0.9239 -0.3827 0.3827 -0.9239 701.898 785.6559)" width="40.001" height="16" />
<rect
x="409.054"
y="165.011"
transform="matrix(-0.9239 0.3827 -0.3827 -0.9239 891.6585 168.6574)"
width="40.001"
height="16"
/>
<rect
x="315.001"
y="46.895"
transform="matrix(0.9238 0.3828 -0.3828 0.9238 50.212 -118.5529)"
width="16"
height="39.999"
/>
<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
<rect x="409.054" y="165.011" transform="matrix(-0.9239 0.3827 -0.3827 -0.9239 891.6585 168.6574)" width="40.001" height="16" />
<rect x="315.001" y="46.895" transform="matrix(0.9238 0.3828 -0.3828 0.9238 50.212 -118.5529)" width="16" height="39.999" />
<path
d="M248,88c-88.224,0-160,71.776-160,160s71.776,160,160,160s160-71.776,160-160S336.224,88,248,88z M248,392
c-79.4,0-144-64.6-144-144s64.6-144,144-144s144,64.6,144,144S327.4,392,248,392z"
/>
<rect x="240" width="16" height="72" />
<rect
x="62.097"
y="90.096"
transform="matrix(0.7071 0.7071 -0.7071 0.7071 98.0963 -40.6334)"
width="71.999"
height="16"
/>
<rect y="240" width="72" height="16" />
/>
<rect x="240" width="16" height="72" />
<rect x="62.097" y="90.096" transform="matrix(0.7071 0.7071 -0.7071 0.7071 98.0963 -40.6334)" width="71.999" height="16" />
<rect y="240" width="72" height="16" />
<rect
x="90.091"
y="361.915"
transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 -113.9157 748.643)"
width="16"
height="71.999"
/>
<rect x="240" y="424" width="16" height="72" />
<rect x="90.091" y="361.915" transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 -113.9157 748.643)" width="16" height="71.999" />
<rect x="240" y="424" width="16" height="72" />
<rect
x="361.881"
y="389.915"
transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 397.8562 960.6281)"
width="71.999"
height="16"
/>
<rect x="424" y="240" width="72" height="16" />
<rect
x="389.911"
y="62.091"
transform="matrix(0.7071 0.7071 -0.7071 0.7071 185.9067 -252.6357)"
width="16"
height="71.999"
/>
</svg>
<svg
version="1.1"
class="moon"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 49.739 49.739"
style="enable-background: new 0 0 49.739 49.739"
xml:space="preserve"
>
<path
d="M25.068,48.889c-9.173,0-18.017-5.06-22.396-13.804C-3.373,23.008,1.164,8.467,13.003,1.979l2.061-1.129l-0.615,2.268
<rect x="361.881" y="389.915" transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 397.8562 960.6281)" width="71.999" height="16" />
<rect x="424" y="240" width="72" height="16" />
<rect x="389.911" y="62.091" transform="matrix(0.7071 0.7071 -0.7071 0.7071 185.9067 -252.6357)" width="16" height="71.999" />
</svg>
<svg
version="1.1"
class="moon"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 49.739 49.739"
style="enable-background: new 0 0 49.739 49.739"
xml:space="preserve"
>
<path
d="M25.068,48.889c-9.173,0-18.017-5.06-22.396-13.804C-3.373,23.008,1.164,8.467,13.003,1.979l2.061-1.129l-0.615,2.268
c-1.479,5.459-0.899,11.25,1.633,16.306c2.75,5.493,7.476,9.587,13.305,11.526c5.831,1.939,12.065,1.492,17.559-1.258v0
c0.25-0.125,0.492-0.258,0.734-0.391l2.061-1.13l-0.585,2.252c-1.863,6.873-6.577,12.639-12.933,15.822
C32.639,48.039,28.825,48.888,25.068,48.889z M12.002,4.936c-9.413,6.428-12.756,18.837-7.54,29.253
c5.678,11.34,19.522,15.945,30.864,10.268c5.154-2.582,9.136-7.012,11.181-12.357c-5.632,2.427-11.882,2.702-17.752,0.748
c-6.337-2.108-11.473-6.557-14.463-12.528C11.899,15.541,11.11,10.16,12.002,4.936z"
/>
</svg>
</label>
</div>
/>
</svg>
</label>
</div>
</template>
<script setup lang="ts">
const emit = defineEmits(["update:modelValue"]);
interface Emits {
(event: 'update:modelValue', value: boolean): void;
}
const emit = defineEmits<Emits>();
const props = defineProps<{
modelValue: boolean;
modelValue: boolean;
}>();
function toggleTheme(event: any) {
emit("update:modelValue", event.target.checked);
function toggleTheme(event: Event) {
const target = event.target as HTMLInputElement | null;
if (target) {
emit('update:modelValue', target.checked);
}
}
</script>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,62 +1,55 @@
import Toast from "@/components/Toast.vue";
import { POSITION, useToast } from "vue-toastification";
import { reactive } from "vue";
import ToastNotification from '@/components/ToastNotification.vue';
import { POSITION, useToast } from 'vue-toastification';
import { reactive } from 'vue';
const toast = useToast();
export function useContractToast() {
function showFailToast(name?: string) {
console.log("name", name);
if (name === "UserRejectedRequestError") {
//
} else {
showSuccessToast(
"",
"Failed!",
"Your transaction didnt go through. Please try again.",
"If the issue persists, please send a message in #helpdesk on our Discord.",
"",
"error"
);
}
}
function showFailToast(name?: string) {
// console.log("name", name);
if (name === 'UserRejectedRequestError') {
//
} else {
showSuccessToast(
'',
'Failed!',
'Your transaction didnt go through. Please try again.',
'If the issue persists, please send a message in #helpdesk on our Discord.',
'',
'error'
);
}
}
function showSuccessToast(
value: string,
header: string,
subheader: string,
info: string,
unit: string,
type: string = "info"
) {
// Define the content object with the component, props and listeners
const content = {
component: Toast,
// Any prop can be passed, but don't expect them to be reactive
props: {
value: value,
header: header,
subheader: subheader,
info: info,
token: unit,
type: type,
},
// Listen and react to events using callbacks. In this case we listen for
// the "click" event emitted when clicking the toast button
listeners: {},
};
function showSuccessToast(value: string, header: string, subheader: string, info: string, unit: string, type: string = 'info') {
// Define the content object with the component, props and listeners
const content = {
component: ToastNotification,
// Any prop can be passed, but don't expect them to be reactive
props: {
value: value,
header: header,
subheader: subheader,
info: info,
token: unit,
type: type,
},
// Listen and react to events using callbacks. In this case we listen for
// the "click" event emitted when clicking the toast button
listeners: {},
};
// Render the toast and its contents
toast(content, {
position: POSITION.TOP_RIGHT,
icon: false,
closeOnClick: false,
toastClassName: "modal-overlay",
closeButton: false,
hideProgressBar: true,
timeout: 10000,
});
}
// Render the toast and its contents
toast(content, {
position: POSITION.TOP_RIGHT,
icon: false,
closeOnClick: false,
toastClassName: 'modal-overlay',
closeButton: false,
hideProgressBar: true,
timeout: 10000,
});
}
return reactive({ showSuccessToast, showFailToast });
return reactive({ showSuccessToast, showFailToast });
}

View file

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

View file

@ -1,30 +1,30 @@
import { ref, onMounted, onUnmounted } from "vue";
import { ref, onMounted, onUnmounted } from 'vue';
// by convention, composable function names start with "use"
export function useMobile() {
const isMobile = ref<boolean>(false);
const isMobile = ref<boolean>(false);
const handleWindowSizeChange = () => {
isMobile.value = isMobileFunc();
};
const handleWindowSizeChange = () => {
isMobile.value = isMobileFunc();
};
isMobile.value = isMobileFunc();
function isMobileFunc() {
if (screen.width <= 768) {
return true;
} else {
return false;
}
}
isMobile.value = isMobileFunc();
function isMobileFunc() {
if (screen.width <= 768) {
return true;
} else {
return false;
}
}
onMounted(async () => {
window.addEventListener("resize", handleWindowSizeChange);
handleWindowSizeChange();
});
onMounted(async () => {
window.addEventListener('resize', handleWindowSizeChange);
handleWindowSizeChange();
});
onUnmounted(() => {
window.removeEventListener("resize", handleWindowSizeChange);
});
onUnmounted(() => {
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 { config } from "@/wagmi";
import { type WatchEventReturnType, toBytes, type Hex } from "viem";
import axios from "axios";
import { getAccount, watchContractEvent, watchChainId, watchAccount } from "@wagmi/core";
import type { WatchChainIdReturnType, WatchAccountReturnType, GetAccountReturnType} from "@wagmi/core";
import { ref, computed, type ComputedRef, onMounted, onUnmounted } from 'vue';
import { config } from '@/wagmi';
import { type WatchEventReturnType, type Hex, toBytes } from 'viem';
import axios from 'axios';
import { getAccount, watchContractEvent, watchChainId, watchAccount, type Config } from '@wagmi/core';
import type { WatchChainIdReturnType, WatchAccountReturnType, GetAccountReturnType } from '@wagmi/core';
import { HarbContract } from "@/contracts/harb";
import { bytesToUint256, uint256ToBytes } from "kraiken-lib";
import { bigInt2Number } from "@/utils/helper";
import { useChain } from "@/composables/useChain";
import { taxRates } from "@/composables/useAdjustTaxRates";
import { chainData } from "@/composables/useWallet";
import logger from "@/utils/logger";
import { HarbContract } from '@/contracts/harb';
import { bytesToUint256 } from 'kraiken-lib';
import { bigInt2Number } from '@/utils/helper';
import { taxRates } from '@/composables/useAdjustTaxRates';
import { chainData } from '@/composables/useWallet';
import logger from '@/utils/logger';
const rawActivePositions = ref<Array<Position>>([]);
const rawClosedPositoins = ref<Array<Position>>([]);
const loading = ref(false);
@ -21,108 +20,108 @@ const POSITIONS_RETRY_MAX_DELAY = 60_000;
const positionsRetryDelayMs = ref(POSITIONS_RETRY_BASE_DELAY);
const GRAPHQL_TIMEOUT_MS = 15_000;
let positionsRetryTimer: number | null = null;
const chain = useChain();
const activePositions = computed(() => {
const account = getAccount(config as any);
const account = getAccount(config as Config);
return rawActivePositions.value
.map((obj: any) => {
return {
...obj,
positionId: formatId(obj.id),
amount: bigInt2Number(obj.harbDeposit, 18),
taxRatePercentage: Number(obj.taxRate) * 100,
taxRate: Number(obj.taxRate),
taxRateIndex: taxRates.find((taxRate) => taxRate.year === Number(obj.taxRate) * 100)?.index,
iAmOwner: obj.owner?.toLowerCase() === account.address?.toLowerCase(),
totalSupplyEnd: obj.totalSupplyEnd ? BigInt(obj.totalSupplyEnd) : undefined,
totalSupplyInit: BigInt(obj.totalSupplyInit),
taxPaid: BigInt(obj.taxPaid),
share: Number(obj.share),
};
})
.sort((a, b) => {
if (a.taxRate > b.taxRate) {
return 1;
} else if (a.taxRate < b.taxRate) {
return -1;
} else {
return 0;
}
});
return rawActivePositions.value
.map(obj => {
return {
...obj,
positionId: formatId(obj.id as Hex),
amount: bigInt2Number(obj.harbDeposit, 18),
taxRatePercentage: Number(obj.taxRate) * 100,
taxRate: Number(obj.taxRate),
taxRateIndex: taxRates.find(taxRate => taxRate.year === Number(obj.taxRate) * 100)?.index,
iAmOwner: obj.owner?.toLowerCase() === account.address?.toLowerCase(),
totalSupplyEnd: obj.totalSupplyEnd ? BigInt(obj.totalSupplyEnd) : undefined,
totalSupplyInit: BigInt(obj.totalSupplyInit),
taxPaid: BigInt(obj.taxPaid),
share: Number(obj.share),
};
})
.sort((a, b) => {
if (a.taxRate > b.taxRate) {
return 1;
} else if (a.taxRate < b.taxRate) {
return -1;
} else {
return 0;
}
});
});
export interface Position {
creationTime: Date;
id: string;
positionId: bigint;
owner: string;
lastTaxTime: Date;
taxPaid: bigint;
taxRate: number;
taxRateIndex: number;
taxRatePercentage: number;
share: number;
status: string;
totalSupplyEnd?: bigint;
totalSupplyInit: bigint;
amount: number;
harbDeposit: bigint;
iAmOwner: boolean;
creationTime: Date;
id: string;
positionId: bigint;
owner: string;
lastTaxTime: Date;
taxPaid: bigint;
taxRate: number;
taxRateIndex?: number;
taxRatePercentage: number;
share: number;
status: string;
totalSupplyEnd?: bigint;
totalSupplyInit: bigint;
amount: number;
harbDeposit: bigint;
iAmOwner: boolean;
}
const myClosedPositions: ComputedRef<Position[]> = computed(() => {
const account = getAccount(config as any);
const account = getAccount(config as Config);
return rawClosedPositoins.value.map((obj: any) => {
const taxRatePosition = obj.taxRate * 100;
console.log("taxRatePosition", taxRatePosition);
return rawClosedPositoins.value.map(obj => {
// console.log("taxRatePosition", taxRatePosition);
console.log("taxRates[taxRatePosition]", taxRates[taxRatePosition]);
// console.log("taxRates[taxRatePosition]", taxRates[taxRatePosition]);
return {
...obj,
positionId: formatId(obj.id),
amount: obj.share * 1000000,
// amount: bigInt2Number(obj.harbDeposit, 18),
taxRatePercentage: Number(obj.taxRate) * 100,
taxRate: Number(obj.taxRate),
taxRateIndex: taxRates.find((taxRate) => taxRate.year === Number(obj.taxRate) * 100)?.index,
iAmOwner: obj.owner?.toLowerCase() === account.address?.toLowerCase(),
totalSupplyEnd: BigInt(obj.totalSupplyEnd),
totalSupplyInit: BigInt(obj.totalSupplyInit),
taxPaid: BigInt(obj.taxPaid),
share: Number(obj.share),
};
});
return {
...obj,
positionId: formatId(obj.id as Hex),
amount: obj.share * 1000000,
// amount: bigInt2Number(obj.harbDeposit, 18),
taxRatePercentage: Number(obj.taxRate) * 100,
taxRate: Number(obj.taxRate),
taxRateIndex: taxRates.find(taxRate => taxRate.year === Number(obj.taxRate) * 100)?.index,
iAmOwner: obj.owner?.toLowerCase() === account.address?.toLowerCase(),
totalSupplyEnd: obj.totalSupplyEnd !== undefined ? BigInt(obj.totalSupplyEnd) : undefined,
totalSupplyInit: BigInt(obj.totalSupplyInit),
taxPaid: BigInt(obj.taxPaid),
share: Number(obj.share),
};
});
});
const myActivePositions: ComputedRef<Position[]> = computed(() =>
activePositions.value.filter((obj: Position) => {
return obj.iAmOwner;
})
activePositions.value.filter((obj: Position) => {
return obj.iAmOwner;
})
);
const tresholdValue = computed(() => {
const arrayTaxRatePositions = activePositions.value.map((obj) => obj.taxRatePercentage);
const sortedPositions = arrayTaxRatePositions.sort((a: any, b: any) => (a > b ? 1 : -1));
const sumq = sortedPositions.reduce((partialSum, a) => partialSum + a, 0);
const avg = sumq / sortedPositions.length;
const arrayTaxRatePositions = activePositions.value.map(obj => obj.taxRatePercentage);
const sortedPositions = arrayTaxRatePositions.sort((a, b) => (a > b ? 1 : -1));
const sumq = sortedPositions.reduce((partialSum, a) => partialSum + a, 0);
const avg = sumq / sortedPositions.length;
return avg / 2;
return avg / 2;
});
export async function loadActivePositions(endpoint?: string) {
logger.info(`loadActivePositions for chain: ${chainData.value?.path}`);
const targetEndpoint = endpoint ?? chainData.value?.graphql?.trim();
if (!targetEndpoint) {
throw new Error("GraphQL endpoint not configured for this chain.");
}
logger.info(`loadActivePositions for chain: ${chainData.value?.path}`);
const targetEndpoint = endpoint ?? chainData.value?.graphql?.trim();
if (!targetEndpoint) {
throw new Error('GraphQL endpoint not configured for this chain.');
}
console.log("chainData.value?.graphql", targetEndpoint);
// console.log("chainData.value?.graphql", targetEndpoint);
const res = await axios.post(targetEndpoint, {
query: `query ActivePositions {
const res = await axios.post(
targetEndpoint,
{
query: `query ActivePositions {
positionss(where: { status: "Active" }, orderBy: "taxRate", orderDirection: "asc", limit: 1000) {
items {
id
@ -140,34 +139,38 @@ export async function loadActivePositions(endpoint?: string) {
}
}
}`,
}, { timeout: GRAPHQL_TIMEOUT_MS });
},
{ timeout: GRAPHQL_TIMEOUT_MS }
);
const errors = res.data?.errors;
if (Array.isArray(errors) && errors.length > 0) {
throw new Error(errors.map((err: any) => err?.message ?? "GraphQL error").join(", "));
}
const errors = res.data?.errors;
if (Array.isArray(errors) && errors.length > 0) {
throw new Error(errors.map((err: unknown) => (err as { message?: string })?.message ?? 'GraphQL error').join(', '));
}
const items = res.data?.data?.positionss?.items ?? [];
return items.map((item: any) => ({
...item,
harbDeposit: item.kraikenDeposit ?? "0",
})) as Position[];
const items = res.data?.data?.positionss?.items ?? [];
return items.map((item: Record<string, unknown>) => ({
...item,
harbDeposit: item.kraikenDeposit ?? '0',
})) as Position[];
}
function formatId(id: Hex) {
const bytes = toBytes(id);
const bigIntId = bytesToUint256(bytes);
return bigIntId;
const bytes = toBytes(id);
const bigIntId = bytesToUint256(bytes);
return bigIntId;
}
export async function loadMyClosedPositions(endpoint: string | undefined, account: GetAccountReturnType) {
logger.info(`loadMyClosedPositions for chain: ${chainData.value?.path}`);
const targetEndpoint = endpoint ?? chainData.value?.graphql?.trim();
if (!targetEndpoint) {
throw new Error("GraphQL endpoint not configured for this chain.");
}
const res = await axios.post(targetEndpoint, {
query: `query ClosedPositions {
logger.info(`loadMyClosedPositions for chain: ${chainData.value?.path}`);
const targetEndpoint = endpoint ?? chainData.value?.graphql?.trim();
if (!targetEndpoint) {
throw new Error('GraphQL endpoint not configured for this chain.');
}
const res = await axios.post(
targetEndpoint,
{
query: `query ClosedPositions {
positionss(where: { status: "Closed", owner: "${account.address?.toLowerCase()}" }, limit: 1000) {
items {
id
@ -185,52 +188,53 @@ export async function loadMyClosedPositions(endpoint: string | undefined, accoun
}
}
}`,
}, { timeout: GRAPHQL_TIMEOUT_MS });
const errors = res.data?.errors;
if (Array.isArray(errors) && errors.length > 0) {
throw new Error(errors.map((err: any) => err?.message ?? "GraphQL error").join(", "));
}
const items = res.data?.data?.positionss?.items ?? [];
return items.map((item: any) => ({
...item,
harbDeposit: item.kraikenDeposit ?? "0",
})) as Position[];
},
{ timeout: GRAPHQL_TIMEOUT_MS }
);
const errors = res.data?.errors;
if (Array.isArray(errors) && errors.length > 0) {
throw new Error(errors.map((err: unknown) => (err as { message?: string })?.message ?? 'GraphQL error').join(', '));
}
const items = res.data?.data?.positionss?.items ?? [];
return items.map((item: Record<string, unknown>) => ({
...item,
harbDeposit: item.kraikenDeposit ?? '0',
})) as Position[];
}
export async function loadPositions() {
loading.value = true;
loading.value = true;
const endpoint = chainData.value?.graphql?.trim();
if (!endpoint) {
rawActivePositions.value = [];
rawClosedPositoins.value = [];
positionsError.value = "GraphQL endpoint not configured for this chain.";
clearPositionsRetryTimer();
positionsRetryDelayMs.value = POSITIONS_RETRY_BASE_DELAY;
loading.value = false;
return;
}
const endpoint = chainData.value?.graphql?.trim();
if (!endpoint) {
rawActivePositions.value = [];
rawClosedPositoins.value = [];
positionsError.value = 'GraphQL endpoint not configured for this chain.';
clearPositionsRetryTimer();
positionsRetryDelayMs.value = POSITIONS_RETRY_BASE_DELAY;
loading.value = false;
return;
}
try {
rawActivePositions.value = await loadActivePositions(endpoint);
const account = getAccount(config as any);
if (account.address) {
rawClosedPositoins.value = await loadMyClosedPositions(endpoint, account);
} else {
rawClosedPositoins.value = [];
}
positionsError.value = null;
positionsRetryDelayMs.value = POSITIONS_RETRY_BASE_DELAY;
clearPositionsRetryTimer();
} catch (error) {
console.warn("[positions] loadPositions() failed", error);
rawActivePositions.value = [];
rawClosedPositoins.value = [];
positionsError.value = formatGraphqlError(error);
schedulePositionsRetry();
} finally {
loading.value = false;
}
try {
rawActivePositions.value = await loadActivePositions(endpoint);
const account = getAccount(config as Config);
if (account.address) {
rawClosedPositoins.value = await loadMyClosedPositions(endpoint, account);
} else {
rawClosedPositoins.value = [];
}
positionsError.value = null;
positionsRetryDelayMs.value = POSITIONS_RETRY_BASE_DELAY;
clearPositionsRetryTimer();
} catch (error) {
rawActivePositions.value = [];
rawClosedPositoins.value = [];
positionsError.value = formatGraphqlError(error);
schedulePositionsRetry();
} finally {
loading.value = false;
}
}
let unwatch: WatchEventReturnType | null;
@ -239,174 +243,174 @@ let unwatchChainSwitch: WatchChainIdReturnType | null;
let unwatchAccountChanged: WatchAccountReturnType | null;
function formatGraphqlError(error: unknown): string {
if (axios.isAxiosError(error)) {
const responseErrors = (error.response?.data as any)?.errors;
if (Array.isArray(responseErrors) && responseErrors.length > 0) {
return responseErrors.map((err: any) => err?.message ?? "GraphQL error").join(", ");
}
if (error.response?.status) {
return `GraphQL request failed with status ${error.response.status}`;
}
if (error.message) {
return error.message;
}
}
if (error instanceof Error && error.message) {
return error.message;
}
return "Unknown GraphQL error";
if (axios.isAxiosError(error)) {
const responseErrors = (error.response?.data as { errors?: unknown[] })?.errors;
if (Array.isArray(responseErrors) && responseErrors.length > 0) {
return responseErrors.map((err: unknown) => (err as { message?: string })?.message ?? 'GraphQL error').join(', ');
}
if (error.response?.status) {
return `GraphQL request failed with status ${error.response.status}`;
}
if (error.message) {
return error.message;
}
}
if (error instanceof Error && error.message) {
return error.message;
}
return 'Unknown GraphQL error';
}
function clearPositionsRetryTimer() {
if (positionsRetryTimer !== null) {
clearTimeout(positionsRetryTimer);
positionsRetryTimer = null;
}
if (positionsRetryTimer !== null) {
clearTimeout(positionsRetryTimer);
positionsRetryTimer = null;
}
}
function schedulePositionsRetry() {
if (typeof window === "undefined") {
return;
}
if (positionsRetryTimer !== null) {
return;
}
const delay = positionsRetryDelayMs.value;
positionsRetryTimer = window.setTimeout(async () => {
positionsRetryTimer = null;
await loadPositions();
}, delay);
positionsRetryDelayMs.value = Math.min(positionsRetryDelayMs.value * 2, POSITIONS_RETRY_MAX_DELAY);
if (typeof window === 'undefined') {
return;
}
if (positionsRetryTimer !== null) {
return;
}
const delay = positionsRetryDelayMs.value;
positionsRetryTimer = window.setTimeout(async () => {
positionsRetryTimer = null;
await loadPositions();
}, delay);
positionsRetryDelayMs.value = Math.min(positionsRetryDelayMs.value * 2, POSITIONS_RETRY_MAX_DELAY);
}
export function usePositions() {
function watchEvent() {
unwatch = watchContractEvent(config as any, {
address: HarbContract.contractAddress,
abi: HarbContract.abi,
eventName: "PositionCreated",
async onLogs(logs) {
console.log("new Position", logs);
await loadPositions();
// await getMinStake();
},
});
}
function watchEvent() {
unwatch = watchContractEvent(config as Config, {
address: HarbContract.contractAddress,
abi: HarbContract.abi,
eventName: 'PositionCreated',
async onLogs(_logs) {
// console.log("new Position", logs);
await loadPositions();
// await getMinStake();
},
});
}
function watchPositionRemoved() {
unwatchPositionRemovedEvent = watchContractEvent(config as any, {
address: HarbContract.contractAddress,
abi: HarbContract.abi,
eventName: "PositionRemoved",
async onLogs(logs) {
console.log("Position removed", logs);
await loadPositions();
// await getMinStake();
},
});
}
function watchPositionRemoved() {
unwatchPositionRemovedEvent = watchContractEvent(config as Config, {
address: HarbContract.contractAddress,
abi: HarbContract.abi,
eventName: 'PositionRemoved',
async onLogs(_logs) {
// console.log("Position removed", logs);
await loadPositions();
// await getMinStake();
},
});
}
onMounted(async () => {
//initial loading positions
onMounted(async () => {
//initial loading positions
if (activePositions.value.length < 1 && loading.value === false) {
await loadPositions();
// await getMinStake();
}
if (activePositions.value.length < 1 && loading.value === false) {
await loadPositions();
// await getMinStake();
}
if (!unwatch) {
watchEvent();
}
if (!unwatchPositionRemovedEvent) {
watchPositionRemoved();
}
if (!unwatch) {
watchEvent();
}
if (!unwatchPositionRemovedEvent) {
watchPositionRemoved();
}
if (!unwatchChainSwitch) {
unwatchChainSwitch = watchChainId(config as any, {
async onChange(chainId) {
await loadPositions();
},
});
}
if (!unwatchChainSwitch) {
unwatchChainSwitch = watchChainId(config as Config, {
async onChange(_chainId) {
await loadPositions();
},
});
}
if (!unwatchAccountChanged) {
unwatchAccountChanged = watchAccount(config as any, {
async onChange() {
await loadPositions();
},
});
}
});
if (!unwatchAccountChanged) {
unwatchAccountChanged = watchAccount(config as Config, {
async onChange() {
await loadPositions();
},
});
}
});
onUnmounted(() => {
if (unwatch) {
unwatch();
unwatch = null;
}
if (unwatchPositionRemovedEvent) {
unwatchPositionRemovedEvent();
unwatchPositionRemovedEvent = null;
}
if (unwatchChainSwitch) {
unwatchChainSwitch();
unwatchChainSwitch = null;
}
if (unwatchAccountChanged) {
unwatchAccountChanged();
unwatchAccountChanged = null;
}
clearPositionsRetryTimer();
});
onUnmounted(() => {
if (unwatch) {
unwatch();
unwatch = null;
}
if (unwatchPositionRemovedEvent) {
unwatchPositionRemovedEvent();
unwatchPositionRemovedEvent = null;
}
if (unwatchChainSwitch) {
unwatchChainSwitch();
unwatchChainSwitch = null;
}
if (unwatchAccountChanged) {
unwatchAccountChanged();
unwatchAccountChanged = null;
}
clearPositionsRetryTimer();
});
function createRandomPosition(amount: number = 1) {
for (let index = 0; index < amount; index++) {
const newPosition: Position = {
creationTime: new Date(),
id: "123",
positionId: 123n,
owner: "bla",
lastTaxTime: new Date(),
taxPaid: 100n,
taxRate: randomInRange(0.01, 1),
taxRateIndex: randomInRange(1, 30),
taxRatePercentage: getRandomInt(1, 100),
share: getRandomInt(0.001, 0.09),
status: "active",
totalSupplyEnd: undefined,
totalSupplyInit: 1000000000000n,
amount: 150,
harbDeposit: getRandomBigInt(1000, 5000),
iAmOwner: false,
};
rawActivePositions.value.push(newPosition);
}
}
function createRandomPosition(amount: number = 1) {
for (let index = 0; index < amount; index++) {
const newPosition: Position = {
creationTime: new Date(),
id: '123',
positionId: 123n,
owner: 'bla',
lastTaxTime: new Date(),
taxPaid: 100n,
taxRate: randomInRange(0.01, 1),
taxRateIndex: Math.floor(randomInRange(1, 30)),
taxRatePercentage: getRandomInt(1, 100),
share: getRandomInt(0.001, 0.09),
status: 'active',
totalSupplyEnd: undefined,
totalSupplyInit: 1000000000000n,
amount: 150,
harbDeposit: getRandomBigInt(1000, 5000),
iAmOwner: false,
};
rawActivePositions.value.push(newPosition);
}
}
function randomInRange(min: number, max: number) {
return Math.random() < 0.5 ? (1 - Math.random()) * (max - min) + min : Math.random() * (max - min) + min;
}
function randomInRange(min: number, max: number) {
return Math.random() < 0.5 ? (1 - Math.random()) * (max - min) + min : Math.random() * (max - min) + min;
}
function getRandomInt(min: number, max: number) {
const minCeiled = Math.ceil(min);
const maxFloored = Math.floor(max);
const randomNumber = Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled); // The maximum is exclusive and the minimum is inclusive
function getRandomInt(min: number, max: number) {
const minCeiled = Math.ceil(min);
const maxFloored = Math.floor(max);
const randomNumber = Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled); // The maximum is exclusive and the minimum is inclusive
return randomNumber;
}
return randomNumber;
}
function getRandomBigInt(min: number, max: number) {
const randomNumber = getRandomInt(min, max);
return BigInt(randomNumber) * 10n ** 18n;
}
function getRandomBigInt(min: number, max: number) {
const randomNumber = getRandomInt(min, max);
return BigInt(randomNumber) * 10n ** 18n;
}
return {
activePositions,
myActivePositions,
myClosedPositions,
tresholdValue,
watchEvent,
watchPositionRemoved,
createRandomPosition,
positionsError,
loading,
};
return {
activePositions,
myActivePositions,
myClosedPositions,
tresholdValue,
watchEvent,
watchPositionRemoved,
createRandomPosition,
positionsError,
loading,
};
}

View file

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

View file

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

View file

@ -1,31 +1,32 @@
import { ref, onMounted, onUnmounted, reactive, computed } from "vue";
import axios from "axios";
import { chainData } from "./useWallet";
import { watchBlocks, watchChainId } from "@wagmi/core";
import { config } from "@/wagmi";
import logger from "@/utils/logger";
import type { WatchBlocksReturnType } from "viem";
import { bigInt2Number } from "@/utils/helper";
const demo = sessionStorage.getItem("demo") === "true";
import { ref, reactive, computed } from 'vue';
import axios from 'axios';
import { chainData } from './useWallet';
import { watchChainId } from '@wagmi/core';
import type { Config } from '@wagmi/core';
import { config } from '@/wagmi';
import logger from '@/utils/logger';
import type { WatchBlocksReturnType } from 'viem';
import { bigInt2Number } from '@/utils/helper';
const demo = sessionStorage.getItem('demo') === 'true';
const GRAPHQL_TIMEOUT_MS = 15_000;
const RETRY_BASE_DELAY_MS = 1_500;
const RETRY_MAX_DELAY_MS = 60_000;
interface StatsRecord {
burnNextHourProjected: string;
burnedLastDay: string;
burnedLastWeek: string;
id: string;
mintNextHourProjected: string;
mintedLastDay: string;
mintedLastWeek: string;
outstandingStake: string;
kraikenTotalSupply: string;
stakeTotalSupply: string;
ringBufferPointer: number;
totalBurned: string;
totalMinted: string;
burnNextHourProjected: string;
burnedLastDay: string;
burnedLastWeek: string;
id: string;
mintNextHourProjected: string;
mintedLastDay: string;
mintedLastWeek: string;
outstandingStake: string;
kraikenTotalSupply: string;
stakeTotalSupply: string;
ringBufferPointer: number;
totalBurned: string;
totalMinted: string;
}
const rawStatsCollections = ref<Array<StatsRecord>>([]);
@ -36,50 +37,52 @@ const statsRetryDelayMs = ref(RETRY_BASE_DELAY_MS);
let statsRetryTimer: number | null = null;
function formatGraphqlError(error: unknown): string {
if (axios.isAxiosError(error)) {
const responseErrors = (error.response?.data as any)?.errors;
if (Array.isArray(responseErrors) && responseErrors.length > 0) {
return responseErrors.map((err: any) => err?.message ?? "GraphQL error").join(", ");
}
if (error.response?.status) {
return `GraphQL request failed with status ${error.response.status}`;
}
if (error.message) {
return error.message;
}
}
if (error instanceof Error && error.message) {
return error.message;
}
return "Unknown GraphQL error";
if (axios.isAxiosError(error)) {
const responseErrors = (error.response?.data as { errors?: unknown[] })?.errors;
if (Array.isArray(responseErrors) && responseErrors.length > 0) {
return responseErrors.map((err: unknown) => (err as { message?: string })?.message ?? 'GraphQL error').join(', ');
}
if (error.response?.status) {
return `GraphQL request failed with status ${error.response.status}`;
}
if (error.message) {
return error.message;
}
}
if (error instanceof Error && error.message) {
return error.message;
}
return 'Unknown GraphQL error';
}
function clearStatsRetryTimer() {
if (statsRetryTimer !== null) {
clearTimeout(statsRetryTimer);
statsRetryTimer = null;
}
if (statsRetryTimer !== null) {
clearTimeout(statsRetryTimer);
statsRetryTimer = null;
}
}
function scheduleStatsRetry() {
if (typeof window === "undefined") {
return;
}
if (statsRetryTimer !== null) {
return;
}
const delay = statsRetryDelayMs.value;
statsRetryTimer = window.setTimeout(async () => {
statsRetryTimer = null;
await loadStats();
}, delay);
statsRetryDelayMs.value = Math.min(statsRetryDelayMs.value * 2, RETRY_MAX_DELAY_MS);
if (typeof window === 'undefined') {
return;
}
if (statsRetryTimer !== null) {
return;
}
const delay = statsRetryDelayMs.value;
statsRetryTimer = window.setTimeout(async () => {
statsRetryTimer = null;
await loadStats();
}, delay);
statsRetryDelayMs.value = Math.min(statsRetryDelayMs.value * 2, RETRY_MAX_DELAY_MS);
}
export async function loadStatsCollection(endpoint: string) {
logger.info(`loadStatsCollection for chain: ${chainData.value?.path}`);
const res = await axios.post(endpoint, {
query: `query StatsQuery {
logger.info(`loadStatsCollection for chain: ${chainData.value?.path}`);
const res = await axios.post(
endpoint,
{
query: `query StatsQuery {
stats(id: "0x01") {
burnNextHourProjected
burnedLastDay
@ -96,209 +99,207 @@ export async function loadStatsCollection(endpoint: string) {
totalMinted
}
}`,
}, { timeout: GRAPHQL_TIMEOUT_MS });
},
{ timeout: GRAPHQL_TIMEOUT_MS }
);
const errors = res.data?.errors;
if (Array.isArray(errors) && errors.length > 0) {
throw new Error(errors.map((err: any) => err?.message ?? "GraphQL error").join(", "));
}
const errors = res.data?.errors;
if (Array.isArray(errors) && errors.length > 0) {
throw new Error(errors.map((err: unknown) => (err as { message?: string })?.message ?? 'GraphQL error').join(', '));
}
const stats = res.data?.data?.stats as StatsRecord | undefined;
if (!stats) {
throw new Error("Stats entity not found in GraphQL response");
}
const stats = res.data?.data?.stats as StatsRecord | undefined;
if (!stats) {
throw new Error('Stats entity not found in GraphQL response');
}
return [{ ...stats, kraikenTotalSupply: stats.kraikenTotalSupply }];
return [{ ...stats, kraikenTotalSupply: stats.kraikenTotalSupply }];
}
const profit7d = computed(() => {
if (!statsCollection.value) {
return 0n;
}
const mintedLastWeek = BigInt(statsCollection.value.mintedLastWeek);
const burnedLastWeek = BigInt(statsCollection.value.burnedLastWeek);
const totalMinted = BigInt(statsCollection.value.totalMinted);
const totalBurned = BigInt(statsCollection.value.totalBurned);
const denominator = totalMinted - totalBurned;
if (denominator === 0n) {
return 0n;
}
return (mintedLastWeek - burnedLastWeek) / denominator;
if (!statsCollection.value) {
return 0n;
}
const mintedLastWeek = BigInt(statsCollection.value.mintedLastWeek);
const burnedLastWeek = BigInt(statsCollection.value.burnedLastWeek);
const totalMinted = BigInt(statsCollection.value.totalMinted);
const totalBurned = BigInt(statsCollection.value.totalBurned);
const denominator = totalMinted - totalBurned;
if (denominator === 0n) {
return 0n;
}
return (mintedLastWeek - burnedLastWeek) / denominator;
});
const nettoToken7d = computed(() => {
if (!statsCollection.value) {
return 0n;
}
if (!statsCollection.value) {
return 0n;
}
return BigInt(statsCollection.value.mintedLastWeek) - BigInt(statsCollection.value.burnedLastWeek);
return BigInt(statsCollection.value.mintedLastWeek) - BigInt(statsCollection.value.burnedLastWeek);
});
const statsCollection = computed(() => {
if (rawStatsCollections.value?.length > 0) {
return rawStatsCollections.value[0];
} else {
return undefined;
}
if (rawStatsCollections.value?.length > 0) {
return rawStatsCollections.value[0];
} else {
return undefined;
}
});
const outstandingStake = computed(() => {
if (demo) {
// outStandingStake = 1990000000000000000000000n;
return 2000000000000000000000000n;
}
if (rawStatsCollections.value?.length > 0) {
return BigInt(rawStatsCollections.value[0].outstandingStake);
} else {
return 0n;
}
if (demo) {
// outStandingStake = 1990000000000000000000000n;
return 2000000000000000000000000n;
}
if (rawStatsCollections.value?.length > 0) {
return BigInt(rawStatsCollections.value[0].outstandingStake);
} else {
return 0n;
}
});
const kraikenTotalSupply = computed(() => {
if (rawStatsCollections.value?.length > 0) {
return BigInt(rawStatsCollections.value[0].kraikenTotalSupply);
} else {
return 0n;
}
if (rawStatsCollections.value?.length > 0) {
return BigInt(rawStatsCollections.value[0].kraikenTotalSupply);
} else {
return 0n;
}
});
const stakeTotalSupply = computed(() => {
if (rawStatsCollections.value?.length > 0) {
return BigInt(rawStatsCollections.value[0].stakeTotalSupply);
} else {
return 0n;
}
if (rawStatsCollections.value?.length > 0) {
return BigInt(rawStatsCollections.value[0].stakeTotalSupply);
} else {
return 0n;
}
});
//Total Supply Change / 7d=mintedLastWeekburnedLastWeek
const totalSupplyChange7d = computed(() => {
if (rawStatsCollections.value?.length > 0) {
return (
BigInt(rawStatsCollections.value[0].mintedLastWeek) -
BigInt(rawStatsCollections.value[0].burnedLastWeek)
);
} else {
return 0n;
}
if (rawStatsCollections.value?.length > 0) {
return BigInt(rawStatsCollections.value[0].mintedLastWeek) - BigInt(rawStatsCollections.value[0].burnedLastWeek);
} else {
return 0n;
}
});
//totalsupply Change7d / harbtotalsupply
const inflation7d = computed(() => {
if (rawStatsCollections.value?.length > 0 && BigInt(rawStatsCollections.value[0].kraikenTotalSupply) > 0n) {
return (
BigInt(rawStatsCollections.value[0].mintedLastWeek) -
BigInt(rawStatsCollections.value[0].burnedLastWeek)
);
} else {
return 0n;
}
if (rawStatsCollections.value?.length > 0 && BigInt(rawStatsCollections.value[0].kraikenTotalSupply) > 0n) {
return BigInt(rawStatsCollections.value[0].mintedLastWeek) - BigInt(rawStatsCollections.value[0].burnedLastWeek);
} else {
return 0n;
}
});
const stakeableSupply = computed(() => {
if (rawStatsCollections.value?.length > 0 && BigInt(rawStatsCollections.value[0].kraikenTotalSupply) > 0n) {
console.log("rawStatsCollections.value[0]", rawStatsCollections.value[0]);
if (rawStatsCollections.value?.length > 0 && BigInt(rawStatsCollections.value[0].kraikenTotalSupply) > 0n) {
// console.log("rawStatsCollections.value[0]", rawStatsCollections.value[0]);
return stakeTotalSupply.value / 5n;
} else {
return 0n;
}
return stakeTotalSupply.value / 5n;
} else {
return 0n;
}
});
//maxSlots
const maxSlots = computed(() => {
if (rawStatsCollections.value?.length > 0 && BigInt(rawStatsCollections.value[0].kraikenTotalSupply) > 0n) {
console.log("rawStatsCollections.value[0]", rawStatsCollections.value[0]);
if (rawStatsCollections.value?.length > 0 && BigInt(rawStatsCollections.value[0].kraikenTotalSupply) > 0n) {
// console.log("rawStatsCollections.value[0]", rawStatsCollections.value[0]);
return (bigInt2Number(stakeTotalSupply.value, 18) * 0.2) / 100;
} else {
return 0;
}
return (bigInt2Number(stakeTotalSupply.value, 18) * 0.2) / 100;
} else {
return 0;
}
});
const claimedSlots = computed(() => {
if (stakeTotalSupply.value > 0n) {
const stakeableSupplyNumber = bigInt2Number(stakeableSupply.value, 18);
const outstandingStakeNumber = bigInt2Number(outstandingStake.value, 18);
return (outstandingStakeNumber / stakeableSupplyNumber) * maxSlots.value;
} else {
return 0;
}
if (stakeTotalSupply.value > 0n) {
const stakeableSupplyNumber = bigInt2Number(stakeableSupply.value, 18);
const outstandingStakeNumber = bigInt2Number(outstandingStake.value, 18);
return (outstandingStakeNumber / stakeableSupplyNumber) * maxSlots.value;
} else {
return 0;
}
});
export async function loadStats() {
loading.value = true;
loading.value = true;
const endpoint = chainData.value?.graphql?.trim();
if (!endpoint) {
rawStatsCollections.value = [];
statsError.value = "GraphQL endpoint not configured for this chain.";
clearStatsRetryTimer();
statsRetryDelayMs.value = RETRY_BASE_DELAY_MS;
loading.value = false;
initialized.value = true;
return;
}
const endpoint = chainData.value?.graphql?.trim();
if (!endpoint) {
rawStatsCollections.value = [];
statsError.value = 'GraphQL endpoint not configured for this chain.';
clearStatsRetryTimer();
statsRetryDelayMs.value = RETRY_BASE_DELAY_MS;
loading.value = false;
initialized.value = true;
return;
}
try {
rawStatsCollections.value = await loadStatsCollection(endpoint);
statsError.value = null;
statsRetryDelayMs.value = RETRY_BASE_DELAY_MS;
clearStatsRetryTimer();
} catch (error) {
console.warn("[stats] loadStats() failed", error);
rawStatsCollections.value = [];
statsError.value = formatGraphqlError(error);
scheduleStatsRetry();
} finally {
loading.value = false;
initialized.value = true;
}
try {
rawStatsCollections.value = await loadStatsCollection(endpoint);
statsError.value = null;
statsRetryDelayMs.value = RETRY_BASE_DELAY_MS;
clearStatsRetryTimer();
} catch (error) {
// console.warn('[stats] loadStats() failed', error);
rawStatsCollections.value = [];
statsError.value = formatGraphqlError(error);
scheduleStatsRetry();
} finally {
loading.value = false;
initialized.value = true;
}
}
let unwatch: any = null;
let unwatchBlock: WatchBlocksReturnType;
const loadingWatchBlock = ref(false);
import { onMounted, onUnmounted } from 'vue';
let unwatch: WatchBlocksReturnType | null = null;
export function useStatCollection() {
onMounted(async () => {
//initial loading stats
if (rawStatsCollections.value?.length === 0 && !loading.value) {
await loadStats();
}
});
if (!unwatch) {
console.log("watchChain");
onMounted(async () => {
//initial loading stats
if (rawStatsCollections.value?.length === 0 && !loading.value) {
await loadStats();
}
});
if (!unwatch) {
// console.log("watchChain");
//chain Switch reload stats for other chain
unwatch = watchChainId(config as any, {
async onChange(chainId) {
await loadStats();
},
});
//chain Switch reload stats for other chain
unwatch = watchChainId(config as Config, {
async onChange(_chainId) {
await loadStats();
},
});
// const unwatchBlock = watchBlocks(config as any, {
// async onBlock(block) {
// console.log('Block changed!', block)
// await loadStats();
// },
// })
}
onUnmounted(() => {
clearStatsRetryTimer();
unwatch();
});
// const unwatchBlock = watchBlocks(config as Config, {
// async onBlock(block) {
// console.log('Block changed!', block)
// await loadStats();
// },
// })
}
onUnmounted(() => {
clearStatsRetryTimer();
if (unwatch) {
unwatch();
}
});
return reactive({
profit7d,
nettoToken7d,
inflation7d,
outstandingStake,
kraikenTotalSupply,
stakeTotalSupply,
totalSupplyChange7d,
initialized,
maxSlots,
stakeableSupply,
claimedSlots,
statsError,
loading,
});
return reactive({
profit7d,
nettoToken7d,
inflation7d,
outstandingStake,
kraikenTotalSupply,
stakeTotalSupply,
totalSupplyChange7d,
initialized,
maxSlots,
stakeableSupply,
claimedSlots,
statsError,
loading,
});
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,5 @@
<template>
<main>
<slot></slot>
</main>
</template>
<main>
<slot></slot>
</main>
</template>

View file

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

View file

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

View file

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

View file

@ -1,85 +1,83 @@
import { type Address, type TypedDataDomain, type Hex, slice, hexToNumber, hexToBigInt, recoverAddress } from "viem";
import { type Address, type TypedDataDomain, slice, hexToBigInt } from 'viem';
export function createPermitObject(
verifyingContract: Address,
fromAddress: Address,
spender: Address,
nonce: BigInt,
deadline: BigInt,
value: BigInt,
chain: number,
domainName: string
verifyingContract: Address,
fromAddress: Address,
spender: Address,
nonce: bigint,
deadline: bigint,
value: bigint,
chain: number,
domainName: string
) {
const message = {
owner: fromAddress,
spender: spender,
nonce: nonce,
deadline: deadline,
value: value,
};
const message = {
owner: fromAddress,
spender: spender,
nonce: nonce,
deadline: deadline,
value: value,
};
const domainType = [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" },
];
const domainType = [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
];
const primaryType: "EIP712Domain" | "Permit" = "Permit";
const primaryType: 'EIP712Domain' | 'Permit' = 'Permit';
const types = {
EIP712Domain: domainType,
Permit: [
{
name: "owner",
type: "address",
},
{
name: "spender",
type: "address",
},
{
name: "value",
type: "uint256",
},
{
name: "nonce",
type: "uint256",
},
{
name: "deadline",
type: "uint256",
},
],
};
const types = {
EIP712Domain: domainType,
Permit: [
{
name: 'owner',
type: 'address',
},
{
name: 'spender',
type: 'address',
},
{
name: 'value',
type: 'uint256',
},
{
name: 'nonce',
type: 'uint256',
},
{
name: 'deadline',
type: 'uint256',
},
],
};
const domain: TypedDataDomain | undefined = {
name: domainName,
version: "1",
chainId: chain,
verifyingContract: verifyingContract,
};
const domain: TypedDataDomain | undefined = {
name: domainName,
version: '1',
chainId: chain,
verifyingContract: verifyingContract,
};
return {
types,
message,
domain,
primaryType,
};
return {
types,
message,
domain,
primaryType,
};
}
export function getSignatureRSV2(sig: `0x${string}`) {
// splits the signature to r, s, and v values.
// const pureSig = sig.replace("0x", "");
const [r, s, v] = [slice(sig, 0, 32), slice(sig, 32, 64), slice(sig, 64, 65)];
return { r, s, v: Number(v) };
// splits the signature to r, s, and v values.
// const pureSig = sig.replace("0x", "");
const [r, s, v] = [slice(sig, 0, 32), slice(sig, 32, 64), slice(sig, 64, 65)];
return { r, s, v: Number(v) };
}
export function getSignatureRSV(signature: `0x${string}`) {
const r = signature.slice(0, 66) as `0x${string}`;
const s = `0x${signature.slice(66, 130)}` as `0x${string}`;
const v = hexToBigInt(`0x${signature.slice(130, 132)}`);
return { r, s, v };
}
const r = signature.slice(0, 66) as `0x${string}`;
const s = `0x${signature.slice(66, 130)}` as `0x${string}`;
const v = Number(hexToBigInt(`0x${signature.slice(130, 132)}`));
return { r, s, v };
}

View file

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

View file

@ -6,7 +6,7 @@
* @returns Number of shares corresponding to the input assets.
*/
export function assetsToShares(assets: bigint, totalSupply: bigint, harbergTotalSupply: bigint): bigint {
return (assets * totalSupply) / harbergTotalSupply;
return (assets * totalSupply) / harbergTotalSupply;
}
/**
@ -17,5 +17,5 @@ export function assetsToShares(assets: bigint, totalSupply: bigint, harbergTotal
* @returns The equivalent number of Harberg tokens for the given shares.
*/
export function sharesToAssets(shares: bigint, totalSupply: bigint, harbergTotalSupply: bigint): bigint {
return (shares * harbergTotalSupply) / totalSupply;
return (shares * harbergTotalSupply) / totalSupply;
}

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,30 +1,26 @@
<template>
<chart-js
:snatchedPositions="snatchPositions.map((obj) => obj.id)"
:positions="activePositions"
:dark="darkTheme"
></chart-js>
<ChartJs :snatchedPositions="snatchPositions.map(obj => obj.id)" :positions="activePositions" :dark="darkTheme"></ChartJs>
</template>
<script setup lang="ts">
import ChartJs from "@/components/chart/ChartJs.vue";
import {bigInt2Number, formatBigIntDivision} from "@/utils/helper";
import { computed, ref } from "vue";
import { useStatCollection } from "@/composables/useStatCollection";
import { useStake } from "@/composables/useStake";
import { usePositions, type Position } from "@/composables/usePositions";
import { useDark } from "@/composables/useDark";
import ChartJs from '@/components/chart/ChartJs.vue';
import { bigInt2Number, formatBigIntDivision } from '@/utils/helper';
import { computed, ref } from 'vue';
import { useStatCollection } from '@/composables/useStatCollection';
import { useStake } from '@/composables/useStake';
import { usePositions, type Position } from '@/composables/usePositions';
import { useDark } from '@/composables/useDark';
const { darkTheme } = useDark();
const { activePositions, myActivePositions, tresholdValue, myClosedPositions, createRandomPosition } = usePositions();
const { activePositions } = usePositions();
const ignoreOwner = ref(false);
const taxRate = ref<number>(1.0);
const minStakeAmount = computed(() => {
console.log("minStake", minStake.value);
// console.log("minStake", minStake.value);
return formatBigIntDivision(minStake.value, 10n ** 18n);
return formatBigIntDivision(minStake.value, 10n ** 18n);
});
const stakeAbleHarbAmount = computed(() => statCollection.kraikenTotalSupply / 5n);
@ -34,35 +30,34 @@ const minStake = computed(() => stakeAbleHarbAmount.value / 600n);
const stake = useStake();
const statCollection = useStatCollection();
const snatchPositions = computed(() => {
if (
bigInt2Number(statCollection.outstandingStake, 18) + stake.stakingAmountNumber <=
bigInt2Number(statCollection.kraikenTotalSupply, 18) * 0.2
) {
return [];
}
//Differenz aus outstandingSupply und totalSupply bestimmen, wie viel HARB kann zum Snatch verwendet werden
const difference =
bigInt2Number(statCollection.outstandingStake, 18) +
stake.stakingAmountNumber -
bigInt2Number(statCollection.kraikenTotalSupply, 18) * 0.2;
console.log("difference", difference);
if (
bigInt2Number(statCollection.outstandingStake, 18) + stake.stakingAmountNumber <=
bigInt2Number(statCollection.kraikenTotalSupply, 18) * 0.2
) {
return [];
}
//Differenz aus outstandingSupply und totalSupply bestimmen, wie viel HARB kann zum Snatch verwendet werden
const difference =
bigInt2Number(statCollection.outstandingStake, 18) +
stake.stakingAmountNumber -
bigInt2Number(statCollection.kraikenTotalSupply, 18) * 0.2;
// console.log("difference", difference);
//Division ohne Rest, um zu schauen wie viele Positionen gesnatched werden könnten
const snatchAblePositionsCount = Math.floor(difference / minStakeAmount.value);
//Division ohne Rest, um zu schauen wie viele Positionen gesnatched werden könnten
const snatchAblePositionsCount = Math.floor(difference / minStakeAmount.value);
//wenn mehr als 0 Positionen gesnatched werden könnten, wird geschaut wie viele Positionen in Frage kommen
if (snatchAblePositionsCount > 0) {
const snatchAblePositions = activePositions.value.filter((obj: Position) => {
if (ignoreOwner.value) {
return obj.taxRatePercentage < taxRate.value;
}
return obj.taxRatePercentage < taxRate.value && !obj.iAmOwner;
});
const slicedArray = snatchAblePositions.slice(0, snatchAblePositionsCount);
return slicedArray;
}
//wenn mehr als 0 Positionen gesnatched werden könnten, wird geschaut wie viele Positionen in Frage kommen
if (snatchAblePositionsCount > 0) {
const snatchAblePositions = activePositions.value.filter((obj: Position) => {
if (ignoreOwner.value) {
return obj.taxRatePercentage < taxRate.value;
}
return obj.taxRatePercentage < taxRate.value && !obj.iAmOwner;
});
const slicedArray = snatchAblePositions.slice(0, snatchAblePositionsCount);
return slicedArray;
}
return [];
return [];
});
</script>

View file

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

View file

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

View file

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

View file

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