staking and vault balance update

This commit is contained in:
mart1n
2025-01-20 15:08:23 +01:00
committed by r4bbit
parent 2cf3b2c468
commit 4aaab3d76e
9 changed files with 950 additions and 46 deletions

314
package-lock.json generated
View File

@@ -8,10 +8,14 @@
"name": "staking-demo-app",
"version": "0.0.1",
"dependencies": {
"@floating-ui/dom": "^1.6.13",
"@inlang/paraglide-sveltekit": "^0.11.1",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"flowbite": "^2.5.2",
"flowbite-svelte": "^0.47.4",
"tailwindcss-animate": "^1.0.7",
"viem": "^2.22.8"
},
"devDependencies": {
@@ -654,6 +658,31 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@floating-ui/core": {
"version": "1.6.9",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz",
"integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==",
"license": "MIT",
"dependencies": {
"@floating-ui/utils": "^0.2.9"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.6.13",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz",
"integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==",
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^1.6.0",
"@floating-ui/utils": "^0.2.9"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz",
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
"license": "MIT"
},
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -1660,6 +1689,68 @@
"integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==",
"license": "MIT"
},
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@rollup/plugin-node-resolve": {
"version": "15.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz",
"integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==",
"license": "MIT",
"dependencies": {
"@rollup/pluginutils": "^5.0.1",
"@types/resolve": "1.20.2",
"deepmerge": "^4.2.2",
"is-module": "^1.0.0",
"resolve": "^1.22.1"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^2.78.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/@rollup/pluginutils": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz",
"integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==",
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0",
"estree-walker": "^2.0.2",
"picomatch": "^4.0.2"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/@rollup/pluginutils/node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.30.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.30.1.tgz",
@@ -2138,6 +2229,12 @@
"undici-types": "~6.20.0"
}
},
"node_modules/@types/resolve": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.19.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.19.1.tgz",
@@ -2457,6 +2554,12 @@
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@yr/monotone-cubic-spline": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz",
"integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==",
"license": "MIT"
},
"node_modules/abitype": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.7.tgz",
@@ -2585,6 +2688,33 @@
"node": ">= 8"
}
},
"node_modules/anymatch/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/apexcharts": {
"version": "3.54.1",
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.54.1.tgz",
"integrity": "sha512-E4et0h/J1U3r3EwS/WlqJCQIbepKbp6wGUmaAwJOMjHUP4Ci0gxanLa7FR3okx6p9coi4st6J853/Cb1NP0vpA==",
"license": "MIT",
"dependencies": {
"@yr/monotone-cubic-spline": "^1.0.3",
"svg.draggable.js": "^2.2.2",
"svg.easing.js": "^2.0.0",
"svg.filter.js": "^2.0.2",
"svg.pathmorphing.js": "^0.1.3",
"svg.resize.js": "^1.4.3",
"svg.select.js": "^3.0.1"
}
},
"node_modules/arg": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
@@ -3634,6 +3764,42 @@
"dev": true,
"license": "ISC"
},
"node_modules/flowbite": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/flowbite/-/flowbite-2.5.2.tgz",
"integrity": "sha512-kwFD3n8/YW4EG8GlY3Od9IoKND97kitO+/ejISHSqpn3vw2i5K/+ZI8Jm2V+KC4fGdnfi0XZ+TzYqQb4Q1LshA==",
"license": "MIT",
"dependencies": {
"@popperjs/core": "^2.9.3",
"flowbite-datepicker": "^1.3.0",
"mini-svg-data-uri": "^1.4.3"
}
},
"node_modules/flowbite-datepicker": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/flowbite-datepicker/-/flowbite-datepicker-1.3.2.tgz",
"integrity": "sha512-6Nfm0MCVX3mpaR7YSCjmEO2GO8CDt6CX8ZpQnGdeu03WUCWtEPQ/uy0PUiNtIJjJZWnX0Cm3H55MOhbD1g+E/g==",
"license": "MIT",
"dependencies": {
"@rollup/plugin-node-resolve": "^15.2.3",
"flowbite": "^2.0.0"
}
},
"node_modules/flowbite-svelte": {
"version": "0.47.4",
"resolved": "https://registry.npmjs.org/flowbite-svelte/-/flowbite-svelte-0.47.4.tgz",
"integrity": "sha512-8oiY/oeWA7fgkDF91MZKEBo5VmjL8El3wuqTDWAFO1j7p45BHIL6G1VGnnidgCEYlbADDQN9BIGCvyPq4J3g+w==",
"license": "MIT",
"dependencies": {
"@floating-ui/dom": "^1.6.11",
"apexcharts": "^3.54.1",
"flowbite": "^2.5.2",
"tailwind-merge": "^2.5.4"
},
"peerDependencies": {
"svelte": "^3.55.1 || ^4.0.0 || ^5.0.0"
}
},
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
@@ -3958,6 +4124,12 @@
"node": ">=0.10.0"
}
},
"node_modules/is-module": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
"license": "MIT"
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@@ -4290,6 +4462,18 @@
"node": ">=8.6"
}
},
"node_modules/micromatch/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -4660,12 +4844,12 @@
"license": "ISC"
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"license": "MIT",
"engines": {
"node": ">=8.6"
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
@@ -5662,6 +5846,107 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/svg.draggable.js": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz",
"integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==",
"license": "MIT",
"dependencies": {
"svg.js": "^2.0.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/svg.easing.js": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz",
"integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==",
"license": "MIT",
"dependencies": {
"svg.js": ">=2.3.x"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/svg.filter.js": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz",
"integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==",
"license": "MIT",
"dependencies": {
"svg.js": "^2.2.5"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/svg.js": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz",
"integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==",
"license": "MIT"
},
"node_modules/svg.pathmorphing.js": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz",
"integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==",
"license": "MIT",
"dependencies": {
"svg.js": "^2.4.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/svg.resize.js": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz",
"integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==",
"license": "MIT",
"dependencies": {
"svg.js": "^2.6.5",
"svg.select.js": "^2.1.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/svg.resize.js/node_modules/svg.select.js": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz",
"integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==",
"license": "MIT",
"dependencies": {
"svg.js": "^2.2.5"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/svg.select.js": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz",
"integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==",
"license": "MIT",
"dependencies": {
"svg.js": "^2.6.5"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/tailwind-merge": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz",
"integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/dcastil"
}
},
"node_modules/tailwindcss": {
"version": "3.4.17",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
@@ -5699,6 +5984,15 @@
"node": ">=14.0.0"
}
},
"node_modules/tailwindcss-animate": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz",
"integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==",
"license": "MIT",
"peerDependencies": {
"tailwindcss": ">=3.0.0 || insiders"
}
},
"node_modules/tailwindcss/node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -5747,6 +6041,18 @@
"url": "https://github.com/sponsors/antonk52"
}
},
"node_modules/tailwindcss/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/tailwindcss/node_modules/postcss-load-config": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",

View File

@@ -40,10 +40,14 @@
"vitest": "^2.0.4"
},
"dependencies": {
"@floating-ui/dom": "^1.6.13",
"@inlang/paraglide-sveltekit": "^0.11.1",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"flowbite": "^2.5.2",
"flowbite-svelte": "^0.47.4",
"tailwindcss-animate": "^1.0.7",
"viem": "^2.22.8"
}
}

