Extract snatch selection into reusable composable (#30)
## Summary - add a useSnatchSelection composable that centralises snatch shortfall calculations, position filtering, and RPC memoisation - refactor StakeHolder.vue to consume the composable instead of reimplementing the flow inline - introduce Vitest config and first composable tests (useSnatchSelection.spec.ts) to cover empty/partial fills and ownership edge cases - wire up project tooling updates so the new tests run (jsdom dep, updated package metadata) ## Testing - cd web-app && npm install - npm test resolves #24 Co-authored-by: openhands <openhands@all-hands.dev> Reviewed-on: https://codeberg.org/johba/harb/pulls/30
This commit is contained in:
parent
76d84341de
commit
26a8771848
6 changed files with 1202 additions and 311 deletions
634
web-app/package-lock.json
generated
634
web-app/package-lock.json
generated
|
|
@ -31,6 +31,7 @@
|
||||||
"@vitejs/plugin-vue": "^5.2.1",
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
"@vue/tsconfig": "^0.7.0",
|
"@vue/tsconfig": "^0.7.0",
|
||||||
"gh-pages": "^6.1.1",
|
"gh-pages": "^6.1.1",
|
||||||
|
"jsdom": "^27.0.0",
|
||||||
"npm-run-all2": "^7.0.2",
|
"npm-run-all2": "^7.0.2",
|
||||||
"typescript": "~5.7.3",
|
"typescript": "~5.7.3",
|
||||||
"vite": "^6.0.11",
|
"vite": "^6.0.11",
|
||||||
|
|
@ -73,6 +74,61 @@
|
||||||
"url": "https://github.com/sponsors/antfu"
|
"url": "https://github.com/sponsors/antfu"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@asamuzakjp/css-color": {
|
||||||
|
"version": "4.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.0.5.tgz",
|
||||||
|
"integrity": "sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@csstools/css-calc": "^2.1.4",
|
||||||
|
"@csstools/css-color-parser": "^3.1.0",
|
||||||
|
"@csstools/css-parser-algorithms": "^3.0.5",
|
||||||
|
"@csstools/css-tokenizer": "^3.0.4",
|
||||||
|
"lru-cache": "^11.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
|
||||||
|
"version": "11.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz",
|
||||||
|
"integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": "20 || >=22"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@asamuzakjp/dom-selector": {
|
||||||
|
"version": "6.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.5.6.tgz",
|
||||||
|
"integrity": "sha512-Mj3Hu9ymlsERd7WOsUKNUZnJYL4IZ/I9wVVYgtvOsWYiEFbkQ4G7VRIh2USxTVW4BBDIsLG+gBUgqOqf2Kvqow==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@asamuzakjp/nwsapi": "^2.3.9",
|
||||||
|
"bidi-js": "^1.0.3",
|
||||||
|
"css-tree": "^3.1.0",
|
||||||
|
"is-potential-custom-element-name": "^1.0.1",
|
||||||
|
"lru-cache": "^11.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": {
|
||||||
|
"version": "11.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz",
|
||||||
|
"integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": "20 || >=22"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@asamuzakjp/nwsapi": {
|
||||||
|
"version": "2.3.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz",
|
||||||
|
"integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.27.1",
|
"version": "7.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
||||||
|
|
@ -609,6 +665,144 @@
|
||||||
"zustand": "5.0.3"
|
"zustand": "5.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@csstools/color-helpers": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
|
||||||
|
"devOptional": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/csstools"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/csstools"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT-0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@csstools/css-calc": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
|
||||||
|
"devOptional": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/csstools"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/csstools"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@csstools/css-parser-algorithms": "^3.0.5",
|
||||||
|
"@csstools/css-tokenizer": "^3.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@csstools/css-color-parser": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
|
||||||
|
"devOptional": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/csstools"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/csstools"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@csstools/color-helpers": "^5.1.0",
|
||||||
|
"@csstools/css-calc": "^2.1.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@csstools/css-parser-algorithms": "^3.0.5",
|
||||||
|
"@csstools/css-tokenizer": "^3.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@csstools/css-parser-algorithms": {
|
||||||
|
"version": "3.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
|
||||||
|
"integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
|
||||||
|
"devOptional": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/csstools"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/csstools"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@csstools/css-tokenizer": "^3.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@csstools/css-syntax-patches-for-csstree": {
|
||||||
|
"version": "1.0.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.14.tgz",
|
||||||
|
"integrity": "sha512-zSlIxa20WvMojjpCSy8WrNpcZ61RqfTfX3XTaOeVlGJrt/8HF3YbzgFZa01yTbT4GWQLwfTcC3EB8i3XnB647Q==",
|
||||||
|
"devOptional": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/csstools"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/csstools"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT-0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"postcss": "^8.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@csstools/css-tokenizer": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
|
||||||
|
"devOptional": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/csstools"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/csstools"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@ctrl/tinycolor": {
|
"node_modules/@ctrl/tinycolor": {
|
||||||
"version": "3.6.1",
|
"version": "3.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
|
||||||
|
|
@ -4994,6 +5188,16 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/agent-base": {
|
||||||
|
"version": "7.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
|
||||||
|
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/alien-signals": {
|
"node_modules/alien-signals": {
|
||||||
"version": "1.0.13",
|
"version": "1.0.13",
|
||||||
"resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.13.tgz",
|
"resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.13.tgz",
|
||||||
|
|
@ -5162,6 +5366,16 @@
|
||||||
"baseline-browser-mapping": "dist/cli.js"
|
"baseline-browser-mapping": "dist/cli.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bidi-js": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"require-from-string": "^2.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/big.js": {
|
"node_modules/big.js": {
|
||||||
"version": "6.2.2",
|
"version": "6.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.2.tgz",
|
||||||
|
|
@ -5710,12 +5924,92 @@
|
||||||
"uncrypto": "^0.1.3"
|
"uncrypto": "^0.1.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/css-tree": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mdn-data": "2.12.2",
|
||||||
|
"source-map-js": "^1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cssstyle": {
|
||||||
|
"version": "5.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.1.tgz",
|
||||||
|
"integrity": "sha512-g5PC9Aiph9eiczFpcgUhd9S4UUO3F+LHGRIi5NUMZ+4xtoIYbHNZwZnWA2JsFGe8OU8nl4WyaEFiZuGuxlutJQ==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@asamuzakjp/css-color": "^4.0.3",
|
||||||
|
"@csstools/css-syntax-patches-for-csstree": "^1.0.14",
|
||||||
|
"css-tree": "^3.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/csstype": {
|
"node_modules/csstype": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/data-urls": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"whatwg-mimetype": "^4.0.0",
|
||||||
|
"whatwg-url": "^15.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/data-urls/node_modules/tr46": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"punycode": "^2.3.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/data-urls/node_modules/webidl-conversions": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/data-urls/node_modules/whatwg-url": {
|
||||||
|
"version": "15.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz",
|
||||||
|
"integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tr46": "^6.0.0",
|
||||||
|
"webidl-conversions": "^8.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/date-fns": {
|
"node_modules/date-fns": {
|
||||||
"version": "2.30.0",
|
"version": "2.30.0",
|
||||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
|
||||||
|
|
@ -5771,6 +6065,13 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/decimal.js": {
|
||||||
|
"version": "10.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
|
||||||
|
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/decode-uri-component": {
|
"node_modules/decode-uri-component": {
|
||||||
"version": "0.2.2",
|
"version": "0.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
|
||||||
|
|
@ -7036,6 +7337,47 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/html-encoding-sniffer": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"whatwg-encoding": "^3.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/http-proxy-agent": {
|
||||||
|
"version": "7.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
|
||||||
|
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"agent-base": "^7.1.0",
|
||||||
|
"debug": "^4.3.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/https-proxy-agent": {
|
||||||
|
"version": "7.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
|
||||||
|
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"agent-base": "^7.1.2",
|
||||||
|
"debug": "4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/human-signals": {
|
"node_modules/human-signals": {
|
||||||
"version": "8.0.1",
|
"version": "8.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz",
|
||||||
|
|
@ -7046,6 +7388,19 @@
|
||||||
"node": ">=18.18.0"
|
"node": ">=18.18.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/iconv-lite": {
|
||||||
|
"version": "0.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
|
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/idb-keyval": {
|
"node_modules/idb-keyval": {
|
||||||
"version": "6.2.1",
|
"version": "6.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz",
|
||||||
|
|
@ -7245,6 +7600,13 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-potential-custom-element-name": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/is-regex": {
|
"node_modules/is-regex": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
|
||||||
|
|
@ -7410,6 +7772,105 @@
|
||||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/jsdom": {
|
||||||
|
"version": "27.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.0.0.tgz",
|
||||||
|
"integrity": "sha512-lIHeR1qlIRrIN5VMccd8tI2Sgw6ieYXSVktcSHaNe3Z5nE/tcPQYQWOq00wxMvYOsz+73eAkNenVvmPC6bba9A==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@asamuzakjp/dom-selector": "^6.5.4",
|
||||||
|
"cssstyle": "^5.3.0",
|
||||||
|
"data-urls": "^6.0.0",
|
||||||
|
"decimal.js": "^10.5.0",
|
||||||
|
"html-encoding-sniffer": "^4.0.0",
|
||||||
|
"http-proxy-agent": "^7.0.2",
|
||||||
|
"https-proxy-agent": "^7.0.6",
|
||||||
|
"is-potential-custom-element-name": "^1.0.1",
|
||||||
|
"parse5": "^7.3.0",
|
||||||
|
"rrweb-cssom": "^0.8.0",
|
||||||
|
"saxes": "^6.0.0",
|
||||||
|
"symbol-tree": "^3.2.4",
|
||||||
|
"tough-cookie": "^6.0.0",
|
||||||
|
"w3c-xmlserializer": "^5.0.0",
|
||||||
|
"webidl-conversions": "^8.0.0",
|
||||||
|
"whatwg-encoding": "^3.1.1",
|
||||||
|
"whatwg-mimetype": "^4.0.0",
|
||||||
|
"whatwg-url": "^15.0.0",
|
||||||
|
"ws": "^8.18.2",
|
||||||
|
"xml-name-validator": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"canvas": "^3.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"canvas": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jsdom/node_modules/tr46": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"punycode": "^2.3.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jsdom/node_modules/webidl-conversions": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jsdom/node_modules/whatwg-url": {
|
||||||
|
"version": "15.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz",
|
||||||
|
"integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tr46": "^6.0.0",
|
||||||
|
"webidl-conversions": "^8.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jsdom/node_modules/ws": {
|
||||||
|
"version": "8.18.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
|
||||||
|
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jsesc": {
|
"node_modules/jsesc": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||||
|
|
@ -7651,6 +8112,13 @@
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mdn-data": {
|
||||||
|
"version": "2.12.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz",
|
||||||
|
"integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "CC0-1.0"
|
||||||
|
},
|
||||||
"node_modules/memoize-one": {
|
"node_modules/memoize-one": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
|
||||||
|
|
@ -8239,6 +8707,32 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/parse5": {
|
||||||
|
"version": "7.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
|
||||||
|
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"entities": "^6.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/parse5/node_modules/entities": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/path-browserify": {
|
"node_modules/path-browserify": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
|
||||||
|
|
@ -8524,6 +9018,16 @@
|
||||||
"once": "^1.3.1"
|
"once": "^1.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/punycode": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/qrcode": {
|
"node_modules/qrcode": {
|
||||||
"version": "1.5.3",
|
"version": "1.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.3.tgz",
|
||||||
|
|
@ -8671,6 +9175,16 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/require-from-string": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/require-main-filename": {
|
"node_modules/require-main-filename": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||||
|
|
@ -8736,6 +9250,13 @@
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rrweb-cssom": {
|
||||||
|
"version": "0.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
|
||||||
|
"integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/run-applescript": {
|
"node_modules/run-applescript": {
|
||||||
"version": "7.1.0",
|
"version": "7.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz",
|
||||||
|
|
@ -8819,6 +9340,13 @@
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/safer-buffer": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/sass": {
|
"node_modules/sass": {
|
||||||
"version": "1.93.1",
|
"version": "1.93.1",
|
||||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.93.1.tgz",
|
"resolved": "https://registry.npmjs.org/sass/-/sass-1.93.1.tgz",
|
||||||
|
|
@ -8839,6 +9367,19 @@
|
||||||
"@parcel/watcher": "^2.4.1"
|
"@parcel/watcher": "^2.4.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/saxes": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"xmlchars": "^2.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=v12.22.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "7.7.2",
|
"version": "7.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||||
|
|
@ -9277,6 +9818,13 @@
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/symbol-tree": {
|
||||||
|
"version": "3.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
||||||
|
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/thread-stream": {
|
"node_modules/thread-stream": {
|
||||||
"version": "0.15.2",
|
"version": "0.15.2",
|
||||||
"resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-0.15.2.tgz",
|
"resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-0.15.2.tgz",
|
||||||
|
|
@ -9379,6 +9927,26 @@
|
||||||
"@popperjs/core": "^2.9.0"
|
"@popperjs/core": "^2.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tldts": {
|
||||||
|
"version": "7.0.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.16.tgz",
|
||||||
|
"integrity": "sha512-5bdPHSwbKTeHmXrgecID4Ljff8rQjv7g8zKQPkCozRo2HWWni+p310FSn5ImI+9kWw9kK4lzOB5q/a6iv0IJsw==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tldts-core": "^7.0.16"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"tldts": "bin/cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tldts-core": {
|
||||||
|
"version": "7.0.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.16.tgz",
|
||||||
|
"integrity": "sha512-XHhPmHxphLi+LGbH0G/O7dmUH9V65OY20R7vH8gETHsp5AZCjBk9l8sqmRKLaGOxnETU7XNSDUPtewAy/K6jbA==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/to-buffer": {
|
"node_modules/to-buffer": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz",
|
||||||
|
|
@ -9416,6 +9984,19 @@
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tough-cookie": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"tldts": "^7.0.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tr46": {
|
"node_modules/tr46": {
|
||||||
"version": "0.0.3",
|
"version": "0.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||||
|
|
@ -10275,6 +10856,19 @@
|
||||||
"typescript": ">=5.0.0"
|
"typescript": ">=5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/w3c-xmlserializer": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"xml-name-validator": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/webextension-polyfill": {
|
"node_modules/webextension-polyfill": {
|
||||||
"version": "0.10.0",
|
"version": "0.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.10.0.tgz",
|
||||||
|
|
@ -10287,6 +10881,29 @@
|
||||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||||
"license": "BSD-2-Clause"
|
"license": "BSD-2-Clause"
|
||||||
},
|
},
|
||||||
|
"node_modules/whatwg-encoding": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"iconv-lite": "0.6.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/whatwg-mimetype": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/whatwg-url": {
|
"node_modules/whatwg-url": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||||
|
|
@ -10489,6 +11106,23 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/xml-name-validator": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/xmlchars": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/xmlhttprequest-ssl": {
|
"node_modules/xmlhttprequest-ssl": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"build-only": "vite build",
|
"build-only": "vite build",
|
||||||
"type-check": "vue-tsc --build",
|
"type-check": "vue-tsc --build",
|
||||||
"subtree": "git subtree push --prefix dist origin gh-pages"
|
"subtree": "git subtree push --prefix dist origin gh-pages",
|
||||||
|
"test": "vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/vue-query": "^5.64.2",
|
"@tanstack/vue-query": "^5.64.2",
|
||||||
|
|
@ -35,6 +36,7 @@
|
||||||
"@vitejs/plugin-vue": "^5.2.1",
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
"@vue/tsconfig": "^0.7.0",
|
"@vue/tsconfig": "^0.7.0",
|
||||||
"gh-pages": "^6.1.1",
|
"gh-pages": "^6.1.1",
|
||||||
|
"jsdom": "^27.0.0",
|
||||||
"npm-run-all2": "^7.0.2",
|
"npm-run-all2": "^7.0.2",
|
||||||
"typescript": "~5.7.3",
|
"typescript": "~5.7.3",
|
||||||
"vite": "^6.0.11",
|
"vite": "^6.0.11",
|
||||||
|
|
|
||||||
|
|
@ -1,127 +1,95 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="hold-inner">
|
<div class="hold-inner">
|
||||||
<div class="stake-inner">
|
<div class="stake-inner">
|
||||||
<template v-if="!statCollection.initialized">
|
<template v-if="!statCollection.initialized">
|
||||||
<div>
|
<div>
|
||||||
<f-loader></f-loader>
|
<f-loader></f-loader>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="subheader2">Token Amount</div>
|
<div class="subheader2">Token Amount</div>
|
||||||
<FSlider :min="minStakeAmount" :max="maxStakeAmount" v-model="stake.stakingAmountNumber"></FSlider>
|
<FSlider :min="minStakeAmount" :max="maxStakeAmount" v-model="stake.stakingAmountNumber"></FSlider>
|
||||||
<div class="formular">
|
<div class="formular">
|
||||||
<div class="row row-1">
|
<div class="row row-1">
|
||||||
<f-input label="Staking Amount" class="staking-amount" v-model="stake.stakingAmountNumber">
|
<f-input label="Staking Amount" class="staking-amount" v-model="stake.stakingAmountNumber">
|
||||||
<template v-slot:details>
|
<template v-slot:details>
|
||||||
<div class="balance">Balance: {{ maxStakeAmount.toFixed(2) }} $KRK</div>
|
<div class="balance">Balance: {{ maxStakeAmount.toFixed(2) }} $KRK</div>
|
||||||
<div @click="setMaxAmount" class="staking-amount-max">
|
<div @click="setMaxAmount" class="staking-amount-max">
|
||||||
<b>Max</b>
|
<b>Max</b>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</f-input>
|
</f-input>
|
||||||
<Icon class="stake-arrow" icon="mdi:chevron-triple-right"></Icon>
|
<Icon class="stake-arrow" icon="mdi:chevron-triple-right"></Icon>
|
||||||
<f-input
|
<f-input
|
||||||
label="Owner Slots"
|
label="Owner Slots"
|
||||||
class="staking-amount"
|
class="staking-amount"
|
||||||
disabled
|
disabled
|
||||||
:modelValue="`${stakeSlots}(${supplyFreeze?.toFixed(4)})`"
|
:modelValue="`${stakeSlots}(${supplyFreeze?.toFixed(4)})`"
|
||||||
>
|
>
|
||||||
<template #info>
|
<template #info>
|
||||||
Slots correspond to a percentage of ownership in the protocol.<br /><br />1,000 Slots =
|
Slots correspond to a percentage of ownership in the protocol.<br /><br />1,000 Slots =
|
||||||
1% Ownership<br /><br />When you unstake you get the exact percentage of the current
|
1% Ownership<br /><br />When you unstake you get the exact percentage of the current
|
||||||
$KRK total supply. When the total supply increased since you staked you get more tokens
|
$KRK total supply. When the total supply increased since you staked you get more tokens
|
||||||
back than before.
|
back than before.
|
||||||
</template>
|
</template>
|
||||||
</f-input>
|
</f-input>
|
||||||
<!-- <f-select :items="adjustTaxRate.taxRates" label="Tax" v-model="taxRate">
|
</div>
|
||||||
<template v-slot:info>
|
<div class="row row-2">
|
||||||
The tax you have to pay to keep your staking position open. The tax is
|
<f-select :items="adjustTaxRate.taxRates" label="Tax" v-model="taxRate">
|
||||||
calculated on a yearly basis but paid continuously.
|
<template v-slot:info>
|
||||||
</template>
|
The yearly tax you have to pay to keep your slots open. The tax is paid when unstaking
|
||||||
</f-select> -->
|
or manually in the dashboard. If someone pays a higher tax they can buy you out.
|
||||||
|
</template>
|
||||||
|
</f-select>
|
||||||
|
<f-input label="Floor Tax" disabled :modelValue="snatchSelection.floorTax">
|
||||||
|
<template v-slot:info>
|
||||||
|
This is the current minimum tax you have to pay to claim owner slots from other owners.
|
||||||
|
</template>
|
||||||
|
</f-input>
|
||||||
|
<f-input label="Positions Buyout" disabled :modelValue="snatchSelection.snatchablePositions.length">
|
||||||
|
<template v-slot:info>
|
||||||
|
This shows you the numbers of staking positions you buy out from current owners by
|
||||||
|
paying a higher tax. If you get bought out yourself by new owners you get paid out the
|
||||||
|
current market value of your position incl. your profits.
|
||||||
|
</template>
|
||||||
|
</f-input>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row row-2">
|
<f-button size="large" disabled block v-if="stake.state === 'NoBalance'">Insufficient Balance</f-button>
|
||||||
<f-select :items="adjustTaxRate.taxRates" label="Tax" v-model="taxRate">
|
<f-button size="large" disabled block v-else-if="stake.stakingAmountNumber < minStakeAmount"
|
||||||
<template v-slot:info>
|
>Stake amount too low</f-button
|
||||||
The yearly tax you have to pay to keep your slots open. The tax is paid when unstaking
|
>
|
||||||
or manually in the dashboard. If someone pays a higher tax they can buy you out.
|
<f-button
|
||||||
</template>
|
size="large"
|
||||||
</f-select>
|
disabled
|
||||||
<f-input label="Floor Tax" disabled :modelValue="floorTax">
|
block
|
||||||
<template v-slot:info>
|
v-else-if="
|
||||||
This is the current minimum tax you have to pay to claim owner slots from other owners.
|
!snatchSelection.openPositionsAvailable && stake.state === 'StakeAble' && snatchSelection.snatchablePositions.length === 0
|
||||||
</template>
|
"
|
||||||
</f-input>
|
>taxRate too low to snatch</f-button
|
||||||
<f-input label="Positions Buyout" disabled :modelValue="snatchAblePositions.length">
|
>
|
||||||
<template v-slot:info>
|
<f-button
|
||||||
This shows you the numbers of staking positions you buy out from current owners by
|
size="large"
|
||||||
paying a higher tax. If you get bought out yourself by new owners you get paid out the
|
block
|
||||||
current market value of your position incl. your profits.
|
v-else-if="stake.state === 'StakeAble' && snatchSelection.snatchablePositions.length === 0"
|
||||||
</template>
|
@click="stakeSnatch"
|
||||||
</f-input>
|
>Stake</f-button
|
||||||
</div>
|
>
|
||||||
</div>
|
<f-button
|
||||||
<f-button size="large" disabled block v-if="stake.state === 'NoBalance'">Insufficient Balance</f-button>
|
size="large"
|
||||||
<f-button size="large" disabled block v-else-if="stake.stakingAmountNumber < minStakeAmount"
|
block
|
||||||
>Stake amount too low</f-button
|
v-else-if="stake.state === 'StakeAble' && snatchSelection.snatchablePositions.length > 0"
|
||||||
>
|
@click="stakeSnatch"
|
||||||
<f-button
|
>Snatch and Stake</f-button
|
||||||
size="large"
|
>
|
||||||
disabled
|
<f-button size="large" outlined block v-else-if="stake.state === 'SignTransaction'"
|
||||||
block
|
>Sign Transaction ...</f-button
|
||||||
v-else-if="
|
>
|
||||||
!openPositionsAvailable && stake.state === 'StakeAble' && snatchAblePositions.length === 0
|
<f-button size="large" outlined block v-else-if="stake.state === 'Waiting'">Waiting ...</f-button>
|
||||||
"
|
</template>
|
||||||
>taxRate too low to snatch</f-button
|
</div>
|
||||||
>
|
|
||||||
<f-button
|
|
||||||
size="large"
|
|
||||||
block
|
|
||||||
v-else-if="stake.state === 'StakeAble' && snatchAblePositions.length === 0"
|
|
||||||
@click="stakeSnatch"
|
|
||||||
>Stake</f-button
|
|
||||||
>
|
|
||||||
<f-button
|
|
||||||
size="large"
|
|
||||||
block
|
|
||||||
v-else-if="stake.state === 'StakeAble' && snatchAblePositions.length > 0"
|
|
||||||
@click="stakeSnatch"
|
|
||||||
>Snatch and Stake</f-button
|
|
||||||
>
|
|
||||||
<f-button size="large" outlined block v-else-if="stake.state === 'SignTransaction'"
|
|
||||||
>Sign Transaction ...</f-button
|
|
||||||
>
|
|
||||||
<f-button size="large" outlined block v-else-if="stake.state === 'Waiting'">Waiting ...</f-button>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <template v-if="myActivePositions.length > 0 && status === 'connected'">
|
|
||||||
<h5>Your Active Positions</h5>
|
|
||||||
<collapse-active
|
|
||||||
v-for="position in myActivePositions"
|
|
||||||
:taxRate="position.taxRatePercentage"
|
|
||||||
:amount="position.amount"
|
|
||||||
:treshold="tresholdValue"
|
|
||||||
:id="position.positionId"
|
|
||||||
:position="position"
|
|
||||||
:key="position.id"
|
|
||||||
></collapse-active>
|
|
||||||
</template>
|
|
||||||
<template v-if="myClosedPositions.length > 0 && status === 'connected'">
|
|
||||||
<h5>History</h5>
|
|
||||||
<collapse-history
|
|
||||||
v-for="position in myClosedPositions"
|
|
||||||
:taxRate="position.taxRatePercentage"
|
|
||||||
:taxPaid="formatBigNumber(position.taxPaid, 18)"
|
|
||||||
:amount="position.amount"
|
|
||||||
:treshold="tresholdValue"
|
|
||||||
:id="position.positionId"
|
|
||||||
:key="position.id"
|
|
||||||
:position="position"
|
|
||||||
></collapse-history>
|
|
||||||
</template> -->
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
@ -133,26 +101,12 @@ import FSlider from "@/components/fcomponents/FSlider.vue";
|
||||||
import FOutput from "@/components/fcomponents/FOutput.vue";
|
import FOutput from "@/components/fcomponents/FOutput.vue";
|
||||||
import { Icon } from "@iconify/vue";
|
import { Icon } from "@iconify/vue";
|
||||||
import { formatBigIntDivision, InsertCommaNumber, formatBigNumber, bigInt2Number } from "@/utils/helper";
|
import { formatBigIntDivision, InsertCommaNumber, formatBigNumber, bigInt2Number } from "@/utils/helper";
|
||||||
// import StatsOutput from "@/components/molecules/StatsOutput.vue";
|
|
||||||
// import ChartJs from "@/components/ChartJs.vue";
|
|
||||||
// import CollapseActive from "@/components/collapse/CollapseActive.vue";
|
|
||||||
// import CollapseHistory from "@/components/collapse/CollapseHistory.vue";
|
|
||||||
// import { bytesToUint256, uint256ToBytes } from "harb-lib";
|
|
||||||
// import { getSnatchList } from "harb-lib/dist/";
|
|
||||||
import { formatUnits } from "viem";
|
import { formatUnits } from "viem";
|
||||||
import { loadPositions, usePositions, type Position } from "@/composables/usePositions";
|
import { loadPositions, usePositions } from "@/composables/usePositions";
|
||||||
import {
|
|
||||||
calculateSnatchShortfall,
|
|
||||||
selectSnatchPositions,
|
|
||||||
minimumTaxRate,
|
|
||||||
type SnatchablePosition,
|
|
||||||
} from "kraiken-lib";
|
|
||||||
|
|
||||||
import { useStake } from "@/composables/useStake";
|
import { useStake } from "@/composables/useStake";
|
||||||
import { useClaim } from "@/composables/useClaim";
|
import { useClaim } from "@/composables/useClaim";
|
||||||
import { useAdjustTaxRate } from "@/composables/useAdjustTaxRates";
|
import { useAdjustTaxRate } from "@/composables/useAdjustTaxRates";
|
||||||
|
import { useSnatchSelection } from "@/composables/useSnatchSelection";
|
||||||
import { assetsToShares } from "@/contracts/stake";
|
|
||||||
import { getMinStake } from "@/contracts/harb";
|
import { getMinStake } from "@/contracts/harb";
|
||||||
import { useWallet } from "@/composables/useWallet";
|
import { useWallet } from "@/composables/useWallet";
|
||||||
import { ref, onMounted, watch, computed, inject, watchEffect } from "vue";
|
import { ref, onMounted, watch, computed, inject, watchEffect } from "vue";
|
||||||
|
|
@ -166,9 +120,7 @@ const adjustTaxRate = useAdjustTaxRate();
|
||||||
|
|
||||||
const activeTab = ref("stake");
|
const activeTab = ref("stake");
|
||||||
const StakeMenuOpen = ref(false);
|
const StakeMenuOpen = ref(false);
|
||||||
// let minStakeAmount = ref();
|
|
||||||
const taxRate = ref<number>(1.0);
|
const taxRate = ref<number>(1.0);
|
||||||
// const positions = ref<Array<any>>([]);
|
|
||||||
const loading = ref<boolean>(true);
|
const loading = ref<boolean>(true);
|
||||||
const stakeSnatchLoading = ref<boolean>(false);
|
const stakeSnatchLoading = ref<boolean>(false);
|
||||||
const stake = useStake();
|
const stake = useStake();
|
||||||
|
|
@ -178,11 +130,9 @@ const statCollection = useStatCollection();
|
||||||
|
|
||||||
const { activePositions } = usePositions();
|
const { activePositions } = usePositions();
|
||||||
|
|
||||||
const floorTax = ref(1);
|
|
||||||
const minStake = ref(0n);
|
const minStake = ref(0n);
|
||||||
const stakeSlots = ref();
|
const stakeSlots = ref();
|
||||||
const supplyFreeze = ref<number>(0);
|
const supplyFreeze = ref<number>(0);
|
||||||
const shortfallShares = ref<bigint>(0n);
|
|
||||||
let debounceTimer: ReturnType<typeof setTimeout>;
|
let debounceTimer: ReturnType<typeof setTimeout>;
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
console.log("supplyFreeze");
|
console.log("supplyFreeze");
|
||||||
|
|
@ -204,18 +154,11 @@ watchEffect(() => {
|
||||||
}, 500); // Verzögerung von 500ms
|
}, 500); // Verzögerung von 500ms
|
||||||
});
|
});
|
||||||
|
|
||||||
const openPositionsAvailable = computed(() => shortfallShares.value <= 0n);
|
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
console.log("stakeSlots");
|
console.log("stakeSlots");
|
||||||
stakeSlots.value = (supplyFreeze.value * 1000)?.toFixed(2);
|
stakeSlots.value = (supplyFreeze.value * 1000)?.toFixed(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
// const stakeAbleHarbAmount = computed(() => statCollection.kraikenTotalSupply / 5n);
|
|
||||||
//war das mal so, wurde das geändert --> funktioniert nicht mehr
|
|
||||||
// const minStake = computed(() => stakeAbleHarbAmount.value / 600n);
|
|
||||||
|
|
||||||
|
|
||||||
const tokenIssuance = computed(() => {
|
const tokenIssuance = computed(() => {
|
||||||
if (statCollection.kraikenTotalSupply === 0n) {
|
if (statCollection.kraikenTotalSupply === 0n) {
|
||||||
return 0n;
|
return 0n;
|
||||||
|
|
@ -224,16 +167,11 @@ const tokenIssuance = computed(() => {
|
||||||
return (statCollection.nettoToken7d / statCollection.kraikenTotalSupply) * 100n;
|
return (statCollection.nettoToken7d / statCollection.kraikenTotalSupply) * 100n;
|
||||||
});
|
});
|
||||||
|
|
||||||
function getMinFloorTax() {
|
|
||||||
const minRate = minimumTaxRate(activePositions.value, 0);
|
|
||||||
return Math.round(minRate * 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function stakeSnatch() {
|
async function stakeSnatch() {
|
||||||
if (snatchAblePositions.value.length === 0) {
|
if (snatchSelection.snatchablePositions.value.length === 0) {
|
||||||
await stake.snatch(stake.stakingAmount, taxRate.value);
|
await stake.snatch(stake.stakingAmount, taxRate.value);
|
||||||
} else {
|
} else {
|
||||||
const snatchAblePositionsIds = snatchAblePositions.value.map((p: Position) => p.positionId);
|
const snatchAblePositionsIds = snatchSelection.snatchablePositions.value.map((p: Position) => p.positionId);
|
||||||
await stake.snatch(stake.stakingAmount, taxRate.value, snatchAblePositionsIds);
|
await stake.snatch(stake.stakingAmount, taxRate.value, snatchAblePositionsIds);
|
||||||
}
|
}
|
||||||
stakeSnatchLoading.value = true;
|
stakeSnatchLoading.value = true;
|
||||||
|
|
@ -249,16 +187,7 @@ watch(
|
||||||
console.log("to", to.hash);
|
console.log("to", to.hash);
|
||||||
if (to.hash === "#stake") {
|
if (to.hash === "#stake") {
|
||||||
console.log("StakeMenuOpen", StakeMenuOpen.value);
|
console.log("StakeMenuOpen", StakeMenuOpen.value);
|
||||||
|
|
||||||
StakeMenuOpen.value = true;
|
StakeMenuOpen.value = true;
|
||||||
|
|
||||||
// if(element){
|
|
||||||
// element.scrollIntoView({behavior: "smooth"});
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if(ele){
|
|
||||||
// window.scrollTo(ele.offsetLeft,ele.offsetTop);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ flush: "pre", immediate: true, deep: true }
|
{ flush: "pre", immediate: true, deep: true }
|
||||||
|
|
@ -266,12 +195,6 @@ watch(
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
//on AccountChange
|
|
||||||
//TODO
|
|
||||||
// await getTotalSupply();
|
|
||||||
// await getTotalSupplyHarb();
|
|
||||||
// await getOutstandingSupply();
|
|
||||||
|
|
||||||
minStake.value = await getMinStake();
|
minStake.value = await getMinStake();
|
||||||
stake.stakingAmountNumber = minStakeAmount.value;
|
stake.stakingAmountNumber = minStakeAmount.value;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -280,32 +203,16 @@ onMounted(async () => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// async function getGraphData(): Promise<Array<any>> {
|
|
||||||
// let res = await axios.post("http://127.0.0.1:42069/graphql", {
|
|
||||||
// query: "query MyQuery {\n positions {\n id\n lastTaxTime\n owner\n share\n status\n taxRate\n creationTime\n }\n}",
|
|
||||||
// });
|
|
||||||
// return res.data.data.positions;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const result = useReadContract({
|
|
||||||
// abi: StakeContract.abi,
|
|
||||||
// address: StakeContract.contractAddress,
|
|
||||||
// functionName: "minStake",
|
|
||||||
// args: [],
|
|
||||||
// });
|
|
||||||
// minStakeAmount = result.data;
|
|
||||||
|
|
||||||
const minStakeAmount = computed(() => {
|
const minStakeAmount = computed(() => {
|
||||||
console.log("minStake", minStake.value);
|
console.log("minStake", minStake.value);
|
||||||
|
|
||||||
return bigInt2Number(minStake.value, 18);
|
return bigInt2Number(minStake.value, 18);
|
||||||
});
|
});
|
||||||
|
|
||||||
const maxStakeAmount = computed(() => {
|
const maxStakeAmount = computed(() => {
|
||||||
if (wallet.balance?.value) {
|
if (wallet.balance?.value) {
|
||||||
// return Number(balance?.value?.value / 10n ** BigInt(balance?.value!.decimals));
|
|
||||||
console.log("wallet.balance.value", wallet.balance);
|
console.log("wallet.balance.value", wallet.balance);
|
||||||
console.log("formatBigIntDivision(wallet.balance.value, 10n ** 18n)", bigInt2Number(wallet.balance.value, 18));
|
console.log("formatBigIntDivision(wallet.balance.value, 10n ** 18n)", bigInt2Number(wallet.balance.value, 18));
|
||||||
|
|
||||||
return bigInt2Number(wallet.balance.value, 18);
|
return bigInt2Number(wallet.balance.value, 18);
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -325,128 +232,10 @@ watch(
|
||||||
|
|
||||||
function setMaxAmount() {
|
function setMaxAmount() {
|
||||||
console.log("maxStakeAmount.value", maxStakeAmount.value);
|
console.log("maxStakeAmount.value", maxStakeAmount.value);
|
||||||
|
|
||||||
stake.stakingAmountNumber = maxStakeAmount.value;
|
stake.stakingAmountNumber = maxStakeAmount.value;
|
||||||
}
|
}
|
||||||
const snatchAblePositions = ref<Position[]>([]);
|
|
||||||
let selectionRun = 0;
|
|
||||||
|
|
||||||
watchEffect((onCleanup) => {
|
const snatchSelection = useSnatchSelection(demo);
|
||||||
const runId = ++selectionRun;
|
|
||||||
let cancelled = false;
|
|
||||||
onCleanup(() => {
|
|
||||||
cancelled = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
const compute = async () => {
|
|
||||||
if (statCollection.stakeTotalSupply === 0n) {
|
|
||||||
shortfallShares.value = 0n;
|
|
||||||
if (!cancelled && runId === selectionRun) {
|
|
||||||
snatchAblePositions.value = [];
|
|
||||||
floorTax.value = getMinFloorTax();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const stakingShares = stake.stakingAmountShares ?? 0n;
|
|
||||||
const shortfall = calculateSnatchShortfall(
|
|
||||||
statCollection.outstandingStake,
|
|
||||||
stakingShares,
|
|
||||||
statCollection.stakeTotalSupply,
|
|
||||||
2n,
|
|
||||||
10n
|
|
||||||
);
|
|
||||||
|
|
||||||
shortfallShares.value = shortfall;
|
|
||||||
|
|
||||||
if (shortfall <= 0n) {
|
|
||||||
if (!cancelled && runId === selectionRun) {
|
|
||||||
snatchAblePositions.value = [];
|
|
||||||
floorTax.value = getMinFloorTax();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxTaxRateDecimal = (taxRate.value ?? 0) / 100;
|
|
||||||
const includeOwned = demo;
|
|
||||||
const recipient = wallet.account.address ?? null;
|
|
||||||
|
|
||||||
const eligiblePositions = activePositions.value.filter((position: Position) => {
|
|
||||||
if (position.taxRate >= maxTaxRateDecimal) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!includeOwned && position.iAmOwner) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (eligiblePositions.length === 0) {
|
|
||||||
if (!cancelled && runId === selectionRun) {
|
|
||||||
snatchAblePositions.value = [];
|
|
||||||
floorTax.value = getMinFloorTax();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const candidates: SnatchablePosition[] = [];
|
|
||||||
|
|
||||||
for (const position of eligiblePositions) {
|
|
||||||
try {
|
|
||||||
const shares = await assetsToShares(position.harbDeposit);
|
|
||||||
candidates.push({
|
|
||||||
id: position.positionId,
|
|
||||||
owner: position.owner,
|
|
||||||
stakeShares: shares,
|
|
||||||
taxRate: position.taxRate,
|
|
||||||
taxRateIndex: position.taxRateIndex,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Unable to compute stake shares for position", position.id, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const selection = selectSnatchPositions(candidates, {
|
|
||||||
shortfallShares: shortfall,
|
|
||||||
maxTaxRate: maxTaxRateDecimal,
|
|
||||||
includeOwned,
|
|
||||||
recipientAddress: recipient,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (cancelled || runId !== selectionRun) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selection.remainingShortfall > 0n) {
|
|
||||||
snatchAblePositions.value = [];
|
|
||||||
floorTax.value = getMinFloorTax();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const positionById = new Map<bigint, Position>();
|
|
||||||
for (const position of activePositions.value) {
|
|
||||||
positionById.set(position.positionId, position);
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedPositions = selection.selected
|
|
||||||
.map((candidate) => positionById.get(candidate.id))
|
|
||||||
.filter((value): value is Position => Boolean(value));
|
|
||||||
|
|
||||||
snatchAblePositions.value = selectedPositions;
|
|
||||||
|
|
||||||
if (selection.maxSelectedTaxRateIndex !== undefined) {
|
|
||||||
const nextIndex = selection.maxSelectedTaxRateIndex + 1;
|
|
||||||
const option =
|
|
||||||
adjustTaxRate.taxRates[nextIndex] ??
|
|
||||||
adjustTaxRate.taxRates[selection.maxSelectedTaxRateIndex];
|
|
||||||
floorTax.value = option ? option.year : getMinFloorTax();
|
|
||||||
} else {
|
|
||||||
floorTax.value = getMinFloorTax();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
compute();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="sass">
|
<style lang="sass">
|
||||||
|
|
@ -482,11 +271,7 @@ watchEffect((onCleanup) => {
|
||||||
justify-content: space-between
|
justify-content: space-between
|
||||||
>*
|
>*
|
||||||
flex: 0 0 30%
|
flex: 0 0 30%
|
||||||
// >:nth-child(2)
|
|
||||||
// flex: 0 0 22%
|
|
||||||
// >:nth-child(3)
|
|
||||||
// flex: 0 1 28%
|
|
||||||
.stake-arrow
|
.stake-arrow
|
||||||
align-self: center
|
align-self: center
|
||||||
font-size: 30px
|
font-size: 30px
|
||||||
</style>
|
</style>
|
||||||
274
web-app/src/composables/__tests__/useSnatchSelection.spec.ts
Normal file
274
web-app/src/composables/__tests__/useSnatchSelection.spec.ts
Normal file
|
|
@ -0,0 +1,274 @@
|
||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||||
|
import { ref, nextTick } from 'vue'
|
||||||
|
import { useSnatchSelection } from '../useSnatchSelection'
|
||||||
|
import { usePositions } from '../usePositions'
|
||||||
|
import { useStake } from '../useStake'
|
||||||
|
import { useWallet } from '../useWallet'
|
||||||
|
import { useStatCollection } from '../useStatCollection'
|
||||||
|
import { useAdjustTaxRate } from '../useAdjustTaxRates'
|
||||||
|
|
||||||
|
// Mock all composables
|
||||||
|
vi.mock('../usePositions', () => ({
|
||||||
|
usePositions: vi.fn()
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('../useStake', () => ({
|
||||||
|
useStake: vi.fn()
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('../useWallet', () => ({
|
||||||
|
useWallet: vi.fn()
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('../useStatCollection', () => ({
|
||||||
|
useStatCollection: vi.fn()
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('../useAdjustTaxRates', () => ({
|
||||||
|
useAdjustTaxRate: vi.fn()
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('kraiken-lib', () => ({
|
||||||
|
calculateSnatchShortfall: vi.fn((outstandingStake, stakingShares, stakeTotalSupply) => {
|
||||||
|
return stakingShares > outstandingStake ? 0n : outstandingStake - stakingShares
|
||||||
|
}),
|
||||||
|
selectSnatchPositions: vi.fn((candidates, options) => {
|
||||||
|
if (candidates.length === 0) {
|
||||||
|
return { selected: [], remainingShortfall: options.shortfallShares }
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
selected: candidates,
|
||||||
|
remainingShortfall: 0n,
|
||||||
|
maxSelectedTaxRateIndex: candidates[candidates.length - 1].taxRateIndex
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
minimumTaxRate: vi.fn(() => 0.01)
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe('useSnatchSelection', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Reset all mocks
|
||||||
|
vi.clearAllMocks()
|
||||||
|
|
||||||
|
// Setup default mock values with proper refs
|
||||||
|
vi.mocked(usePositions).mockReturnValue({
|
||||||
|
activePositions: ref([])
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
vi.mocked(useStake).mockReturnValue({
|
||||||
|
stakingAmountShares: 0n,
|
||||||
|
taxRate: 1.0
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
vi.mocked(useWallet).mockReturnValue({
|
||||||
|
account: { address: '0x123' }
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
// Mock with realistic values for local computation
|
||||||
|
// stakeTotalSupply is typically 10^(18+7) = 10^25 from contract
|
||||||
|
// kraikenTotalSupply is dynamic but starts around 10^25
|
||||||
|
vi.mocked(useStatCollection).mockReturnValue({
|
||||||
|
stakeTotalSupply: 10000000000000000000000000n, // 10^25
|
||||||
|
kraikenTotalSupply: 10000000000000000000000000n, // 10^25
|
||||||
|
outstandingStake: 500n
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
vi.mocked(useAdjustTaxRate).mockReturnValue({
|
||||||
|
taxRates: [{ year: 1 }]
|
||||||
|
} as any)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should initialize with empty snatchable positions', () => {
|
||||||
|
const { snatchablePositions } = useSnatchSelection()
|
||||||
|
expect(snatchablePositions.value).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle no active positions', () => {
|
||||||
|
const { snatchablePositions, floorTax } = useSnatchSelection()
|
||||||
|
expect(snatchablePositions.value).toEqual([])
|
||||||
|
expect(floorTax.value).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle no shortfall', () => {
|
||||||
|
vi.mocked(useStatCollection).mockReturnValue({
|
||||||
|
stakeTotalSupply: 1000n,
|
||||||
|
outstandingStake: 100n
|
||||||
|
})
|
||||||
|
|
||||||
|
vi.mocked(useStake).mockReturnValue({
|
||||||
|
stakingAmountShares: 900n,
|
||||||
|
taxRate: 1.0
|
||||||
|
})
|
||||||
|
|
||||||
|
const { snatchablePositions, openPositionsAvailable } = useSnatchSelection()
|
||||||
|
expect(snatchablePositions.value).toEqual([])
|
||||||
|
expect(openPositionsAvailable.value).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should filter out positions with higher tax rate', async () => {
|
||||||
|
vi.mocked(usePositions).mockReturnValue({
|
||||||
|
activePositions: ref([
|
||||||
|
{
|
||||||
|
positionId: 1n,
|
||||||
|
owner: '0x456',
|
||||||
|
harbDeposit: 100n,
|
||||||
|
taxRate: 2.0,
|
||||||
|
taxRateIndex: 1,
|
||||||
|
iAmOwner: false
|
||||||
|
}
|
||||||
|
])
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
vi.mocked(useStake).mockReturnValue({
|
||||||
|
stakingAmountShares: 100n,
|
||||||
|
taxRate: 1.0
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const { snatchablePositions } = useSnatchSelection()
|
||||||
|
|
||||||
|
// Wait for watchEffect to run
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 0))
|
||||||
|
|
||||||
|
expect(snatchablePositions.value).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should filter out owned positions by default', async () => {
|
||||||
|
vi.mocked(usePositions).mockReturnValue({
|
||||||
|
activePositions: ref([
|
||||||
|
{
|
||||||
|
positionId: 1n,
|
||||||
|
owner: '0x123',
|
||||||
|
harbDeposit: 100n,
|
||||||
|
taxRate: 0.5,
|
||||||
|
taxRateIndex: 1,
|
||||||
|
iAmOwner: true
|
||||||
|
}
|
||||||
|
])
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const { snatchablePositions } = useSnatchSelection()
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 0))
|
||||||
|
expect(snatchablePositions.value).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should include owned positions when demo mode is enabled', async () => {
|
||||||
|
const position = {
|
||||||
|
positionId: 1n,
|
||||||
|
owner: '0x123',
|
||||||
|
harbDeposit: 100n,
|
||||||
|
taxRate: 0.005, // 0.5% tax rate (less than maxTaxRate)
|
||||||
|
taxRateIndex: 1,
|
||||||
|
iAmOwner: true
|
||||||
|
}
|
||||||
|
|
||||||
|
vi.mocked(usePositions).mockReturnValue({
|
||||||
|
activePositions: ref([position])
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
vi.mocked(useStake).mockReturnValue({
|
||||||
|
stakingAmountShares: 100n,
|
||||||
|
taxRate: 1.0 // Will be converted to 0.01 (1%) decimal
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
// Need outstandingStake > stakingAmountShares to create shortfall
|
||||||
|
vi.mocked(useStatCollection).mockReturnValue({
|
||||||
|
stakeTotalSupply: 10000000000000000000000000n,
|
||||||
|
kraikenTotalSupply: 10000000000000000000000000n,
|
||||||
|
outstandingStake: 500n
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const { snatchablePositions } = useSnatchSelection(true)
|
||||||
|
|
||||||
|
// Wait for watchEffect to run (no longer async)
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
expect(snatchablePositions.value).toContainEqual(position)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle partial fills', async () => {
|
||||||
|
const position1 = {
|
||||||
|
positionId: 1n,
|
||||||
|
owner: '0x456',
|
||||||
|
harbDeposit: 100n,
|
||||||
|
taxRate: 0.005, // 0.5% tax rate
|
||||||
|
taxRateIndex: 1,
|
||||||
|
iAmOwner: false
|
||||||
|
}
|
||||||
|
|
||||||
|
const position2 = {
|
||||||
|
positionId: 2n,
|
||||||
|
owner: '0x789',
|
||||||
|
harbDeposit: 200n,
|
||||||
|
taxRate: 0.006, // 0.6% tax rate
|
||||||
|
taxRateIndex: 2,
|
||||||
|
iAmOwner: false
|
||||||
|
}
|
||||||
|
|
||||||
|
vi.mocked(usePositions).mockReturnValue({
|
||||||
|
activePositions: ref([position1, position2])
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
vi.mocked(useStake).mockReturnValue({
|
||||||
|
stakingAmountShares: 150n,
|
||||||
|
taxRate: 1.0 // Will be converted to 0.01 (1%) decimal
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
// Need outstandingStake > stakingAmountShares to create shortfall
|
||||||
|
vi.mocked(useStatCollection).mockReturnValue({
|
||||||
|
stakeTotalSupply: 10000000000000000000000000n,
|
||||||
|
kraikenTotalSupply: 10000000000000000000000000n,
|
||||||
|
outstandingStake: 500n
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const { snatchablePositions } = useSnatchSelection()
|
||||||
|
|
||||||
|
// Wait for watchEffect to run
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
expect(snatchablePositions.value).toEqual([position1, position2])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should update floor tax based on selected positions', async () => {
|
||||||
|
const position = {
|
||||||
|
positionId: 1n,
|
||||||
|
owner: '0x456',
|
||||||
|
harbDeposit: 100n,
|
||||||
|
taxRate: 0.005, // 0.5% tax rate
|
||||||
|
taxRateIndex: 1,
|
||||||
|
iAmOwner: false
|
||||||
|
}
|
||||||
|
|
||||||
|
vi.mocked(usePositions).mockReturnValue({
|
||||||
|
activePositions: ref([position])
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
vi.mocked(useStake).mockReturnValue({
|
||||||
|
stakingAmountShares: 100n,
|
||||||
|
taxRate: 1.0 // Will be converted to 0.01 (1%) decimal
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
// Need outstandingStake > stakingAmountShares to create shortfall
|
||||||
|
vi.mocked(useStatCollection).mockReturnValue({
|
||||||
|
stakeTotalSupply: 10000000000000000000000000n,
|
||||||
|
kraikenTotalSupply: 10000000000000000000000000n,
|
||||||
|
outstandingStake: 500n
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
vi.mocked(useAdjustTaxRate).mockReturnValue({
|
||||||
|
taxRates: [
|
||||||
|
{ year: 1 },
|
||||||
|
{ year: 2 },
|
||||||
|
{ year: 3 }
|
||||||
|
]
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const { floorTax } = useSnatchSelection()
|
||||||
|
|
||||||
|
// Wait for watchEffect to run
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
// Floor tax should be taxRates[maxSelectedTaxRateIndex + 1]
|
||||||
|
// Position has taxRateIndex: 1, so nextIndex = 2, taxRates[2] = { year: 3 }
|
||||||
|
expect(floorTax.value).toBe(3)
|
||||||
|
})
|
||||||
|
})
|
||||||
180
web-app/src/composables/useSnatchSelection.ts
Normal file
180
web-app/src/composables/useSnatchSelection.ts
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
import { ref, watchEffect, computed } from 'vue'
|
||||||
|
import { usePositions, type Position } from './usePositions'
|
||||||
|
import { useStake } from './useStake'
|
||||||
|
import { useWallet } from './useWallet'
|
||||||
|
import { useStatCollection } from './useStatCollection'
|
||||||
|
import { useAdjustTaxRate } from './useAdjustTaxRates'
|
||||||
|
import {
|
||||||
|
calculateSnatchShortfall,
|
||||||
|
selectSnatchPositions,
|
||||||
|
minimumTaxRate,
|
||||||
|
type SnatchablePosition,
|
||||||
|
} from 'kraiken-lib'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts Kraiken token assets to shares using the same formula as Stake.sol:
|
||||||
|
* shares = (assets * stakeTotalSupply) / kraikenTotalSupply
|
||||||
|
*
|
||||||
|
* @param assets - Amount of Kraiken tokens
|
||||||
|
* @param kraikenTotalSupply - Total supply of Kraiken tokens
|
||||||
|
* @param stakeTotalSupply - Total supply of stake shares (constant from contract)
|
||||||
|
* @returns Number of shares corresponding to the assets
|
||||||
|
*/
|
||||||
|
function assetsToSharesLocal(
|
||||||
|
assets: bigint,
|
||||||
|
kraikenTotalSupply: bigint,
|
||||||
|
stakeTotalSupply: bigint
|
||||||
|
): bigint {
|
||||||
|
if (kraikenTotalSupply === 0n) {
|
||||||
|
return 0n
|
||||||
|
}
|
||||||
|
// Equivalent to: assets.mulDiv(stakeTotalSupply, kraikenTotalSupply, Math.Rounding.Down)
|
||||||
|
return (assets * stakeTotalSupply) / kraikenTotalSupply
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function useSnatchSelection(demo = false) {
|
||||||
|
const { activePositions } = usePositions()
|
||||||
|
const stake = useStake()
|
||||||
|
const wallet = useWallet()
|
||||||
|
const statCollection = useStatCollection()
|
||||||
|
const adjustTaxRate = useAdjustTaxRate()
|
||||||
|
|
||||||
|
const snatchablePositions = ref<Position[]>([])
|
||||||
|
const shortfallShares = ref<bigint>(0n)
|
||||||
|
const floorTax = ref(1)
|
||||||
|
let selectionRun = 0
|
||||||
|
|
||||||
|
const openPositionsAvailable = computed(() => shortfallShares.value <= 0n)
|
||||||
|
|
||||||
|
function getMinFloorTax() {
|
||||||
|
const minRate = minimumTaxRate(activePositions.value, 0)
|
||||||
|
return Math.round(minRate * 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
watchEffect((onCleanup) => {
|
||||||
|
const runId = ++selectionRun
|
||||||
|
let cancelled = false
|
||||||
|
onCleanup(() => {
|
||||||
|
cancelled = true
|
||||||
|
})
|
||||||
|
|
||||||
|
// No longer async since we compute shares locally
|
||||||
|
const compute = () => {
|
||||||
|
if (statCollection.stakeTotalSupply === 0n) {
|
||||||
|
shortfallShares.value = 0n
|
||||||
|
if (!cancelled && runId === selectionRun) {
|
||||||
|
snatchablePositions.value = []
|
||||||
|
floorTax.value = getMinFloorTax()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const stakingShares = stake.stakingAmountShares ?? 0n
|
||||||
|
const shortfall = calculateSnatchShortfall(
|
||||||
|
statCollection.outstandingStake,
|
||||||
|
stakingShares,
|
||||||
|
statCollection.stakeTotalSupply,
|
||||||
|
2n,
|
||||||
|
10n
|
||||||
|
)
|
||||||
|
|
||||||
|
shortfallShares.value = shortfall
|
||||||
|
|
||||||
|
if (shortfall <= 0n) {
|
||||||
|
if (!cancelled && runId === selectionRun) {
|
||||||
|
snatchablePositions.value = []
|
||||||
|
floorTax.value = getMinFloorTax()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxTaxRateDecimal = (stake.taxRate ?? 0) / 100
|
||||||
|
const includeOwned = demo
|
||||||
|
const recipient = wallet.account.address ?? null
|
||||||
|
|
||||||
|
const eligiblePositions = activePositions.value.filter((position: Position) => {
|
||||||
|
if (position.taxRate >= maxTaxRateDecimal) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!includeOwned && position.iAmOwner) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if (eligiblePositions.length === 0) {
|
||||||
|
if (!cancelled && runId === selectionRun) {
|
||||||
|
snatchablePositions.value = []
|
||||||
|
floorTax.value = getMinFloorTax()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidates: SnatchablePosition[] = []
|
||||||
|
|
||||||
|
// Compute shares locally using the same formula as Stake.sol
|
||||||
|
for (const position of eligiblePositions) {
|
||||||
|
const shares = assetsToSharesLocal(
|
||||||
|
position.harbDeposit,
|
||||||
|
statCollection.kraikenTotalSupply,
|
||||||
|
statCollection.stakeTotalSupply
|
||||||
|
)
|
||||||
|
candidates.push({
|
||||||
|
id: position.positionId,
|
||||||
|
owner: position.owner,
|
||||||
|
stakeShares: shares,
|
||||||
|
taxRate: position.taxRate,
|
||||||
|
taxRateIndex: position.taxRateIndex,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const selection = selectSnatchPositions(candidates, {
|
||||||
|
shortfallShares: shortfall,
|
||||||
|
maxTaxRate: maxTaxRateDecimal,
|
||||||
|
includeOwned,
|
||||||
|
recipientAddress: recipient,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (cancelled || runId !== selectionRun) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selection.remainingShortfall > 0n) {
|
||||||
|
snatchablePositions.value = []
|
||||||
|
floorTax.value = getMinFloorTax()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const positionById = new Map<bigint, Position>()
|
||||||
|
for (const position of activePositions.value) {
|
||||||
|
positionById.set(position.positionId, position)
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedPositions = selection.selected
|
||||||
|
.map((candidate) => positionById.get(candidate.id))
|
||||||
|
.filter((value): value is Position => Boolean(value))
|
||||||
|
|
||||||
|
snatchablePositions.value = selectedPositions
|
||||||
|
|
||||||
|
if (selection.maxSelectedTaxRateIndex !== undefined) {
|
||||||
|
const nextIndex = selection.maxSelectedTaxRateIndex + 1
|
||||||
|
const option =
|
||||||
|
adjustTaxRate.taxRates[nextIndex] ??
|
||||||
|
adjustTaxRate.taxRates[selection.maxSelectedTaxRateIndex]
|
||||||
|
floorTax.value = option ? option.year : getMinFloorTax()
|
||||||
|
} else {
|
||||||
|
floorTax.value = getMinFloorTax()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compute()
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
snatchablePositions,
|
||||||
|
shortfallShares,
|
||||||
|
floorTax,
|
||||||
|
openPositionsAvailable,
|
||||||
|
}
|
||||||
|
}
|
||||||
16
web-app/vitest.config.ts
Normal file
16
web-app/vitest.config.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { defineConfig } from 'vitest/config'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
test: {
|
||||||
|
environment: 'jsdom',
|
||||||
|
globals: true,
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
Loading…
Add table
Add a link
Reference in a new issue