From 4aaab3d76ecef3d27f6abf9201d4513ae54d048a Mon Sep 17 00:00:00 2001 From: mart1n <20109376+mart1n-xyz@users.noreply.github.com> Date: Mon, 20 Jan 2025 15:08:23 +0100 Subject: [PATCH] staking and vault balance update --- package-lock.json | 314 ++++++++++++++++++++++++- package.json | 4 + src/app.css | 6 +- src/lib/components/StakingModal.svelte | 181 ++++++++++++++ src/lib/utils.ts | 4 + src/lib/viem.ts | 231 +++++++++++++++++- src/routes/+page.svelte | 47 +++- src/routes/stake/+page.svelte | 200 ++++++++++++++-- tailwind.config.ts | 9 +- 9 files changed, 950 insertions(+), 46 deletions(-) create mode 100644 src/lib/components/StakingModal.svelte create mode 100644 src/lib/utils.ts diff --git a/package-lock.json b/package-lock.json index f4cf784..db6a5d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 44882b0..f7d3e23 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/src/app.css b/src/app.css index a31e444..b5c61c9 100644 --- a/src/app.css +++ b/src/app.css @@ -1,3 +1,3 @@ -@import 'tailwindcss/base'; -@import 'tailwindcss/components'; -@import 'tailwindcss/utilities'; +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/src/lib/components/StakingModal.svelte b/src/lib/components/StakingModal.svelte new file mode 100644 index 0000000..9c7f58e --- /dev/null +++ b/src/lib/components/StakingModal.svelte @@ -0,0 +1,181 @@ + + + +{#if isOpen} + +{/if} \ No newline at end of file diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..a54bd91 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,4 @@ +// Function to open transaction on Etherscan +export function openEtherscan(hash: string) { + window.open(`https://sepolia.etherscan.io/tx/${hash}`, '_blank'); +} \ No newline at end of file diff --git a/src/lib/viem.ts b/src/lib/viem.ts index 5470213..91d3f41 100644 --- a/src/lib/viem.ts +++ b/src/lib/viem.ts @@ -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(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(0); + +// Add new store for MP balances +export const vaultMpBalances = writable>({}); +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); + 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 }; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index dcb5495..f3fc17b 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,7 +1,9 @@
@@ -68,7 +85,7 @@

Your Multiplier Points

- {userStats.availableRewards} + {$formattedTotalMpBalance} MPs
@@ -117,7 +134,7 @@

Total SNT Staked

- {stakingStats.globalStats.totalSntStaked} + {$formattedGlobalTotalStaked} {SNT_TOKEN.symbol}
@@ -129,7 +146,7 @@

Total Value Staked

- ${stakingStats.globalStats.totalValueUsd} + ${totalValueUsd} USD
@@ -166,8 +183,12 @@ {shortenAddress(vault)} - - - - + + {$vaultStakedAmounts[vault] ? formatAmount($vaultStakedAmounts[vault]) : '0.00'} {SNT_TOKEN.symbol} + + + {$vaultMpBalances[vault] ? formatAmount($vaultMpBalances[vault]) : '0.00'} MP + - {/each} @@ -193,14 +214,18 @@
Staked Amount - - + + {$vaultStakedAmounts[vault] ? formatAmount($vaultStakedAmounts[vault]) : '0.00'} {SNT_TOKEN.symbol} +
- Available Rewards - - + MPs + + {$vaultMpBalances[vault] ? formatAmount($vaultMpBalances[vault]) : '0.00'} MP +
- APR + Rewards -
diff --git a/src/routes/stake/+page.svelte b/src/routes/stake/+page.svelte index 0b918f1..4f4bb6c 100644 --- a/src/routes/stake/+page.svelte +++ b/src/routes/stake/+page.svelte @@ -1,20 +1,33 @@