View File

@@ -1,3 +1,3 @@
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -0,0 +1,181 @@
<script lang="ts">
import { fade, fly } from 'svelte/transition';
import type { Address } from 'viem';
export let isOpen = false;
export let onClose: () => void;
export let approvalHash: string | undefined;
export let stakingHash: string | undefined;
export let vaultAddress: Address | undefined;
export let isCheckingAllowance = false;
export let isApproving = false;
export let isStaking = false;
export let isCompleted = false;
export let amount: string | undefined;
export let isAllowanceSet = false;
export let isResettingAllowance = false;
function shortenAddress(address: string | undefined): string {
if (!address) return '';
return `${address.slice(0, 6)}...${address.slice(-4)}`;
}
function openTxOnEtherscan(hash: string | undefined) {
if (hash) {
window.open(`https://sepolia.etherscan.io/tx/${hash}`, '_blank');
}
}
function openAddressEtherscan(address: string) {
window.open(`https://sepolia.etherscan.io/address/${address}`, '_blank');
}
</script>
{#if isOpen}
<div
class="fixed inset-0 z-50 overflow-y-auto"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true"
transition:fade={{ duration: 200 }}
>
<!-- Background overlay -->
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"></div>
<!-- Modal panel -->
<div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<div
class="relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6"
transition:fly={{ y: 20, duration: 200 }}
>
<!-- Close button -->
<div class="absolute right-0 top-0 pr-4 pt-4">
<button
type="button"
class="rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none"
on:click={onClose}
>
<span class="sr-only">Close</span>
<svg
class="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="sm:flex sm:items-start">
<div class="mt-3 text-center sm:mt-0 sm:text-left w-full">
<h3 class="text-base font-semibold leading-6 text-gray-900" id="modal-title">
Staking {amount} STT tokens
</h3>
<div class="mt-6 space-y-6">
<div class="flex flex-col gap-4">
<!-- Approval Step -->
{#if isCheckingAllowance}
<div class="flex items-center gap-3">
<div class="h-8 w-8 flex items-center justify-center">
<div class="animate-spin">
<svg class="h-5 w-5 text-blue-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</div>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-gray-900">Increasing token allowance...</p>
</div>
</div>
{:else if isApproving}
<div class="flex items-center gap-3">
<div class="h-8 w-8 flex items-center justify-center">
<button
class="animate-spin"
on:click={() => openTxOnEtherscan(approvalHash)}
>
<svg class="h-5 w-5 text-blue-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</button>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-gray-900">Increasing token allowance...</p>
</div>
</div>
{:else if isAllowanceSet}
<div class="flex items-center gap-3">
<div class="h-8 w-8 flex items-center justify-center">
<svg class="h-8 w-8 text-green-500" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-gray-900">Token allowance set</p>
</div>
</div>
{/if}
<!-- Staking Step -->
{#if isStaking}
<div class="flex items-center gap-3">
<div class="h-8 w-8 flex items-center justify-center">
<button
class="animate-spin"
on:click={() => openTxOnEtherscan(stakingHash)}
>
<svg class="h-5 w-5 text-blue-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</button>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-gray-900">Staking your tokens...</p>
</div>
</div>
{:else if isCompleted}
<div class="flex items-center gap-3">
<div class="h-8 w-8 flex items-center justify-center">
<svg class="h-8 w-8 text-green-500" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-gray-900">Successfully staked {amount} STT</p>
<button
class="mt-1 text-sm text-blue-600 hover:text-blue-700 truncate"
on:click={() => vaultAddress && openAddressEtherscan(vaultAddress)}
>
Vault: {shortenAddress(vaultAddress)}
</button>
</div>
</div>
{/if}
</div>
<!-- Close button for completed state -->
{#if isCompleted}
<div class="mt-8 text-center">
<button
type="button"
class="inline-flex justify-center rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
on:click={onClose}
>
Close
</button>
</div>
{/if}
</div>
</div>
</div>
</div>
</div>
</div>
{/if}

4
src/lib/utils.ts Normal file
View File

@@ -0,0 +1,4 @@
// Function to open transaction on Etherscan
export function openEtherscan(hash: string) {
window.open(`https://sepolia.etherscan.io/tx/${hash}`, '_blank');
}

View File

@@ -112,6 +112,20 @@ const STAKING_MANAGER_ABI = [
"outputs": [{"internalType": "address[]","name": "","type": "address[]"}],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalStaked",
"outputs": [{"internalType": "uint256","name": "","type": "uint256"}],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{"internalType": "address","name": "vault","type": "address"}],
"name": "mpBalanceOf",
"outputs": [{"internalType": "uint256","name": "","type": "uint256"}],
"stateMutability": "view",
"type": "function"
}
] as const;
@@ -140,6 +154,16 @@ const VAULT_ABI = [
"outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{"internalType": "uint256", "name": "_amount", "type": "uint256"},
{"internalType": "uint256", "name": "_seconds", "type": "uint256"}
],
"name": "stake",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
] as const;
@@ -155,6 +179,29 @@ export const formattedTotalStaked = derived(totalStaked, ($total) => {
return Number(formatUnits($total, SNT_TOKEN.decimals)).toFixed(2);
});
// Add new store for total staked
export const globalTotalStaked = writable<bigint>(0n);
export const formattedGlobalTotalStaked = derived(globalTotalStaked, ($total) => {
if ($total === undefined) return '0';
return Number(formatUnits($total, SNT_TOKEN.decimals))
.toFixed(2)
.replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
});
// Add new store for token price
export const tokenPriceUsd = writable<number>(0);
// Add new store for MP balances
export const vaultMpBalances = writable<Record<Address, bigint>>({});
export const totalMpBalance = derived(vaultMpBalances, ($balances) => {
return Object.values($balances).reduce((sum, balance) => sum + balance, 0n);
});
export const formattedTotalMpBalance = derived(totalMpBalance, ($total) => {
if ($total === undefined) return '0.00';
return Number(formatUnits($total, SNT_TOKEN.decimals)).toFixed(2);
});
// Function to fetch ETH balance
async function fetchBalance(address: Address) {
try {
@@ -223,6 +270,38 @@ async function fetchAllVaultStakedAmounts(vaults: readonly Address[]) {
}
}
// Function to fetch MP balance for a single vault
async function fetchVaultMpBalance(vaultAddress: Address) {
try {
const balance = await publicClient.readContract({
address: STAKING_MANAGER.address,
abi: STAKING_MANAGER_ABI,
functionName: 'mpBalanceOf',
args: [vaultAddress]
});
return balance;
} catch (error) {
console.error(`Failed to fetch MP balance for vault ${vaultAddress}:`, error);
return 0n;
}
}
// Function to fetch all vault MP balances
async function fetchAllVaultMpBalances(vaults: readonly Address[]) {
try {
console.log('Fetching MP balances for all vaults');
const balances = await Promise.all(vaults.map(fetchVaultMpBalance));
const balancesMap = vaults.reduce((acc, vault, i) => {
acc[vault] = balances[i];
return acc;
}, {} as Record<Address, bigint>);
vaultMpBalances.set(balancesMap);
} catch (error) {
console.error('Failed to fetch vault MP balances:', error);
vaultMpBalances.set({});
}
}
// Function to fetch user vaults
async function fetchUserVaults(address: Address) {
try {
@@ -236,12 +315,49 @@ async function fetchUserVaults(address: Address) {
console.log('Received vaults:', vaults);
userVaults.set([...vaults]);
// After getting vaults, fetch their staked amounts
await fetchAllVaultStakedAmounts(vaults);
// After getting vaults, fetch their staked amounts and MP balances
await Promise.all([
fetchAllVaultStakedAmounts(vaults),
fetchAllVaultMpBalances(vaults)
]);
} catch (error) {
console.error('Failed to fetch user vaults:', error);
userVaults.set([]);
vaultStakedAmounts.set({});
vaultMpBalances.set({});
}
}
// Function to fetch total staked
async function fetchTotalStaked() {
try {
console.log('Fetching total staked amount');
const total = await publicClient.readContract({
address: STAKING_MANAGER.address,
abi: STAKING_MANAGER_ABI,
functionName: 'totalStaked'
});
console.log('Received total staked:', total.toString());
globalTotalStaked.set(total);
} catch (error) {
console.error('Failed to fetch total staked:', error);
globalTotalStaked.set(0n);
}
}
// Function to fetch token price from Binance
async function fetchTokenPrice() {
try {
console.log('Fetching SNT price from Binance');
const response = await fetch('https://api.binance.com/api/v3/ticker/price?symbol=SNTUSDT');
const data = await response.json();
const price = parseFloat(data.price);
console.log('Received SNT price:', price);
tokenPriceUsd.set(price);
return price;
} catch (error) {
console.error('Failed to fetch SNT price:', error);
return null;
}
}
@@ -249,7 +365,9 @@ async function fetchUserVaults(address: Address) {
export async function refreshBalances(address: Address) {
await Promise.all([
fetchBalance(address),
fetchSntBalance(address)
fetchSntBalance(address),
fetchTotalStaked(),
fetchUserVaults(address)
]);
}
@@ -382,7 +500,17 @@ export async function registerVault(vaultAddress: Address) {
}
// Function to stake tokens
export async function stakeTokens(vaultAddress: Address, amount: bigint) {
export async function stakeTokens(
vaultAddress: Address,
amount: bigint,
callbacks?: {
onApprovalSubmitted?: (hash: string) => void;
onApprovalConfirmed?: () => void;
onStakingSubmitted?: (hash: string) => void;
onStakingConfirmed?: () => void;
onAllowanceAlreadySet?: () => void;
}
) {
const address = get(walletAddress);
const client = get(walletClient);
@@ -390,12 +518,103 @@ export async function stakeTokens(vaultAddress: Address, amount: bigint) {
throw new Error('Wallet not connected');
}
// ... staking implementation will go here ...
console.log('Staking tokens:', { vaultAddress, amount: amount.toString() });
// First check current allowance
const currentAllowance = await publicClient.readContract({
address: SNT_TOKEN.address,
abi: CONTRACT_ABI,
functionName: 'allowance',
args: [address, vaultAddress]
});
// After staking, refresh balances since tokens were transferred
console.log('Current allowance:', currentAllowance.toString());
let approvalHash: `0x${string}` | undefined;
let allowanceWasSet = false;
// Only approve if the current allowance is less than the amount we want to stake
if (currentAllowance < amount) {
console.log('Approving tokens...');
// If there's an existing non-zero allowance, we need to reset it first
if (currentAllowance > 0n) {
console.log('Resetting existing allowance to 0...');
approvalHash = await client.writeContract({
chain: sepolia,
account: address,
address: SNT_TOKEN.address,
abi: CONTRACT_ABI,
functionName: 'approve',
args: [vaultAddress, 0n]
});
console.log('Reset allowance transaction hash:', approvalHash);
callbacks?.onApprovalSubmitted?.(approvalHash);
const resetReceipt = await publicClient.waitForTransactionReceipt({ hash: approvalHash });
if (resetReceipt.status !== 'success') {
throw new Error('Reset allowance transaction failed');
}
}
// Now set the new allowance
approvalHash = await client.writeContract({
chain: sepolia,
account: address,
address: SNT_TOKEN.address,
abi: CONTRACT_ABI,
functionName: 'approve',
args: [vaultAddress, amount]
});
console.log('Token approval transaction hash:', approvalHash);
callbacks?.onApprovalSubmitted?.(approvalHash);
const approvalReceipt = await publicClient.waitForTransactionReceipt({ hash: approvalHash });
if (approvalReceipt.status !== 'success') {
throw new Error('Approval transaction failed');
}
callbacks?.onApprovalConfirmed?.();
} else {
console.log('Sufficient allowance already exists');
allowanceWasSet = true;
callbacks?.onAllowanceAlreadySet?.();
}
// Then stake the tokens
const stakingHash = await client.writeContract({
chain: sepolia,
account: address,
address: vaultAddress,
abi: VAULT_ABI,
functionName: 'stake',
args: [amount, 0n] // 0 seconds lock period
});
console.log('Staking transaction hash:', stakingHash);
callbacks?.onStakingSubmitted?.(stakingHash);
const receipt = await publicClient.waitForTransactionReceipt({ hash: stakingHash });
console.log('Staking receipt:', receipt);
if (receipt.status !== 'success') {
throw new Error('Staking transaction failed');
}
callbacks?.onStakingConfirmed?.();
// Refresh balances and vault data
await refreshBalances(address);
return { approvalHash, hash: stakingHash, receipt, allowanceWasSet };
}
function formatAmount(amount: bigint): string {
return Number(formatUnits(amount, SNT_TOKEN.decimals)).toFixed(2);
}
// Initial fetch of total staked
fetchTotalStaked();
// Export the fetch functions to be used by components
export { fetchTotalStaked, fetchTokenPrice };

View File

@@ -1,7 +1,9 @@
<script lang="ts">
import { walletAddress, formattedBalance, formattedSntBalance, network, SNT_TOKEN, sntError, userVaults, formattedTotalStaked, vaultStakedAmounts } from '$lib/viem';
import { walletAddress, formattedBalance, formattedSntBalance, network, SNT_TOKEN, sntError, userVaults, formattedTotalStaked, vaultStakedAmounts, formattedGlobalTotalStaked, fetchTotalStaked, fetchTokenPrice, tokenPriceUsd, globalTotalStaked, vaultMpBalances, formattedTotalMpBalance } from '$lib/viem';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { formatUnits } from 'viem';
import { onMount } from 'svelte';
const SNT_USD_RATE = 0.04298;
@@ -28,6 +30,10 @@
]
};
// Calculate total value in USD
$: totalValueUsd = $globalTotalStaked ?
Math.floor(Number(formatUnits($globalTotalStaked, SNT_TOKEN.decimals)) * $tokenPriceUsd).toLocaleString() : '0';
function handleStartStaking() {
goto('/stake');
}
@@ -41,8 +47,19 @@
}
function formatAmount(amount: bigint): string {
return Number(formatUnits(amount, SNT_TOKEN.decimals)).toFixed(4);
return Number(formatUnits(amount, SNT_TOKEN.decimals)).toFixed(2);
}
// Fetch data when navigating to overview page
$: if ($page.url.pathname === '/') {
fetchTotalStaked();
fetchTokenPrice();
}
onMount(() => {
fetchTotalStaked();
fetchTokenPrice();
});
</script>
<div class="mx-auto max-w-7xl px-6 lg:px-8">
@@ -68,7 +85,7 @@
<h3 class="text-sm font-medium leading-6 text-gray-500">Your Multiplier Points</h3>
<div class="mt-4 flex items-baseline justify-end gap-x-2">
<span class="text-4xl font-bold tracking-tight text-gray-900">
{userStats.availableRewards}
{$formattedTotalMpBalance}
</span>
<span class="text-sm font-semibold leading-6 text-gray-500">MPs</span>
</div>
@@ -117,7 +134,7 @@
<h3 class="text-sm font-medium leading-6 text-gray-500">Total SNT Staked</h3>
<div class="mt-4 flex items-baseline justify-end gap-x-2">
<span class="text-4xl font-bold tracking-tight text-gray-900">
{stakingStats.globalStats.totalSntStaked}
{$formattedGlobalTotalStaked}
</span>
<span class="text-sm font-semibold leading-6 text-gray-500">{SNT_TOKEN.symbol}</span>
</div>
@@ -129,7 +146,7 @@
<h3 class="text-sm font-medium leading-6 text-gray-500">Total Value Staked</h3>
<div class="mt-4 flex items-baseline justify-end gap-x-2">
<span class="text-4xl font-bold tracking-tight text-gray-900">
${stakingStats.globalStats.totalValueUsd}
${totalValueUsd}
</span>
<span class="text-sm font-semibold leading-6 text-gray-500">USD</span>
</div>
@@ -166,8 +183,12 @@
{shortenAddress(vault)}
</button>
</td>
<td class="whitespace-nowrap px-6 py-4 text-right text-sm text-gray-900">-</td>
<td class="whitespace-nowrap px-6 py-4 text-right text-sm text-gray-900">-</td>
<td class="whitespace-nowrap px-6 py-4 text-right text-sm text-gray-900">
{$vaultStakedAmounts[vault] ? formatAmount($vaultStakedAmounts[vault]) : '0.00'} {SNT_TOKEN.symbol}
</td>
<td class="whitespace-nowrap px-6 py-4 text-right text-sm text-gray-900">
{$vaultMpBalances[vault] ? formatAmount($vaultMpBalances[vault]) : '0.00'} MP
</td>
<td class="whitespace-nowrap px-6 py-4 text-right text-sm text-gray-900">-</td>
</tr>
{/each}
@@ -193,14 +214,18 @@
<div class="mt-4 space-y-3">
<div class="flex justify-between">
<span class="text-sm text-gray-500">Staked Amount</span>
<span class="text-sm font-medium text-gray-900">-</span>
<span class="text-sm font-medium text-gray-900">
{$vaultStakedAmounts[vault] ? formatAmount($vaultStakedAmounts[vault]) : '0.00'} {SNT_TOKEN.symbol}
</span>
</div>
<div class="flex justify-between">
<span class="text-sm text-gray-500">Available Rewards</span>
<span class="text-sm font-medium text-gray-900">-</span>
<span class="text-sm text-gray-500">MPs</span>
<span class="text-sm font-medium text-gray-900">
{$vaultMpBalances[vault] ? formatAmount($vaultMpBalances[vault]) : '0.00'} MP
</span>
</div>
<div class="flex justify-between">
<span class="text-sm text-gray-500">APR</span>
<span class="text-sm text-gray-500">Rewards</span>
<span class="text-sm font-medium text-gray-900">-</span>
</div>
</div>

View File

@@ -1,20 +1,33 @@
<script lang="ts">
import { walletAddress, formattedSntBalance, SNT_TOKEN, userVaults, deployVault, VAULT_FACTORY, publicClient, vaultStakedAmounts } from '$lib/viem';
import { decodeEventLog, formatUnits } from 'viem';
import { walletAddress, formattedSntBalance, SNT_TOKEN, userVaults, deployVault, VAULT_FACTORY, publicClient, vaultStakedAmounts, stakeTokens } from '$lib/viem';
import { decodeEventLog, formatUnits, parseUnits, type Address } from 'viem';
import TransactionModal from '$lib/components/TransactionModal.svelte';
import type { Address, Log } from 'viem';
import StakingModal from '$lib/components/StakingModal.svelte';
let amount = '';
let selectedVaultId = '';
let selectedLockVaultId = '';
let selectedLockVaultId: Address | '' = '';
let lockDurationDays = 365; // Default to 1 year
let isDeploying = false;
let deployError: string | undefined;
let stakingError: string | undefined;
// Transaction modal state
let isModalOpen = false;
let txHash: string | undefined;
let deployedVaultAddress: Address | undefined;
// Staking modal state
let isStakingModalOpen = false;
let isCheckingAllowance = false;
let isApproving = false;
let isStaking = false;
let isCompleted = false;
let approvalHash: string | undefined;
let stakingHash: string | undefined;
let isAllowanceSet = false;
let isResettingAllowance = false;
function shortenAddress(address: string): string {
return `${address.slice(0, 6)}...${address.slice(-4)}`;
}
@@ -23,6 +36,10 @@
return Number(formatUnits(amount, SNT_TOKEN.decimals)).toFixed(2);
}
function openAddressEtherscan(address: string) {
window.open(`https://sepolia.etherscan.io/address/${address}`, '_blank');
}
// Dummy data for demonstration
const existingVaults = [
{ id: 1, staked: '2,000' },
@@ -36,8 +53,66 @@
apr: '100'
};
function handleStake() {
alert('Staking functionality will be implemented later');
async function handleStake() {
if (!selectedVaultId || !amount) return;
try {
stakingError = undefined;
isStakingModalOpen = true;
isCheckingAllowance = true;
isResettingAllowance = false;
approvalHash = undefined;
stakingHash = undefined;
isAllowanceSet = false;
isApproving = false;
isStaking = false;
isCompleted = false;
const amountToStake = parseUnits(amount, SNT_TOKEN.decimals);
// Subscribe to state updates from stakeTokens
await stakeTokens(selectedVaultId as Address, amountToStake, {
onApprovalSubmitted: (hash) => {
approvalHash = hash;
isApproving = true;
isCheckingAllowance = false;
},
onApprovalConfirmed: () => {
isAllowanceSet = true;
isApproving = false;
},
onStakingSubmitted: (hash) => {
stakingHash = hash;
isStaking = true;
},
onStakingConfirmed: () => {
isStaking = false;
isCompleted = true;
},
onAllowanceAlreadySet: () => {
isCheckingAllowance = false;
isAllowanceSet = true;
}
});
} catch (error) {
console.error('Failed to stake:', error);
stakingError = error instanceof Error ? error.message : 'Failed to stake';
isStakingModalOpen = false;
} finally {
isCheckingAllowance = false;
isResettingAllowance = false;
}
}
function handleCloseStakingModal() {
isStakingModalOpen = false;
approvalHash = undefined;
stakingHash = undefined;
isApproving = false;
isStaking = false;
isCompleted = false;
isResettingAllowance = false;
amount = '';
selectedVaultId = '';
}
@@ -189,7 +264,9 @@
<form
class="mt-6"
on:submit|preventDefault={handleStake}
on:submit|preventDefault={async (e) => {
await handleStake();
}}
>
<div class="space-y-2">
<label
@@ -292,16 +369,95 @@
{/if}
{/each}
</select>
{#if selectedLockVaultId}
<div class="mt-4 rounded-lg bg-gray-50 px-4 py-3">
<button
type="button"
class="text-sm text-blue-600 hover:text-blue-700 truncate"
on:click={() => openAddressEtherscan(selectedLockVaultId)}
>
{selectedLockVaultId}
</button>
<p class="mt-1 text-sm text-gray-600">
Balance: {formatAmount($vaultStakedAmounts[selectedLockVaultId])} {SNT_TOKEN.symbol}
</p>
</div>
{/if}
</div>
<div class="mt-6 space-y-2">
<label class="block text-sm font-medium leading-6 text-gray-900">
Lock Duration
</label>
<div>
<input
type="range"
min="1"
max="1460"
bind:value={lockDurationDays}
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-blue-600"
/>
<div class="mt-1 flex justify-between text-xs text-gray-500">
<span>1 day</span>
<span>1 year</span>
<span>2 years</span>
<span>3 years</span>
<span>4 years</span>
</div>
</div>
<div class="mt-4 flex gap-4 items-start">
<div class="flex-1">
<label for="durationYears" class="block text-sm font-medium text-gray-700 mb-1">Years</label>
<div class="relative">
<input
type="number"
id="durationYears"
value={Number((lockDurationDays / 365).toFixed(2))}
on:input={(e) => {
const years = Number(e.currentTarget.value);
if (!isNaN(years)) {
lockDurationDays = Math.round(years * 365);
}
}}
min="0"
max="4"
step="1"
class="block w-full rounded-lg border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6"
placeholder="0"
/>
</div>
</div>
<div class="flex-none pt-7">
<span class="text-sm text-gray-500">or</span>
</div>
<div class="flex-1">
<label for="durationDays" class="block text-sm font-medium text-gray-700 mb-1">Days</label>
<div class="relative">
<input
type="number"
id="durationDays"
bind:value={lockDurationDays}
min="1"
max="1460"
class="block w-full rounded-lg border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6"
placeholder="0"
/>
</div>
</div>
</div>
</div>
<div class="mt-6 flex items-center justify-between text-sm">
<span class="text-gray-500">Lock Duration</span>
<span class="font-medium text-gray-900">12 months</span>
</div>
<div class="mt-6 flex items-center justify-between text-sm">
<span class="text-gray-500">MP Bonus Rate</span>
<span class="font-medium text-gray-900">+50%</span>
<span class="text-gray-500">MP Bonus</span>
<span class="font-medium">
{#if selectedLockVaultId && $vaultStakedAmounts[selectedLockVaultId]}
{@const bonus = $vaultStakedAmounts[selectedLockVaultId] * BigInt(Math.floor(lockDurationDays / 365 * 1e18)) / 1000000000000000000n}
<div class={bonus > 0n ? "px-2 py-0.5 rounded bg-green-50" : ""}>
<span class={bonus > 0n ? "text-green-600" : "text-gray-900"}>{formatAmount(bonus)} MP</span>
</div>
{/if}
</span>
</div>
<div class="mt-6">
@@ -319,7 +475,6 @@
<p class="text-sm font-medium text-gray-900">
You need to stake tokens in a vault first!
</p>
</div>
</div>
{/if}
@@ -344,4 +499,19 @@
vaultAddress={deployedVaultAddress}
isDeploying={isDeploying}
isDeployed={!isDeploying && txHash !== undefined}
/>
<StakingModal
isOpen={isStakingModalOpen}
onClose={handleCloseStakingModal}
approvalHash={approvalHash}
stakingHash={stakingHash}
vaultAddress={selectedVaultId as Address | undefined}
isCheckingAllowance={isCheckingAllowance}
isApproving={isApproving}
isStaking={isStaking}
isCompleted={isCompleted}
amount={amount}
isAllowanceSet={isAllowanceSet}
isResettingAllowance={isResettingAllowance}
/>

View File

@@ -1,14 +1,9 @@
import containerQueries from '@tailwindcss/container-queries';
import forms from '@tailwindcss/forms';
import typography from '@tailwindcss/typography';
import type { Config } from 'tailwindcss';
import { type Config } from 'tailwindcss';
export default {
content: ['./src/**/*.{html,js,svelte,ts}'],
theme: {
extend: {}
},
plugins: [typography, forms, containerQueries]
plugins: []
} satisfies Config;