mirror of
https://github.com/vacp2p/staking-demo-app.git
synced 2026-01-06 19:44:00 -05:00
staking and vault balance update
This commit is contained in:
314
package-lock.json
generated
314
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
@import 'tailwindcss/base';
|
||||
@import 'tailwindcss/components';
|
||||
@import 'tailwindcss/utilities';
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
181
src/lib/components/StakingModal.svelte
Normal file
181
src/lib/components/StakingModal.svelte
Normal 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
4
src/lib/utils.ts
Normal 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');
|
||||
}
|
||||
231
src/lib/viem.ts
231
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<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 };
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user