diff --git a/contracts/bootstrap/package.json b/contracts/bootstrap/package.json index 6ddc61bb..95eb8cf9 100644 --- a/contracts/bootstrap/package.json +++ b/contracts/bootstrap/package.json @@ -1,6 +1,6 @@ { "name": "bootstrap", - "version": "0.8.6", + "version": "0.8.7", "description": "", "main": "index.ts", "scripts": { diff --git a/contracts/package.json b/contracts/package.json index 6dbfe6a3..0c7e1081 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -1,6 +1,6 @@ { "name": "reti-contracts", - "version": "0.8.6", + "version": "0.8.7", "license": "MIT", "scripts": { "generate-client": "algokit generate client contracts/artifacts/ --language typescript --output contracts/clients/{contract_name}Client.ts && ./update_contract_artifacts.sh``", @@ -24,8 +24,8 @@ "@algorandfoundation/tealscript": "=0.92.0", "@jest/globals": "^29.7.0", "@joe-p/algokit-generate-component": "^0.2.1", - "@typescript-eslint/eslint-plugin": "7.8.0", - "@typescript-eslint/parser": "7.8.0", + "@typescript-eslint/eslint-plugin": "7.9.0", + "@typescript-eslint/parser": "7.9.0", "eslint": "8.57.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-typescript": "^18.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6afe331a..c0b026ec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,11 +30,11 @@ importers: specifier: ^0.2.1 version: 0.2.1 '@typescript-eslint/eslint-plugin': - specifier: 7.8.0 - version: 7.8.0(@typescript-eslint/parser@7.8.0)(eslint@8.57.0)(typescript@5.4.5) + specifier: 7.9.0 + version: 7.9.0(@typescript-eslint/parser@7.9.0)(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/parser': - specifier: 7.8.0 - version: 7.8.0(eslint@8.57.0)(typescript@5.4.5) + specifier: 7.9.0 + version: 7.9.0(eslint@8.57.0)(typescript@5.4.5) eslint: specifier: 8.57.0 version: 8.57.0 @@ -43,13 +43,13 @@ importers: version: 15.0.0(eslint-plugin-import@2.29.1)(eslint@8.57.0) eslint-config-airbnb-typescript: specifier: ^18.0.0 - version: 18.0.0(@typescript-eslint/eslint-plugin@7.8.0)(@typescript-eslint/parser@7.8.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + version: 18.0.0(@typescript-eslint/eslint-plugin@7.9.0)(@typescript-eslint/parser@7.9.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0) eslint-config-prettier: specifier: ^9.1.0 version: 9.1.0(eslint@8.57.0) eslint-plugin-import: specifier: ^2.29.1 - version: 2.29.1(@typescript-eslint/parser@7.8.0)(eslint@8.57.0) + version: 2.29.1(@typescript-eslint/parser@7.9.0)(eslint@8.57.0) eslint-plugin-prettier: specifier: ^5.1.3 version: 5.1.3(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.2.5) @@ -86,7 +86,7 @@ importers: version: 18.2.4 '@types/node': specifier: ^20.12.2 - version: 20.12.11 + version: 20.12.12 '@types/prompts': specifier: ^2.4.9 version: 2.4.9 @@ -276,7 +276,7 @@ importers: version: 6.2.2 '@types/node': specifier: 20.12.11 - version: 20.12.11 + version: 20.12.12 '@types/react': specifier: 18.3.1 version: 18.3.1 @@ -327,16 +327,16 @@ importers: version: 3.4.3(ts-node@10.9.2) ts-node: specifier: 10.9.2 - version: 10.9.2(@types/node@20.12.11)(typescript@5.4.5) + version: 10.9.2(@types/node@20.12.12)(typescript@5.4.5) typescript: specifier: 5.4.5 version: 5.4.5 vite: specifier: 5.2.11 - version: 5.2.11(@types/node@20.12.11) + version: 5.2.11(@types/node@20.12.12) vitest: specifier: 1.6.0 - version: 1.6.0(@types/node@20.12.11)(jsdom@24.0.0) + version: 1.6.0(@types/node@20.12.12)(jsdom@24.0.0) packages: @@ -1227,7 +1227,7 @@ packages: '@inquirer/figures': 1.0.1 '@inquirer/type': 1.3.1 '@types/mute-stream': 0.0.4 - '@types/node': 20.12.11 + '@types/node': 20.12.12 '@types/wrap-ansi': 3.0.0 ansi-escapes: 4.3.2 chalk: 4.1.2 @@ -1281,7 +1281,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.12.11 + '@types/node': 20.12.12 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -1302,14 +1302,14 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.11 + '@types/node': 20.12.12 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.12.11) + jest-config: 29.7.0(@types/node@20.12.12) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -1337,7 +1337,7 @@ packages: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.11 + '@types/node': 20.12.12 jest-mock: 29.7.0 dev: true @@ -1364,7 +1364,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.12.11 + '@types/node': 20.12.12 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -1397,7 +1397,7 @@ packages: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 20.12.11 + '@types/node': 20.12.12 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -1485,7 +1485,7 @@ packages: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.12.11 + '@types/node': 20.12.12 '@types/yargs': 17.0.32 chalk: 4.1.2 dev: true @@ -3217,7 +3217,7 @@ packages: dom-accessibility-api: 0.6.3 lodash: 4.17.21 redent: 3.0.0 - vitest: 1.6.0(@types/node@20.12.11)(jsdom@24.0.0) + vitest: 1.6.0(@types/node@20.12.12)(jsdom@24.0.0) dev: true /@testing-library/react@15.0.7(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1): @@ -3433,7 +3433,7 @@ packages: /@types/graceful-fs@4.1.9: resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} dependencies: - '@types/node': 20.12.11 + '@types/node': 20.12.12 dev: true /@types/istanbul-lib-coverage@2.0.6: @@ -3463,18 +3463,18 @@ packages: /@types/mute-stream@0.0.4: resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==} dependencies: - '@types/node': 20.12.11 + '@types/node': 20.12.12 dev: true - /@types/node@20.12.11: - resolution: {integrity: sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==} + /@types/node@20.12.12: + resolution: {integrity: sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==} dependencies: undici-types: 5.26.5 /@types/prompts@2.4.9: resolution: {integrity: sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==} dependencies: - '@types/node': 20.12.11 + '@types/node': 20.12.12 kleur: 3.0.3 dev: true @@ -3551,6 +3551,33 @@ packages: - supports-color dev: true + /@typescript-eslint/eslint-plugin@7.9.0(@typescript-eslint/parser@7.9.0)(eslint@8.57.0)(typescript@5.4.5): + resolution: {integrity: sha512-6e+X0X3sFe/G/54aC3jt0txuMTURqLyekmEHViqyA2VnxhLMpvA6nqmcjIy+Cr9tLDHPssA74BP5Mx9HQIxBEA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + '@typescript-eslint/parser': ^7.0.0 + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@eslint-community/regexpp': 4.10.0 + '@typescript-eslint/parser': 7.9.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/scope-manager': 7.9.0 + '@typescript-eslint/type-utils': 7.9.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/utils': 7.9.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/visitor-keys': 7.9.0 + eslint: 8.57.0 + graphemer: 1.4.0 + ignore: 5.3.1 + natural-compare: 1.4.0 + ts-api-utils: 1.3.0(typescript@5.4.5) + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/parser@7.8.0(eslint@8.57.0)(typescript@5.4.5): resolution: {integrity: sha512-KgKQly1pv0l4ltcftP59uQZCi4HUYswCLbTqVZEJu7uLX8CTLyswqMLqLN+2QFz4jCptqWVV4SB7vdxcH2+0kQ==} engines: {node: ^18.18.0 || >=20.0.0} @@ -3572,6 +3599,27 @@ packages: - supports-color dev: true + /@typescript-eslint/parser@7.9.0(eslint@8.57.0)(typescript@5.4.5): + resolution: {integrity: sha512-qHMJfkL5qvgQB2aLvhUSXxbK7OLnDkwPzFalg458pxQgfxKDfT1ZDbHQM/I6mDIf/svlMkj21kzKuQ2ixJlatQ==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 7.9.0 + '@typescript-eslint/types': 7.9.0 + '@typescript-eslint/typescript-estree': 7.9.0(typescript@5.4.5) + '@typescript-eslint/visitor-keys': 7.9.0 + debug: 4.3.4 + eslint: 8.57.0 + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/scope-manager@7.8.0: resolution: {integrity: sha512-viEmZ1LmwsGcnr85gIq+FCYI7nO90DVbE37/ll51hjv9aG+YZMb4WDE2fyWpUR4O/UrhGRpYXK/XajcGTk2B8g==} engines: {node: ^18.18.0 || >=20.0.0} @@ -3580,6 +3628,14 @@ packages: '@typescript-eslint/visitor-keys': 7.8.0 dev: true + /@typescript-eslint/scope-manager@7.9.0: + resolution: {integrity: sha512-ZwPK4DeCDxr3GJltRz5iZejPFAAr4Wk3+2WIBaj1L5PYK5RgxExu/Y68FFVclN0y6GGwH8q+KgKRCvaTmFBbgQ==} + engines: {node: ^18.18.0 || >=20.0.0} + dependencies: + '@typescript-eslint/types': 7.9.0 + '@typescript-eslint/visitor-keys': 7.9.0 + dev: true + /@typescript-eslint/type-utils@7.8.0(eslint@8.57.0)(typescript@5.4.5): resolution: {integrity: sha512-H70R3AefQDQpz9mGv13Uhi121FNMh+WEaRqcXTX09YEDky21km4dV1ZXJIp8QjXc4ZaVkXVdohvWDzbnbHDS+A==} engines: {node: ^18.18.0 || >=20.0.0} @@ -3600,11 +3656,36 @@ packages: - supports-color dev: true + /@typescript-eslint/type-utils@7.9.0(eslint@8.57.0)(typescript@5.4.5): + resolution: {integrity: sha512-6Qy8dfut0PFrFRAZsGzuLoM4hre4gjzWJB6sUvdunCYZsYemTkzZNwF1rnGea326PHPT3zn5Lmg32M/xfJfByA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 7.9.0(typescript@5.4.5) + '@typescript-eslint/utils': 7.9.0(eslint@8.57.0)(typescript@5.4.5) + debug: 4.3.4 + eslint: 8.57.0 + ts-api-utils: 1.3.0(typescript@5.4.5) + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/types@7.8.0: resolution: {integrity: sha512-wf0peJ+ZGlcH+2ZS23aJbOv+ztjeeP8uQ9GgwMJGVLx/Nj9CJt17GWgWWoSmoRVKAX2X+7fzEnAjxdvK2gqCLw==} engines: {node: ^18.18.0 || >=20.0.0} dev: true + /@typescript-eslint/types@7.9.0: + resolution: {integrity: sha512-oZQD9HEWQanl9UfsbGVcZ2cGaR0YT5476xfWE0oE5kQa2sNK2frxOlkeacLOTh9po4AlUT5rtkGyYM5kew0z5w==} + engines: {node: ^18.18.0 || >=20.0.0} + dev: true + /@typescript-eslint/typescript-estree@7.8.0(typescript@5.4.5): resolution: {integrity: sha512-5pfUCOwK5yjPaJQNy44prjCwtr981dO8Qo9J9PwYXZ0MosgAbfEMB008dJ5sNo3+/BN6ytBPuSvXUg9SAqB0dg==} engines: {node: ^18.18.0 || >=20.0.0} @@ -3627,6 +3708,28 @@ packages: - supports-color dev: true + /@typescript-eslint/typescript-estree@7.9.0(typescript@5.4.5): + resolution: {integrity: sha512-zBCMCkrb2YjpKV3LA0ZJubtKCDxLttxfdGmwZvTqqWevUPN0FZvSI26FalGFFUZU/9YQK/A4xcQF9o/VVaCKAg==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 7.9.0 + '@typescript-eslint/visitor-keys': 7.9.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.4 + semver: 7.6.0 + ts-api-utils: 1.3.0(typescript@5.4.5) + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/utils@7.8.0(eslint@8.57.0)(typescript@5.4.5): resolution: {integrity: sha512-L0yFqOCflVqXxiZyXrDr80lnahQfSOfc9ELAAZ75sqicqp2i36kEZZGuUymHNFoYOqxRT05up760b4iGsl02nQ==} engines: {node: ^18.18.0 || >=20.0.0} @@ -3646,6 +3749,22 @@ packages: - typescript dev: true + /@typescript-eslint/utils@7.9.0(eslint@8.57.0)(typescript@5.4.5): + resolution: {integrity: sha512-5KVRQCzZajmT4Ep+NEgjXCvjuypVvYHUW7RHlXzNPuak2oWpVoD1jf5xCP0dPAuNIchjC7uQyvbdaSTFaLqSdA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@typescript-eslint/scope-manager': 7.9.0 + '@typescript-eslint/types': 7.9.0 + '@typescript-eslint/typescript-estree': 7.9.0(typescript@5.4.5) + eslint: 8.57.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /@typescript-eslint/visitor-keys@7.8.0: resolution: {integrity: sha512-q4/gibTNBQNA0lGyYQCmWRS5D15n8rXh4QjK3KV+MBPlTYHpfBUT3D3PaPR/HeNiI9W6R7FvlkcGhNyAoP+caA==} engines: {node: ^18.18.0 || >=20.0.0} @@ -3654,6 +3773,14 @@ packages: eslint-visitor-keys: 3.4.3 dev: true + /@typescript-eslint/visitor-keys@7.9.0: + resolution: {integrity: sha512-iESPx2TNLDNGQLyjKhUvIKprlP49XNEK+MvIf9nIO7ZZaZdbnfWKHnXAgufpxqfA0YryH8XToi4+CjBgVnFTSQ==} + engines: {node: ^18.18.0 || >=20.0.0} + dependencies: + '@typescript-eslint/types': 7.9.0 + eslint-visitor-keys: 3.4.3 + dev: true + /@ungap/structured-clone@1.2.0: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} dev: true @@ -3669,7 +3796,7 @@ packages: '@babel/plugin-transform-react-jsx-source': 7.24.1(@babel/core@7.24.5) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.2.11(@types/node@20.12.11) + vite: 5.2.11(@types/node@20.12.12) transitivePeerDependencies: - supports-color dev: true @@ -3692,7 +3819,7 @@ packages: std-env: 3.7.0 strip-literal: 2.1.0 test-exclude: 6.0.0 - vitest: 1.6.0(@types/node@20.12.11)(jsdom@24.0.0) + vitest: 1.6.0(@types/node@20.12.12)(jsdom@24.0.0) transitivePeerDependencies: - supports-color dev: true @@ -5054,7 +5181,7 @@ packages: chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.12.11) + jest-config: 29.7.0(@types/node@20.12.12) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -5660,21 +5787,21 @@ packages: dependencies: confusing-browser-globals: 1.0.11 eslint: 8.57.0 - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.8.0)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.9.0)(eslint@8.57.0) object.assign: 4.1.5 object.entries: 1.1.8 semver: 6.3.1 dev: true - /eslint-config-airbnb-typescript@18.0.0(@typescript-eslint/eslint-plugin@7.8.0)(@typescript-eslint/parser@7.8.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0): + /eslint-config-airbnb-typescript@18.0.0(@typescript-eslint/eslint-plugin@7.9.0)(@typescript-eslint/parser@7.9.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0): resolution: {integrity: sha512-oc+Lxzgzsu8FQyFVa4QFaVKiitTYiiW3frB9KYW5OWdPrqFc7FzxgB20hP4cHMlr+MBzGcLl3jnCOVOydL9mIg==} peerDependencies: '@typescript-eslint/eslint-plugin': ^7.0.0 '@typescript-eslint/parser': ^7.0.0 eslint: ^8.56.0 dependencies: - '@typescript-eslint/eslint-plugin': 7.8.0(@typescript-eslint/parser@7.8.0)(eslint@8.57.0)(typescript@5.4.5) - '@typescript-eslint/parser': 7.8.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/eslint-plugin': 7.9.0(@typescript-eslint/parser@7.9.0)(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/parser': 7.9.0(eslint@8.57.0)(typescript@5.4.5) eslint: 8.57.0 eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.1)(eslint@8.57.0) transitivePeerDependencies: @@ -5700,7 +5827,7 @@ packages: - supports-color dev: true - /eslint-module-utils@2.8.1(@typescript-eslint/parser@7.8.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0): + /eslint-module-utils@2.8.1(@typescript-eslint/parser@7.9.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0): resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==} engines: {node: '>=4'} peerDependencies: @@ -5721,7 +5848,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 7.8.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/parser': 7.9.0(eslint@8.57.0)(typescript@5.4.5) debug: 3.2.7 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 @@ -5729,7 +5856,7 @@ packages: - supports-color dev: true - /eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.8.0)(eslint@8.57.0): + /eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.9.0)(eslint@8.57.0): resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} engines: {node: '>=4'} peerDependencies: @@ -5739,7 +5866,7 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 7.8.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/parser': 7.9.0(eslint@8.57.0)(typescript@5.4.5) array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.2 @@ -5748,7 +5875,7 @@ packages: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.8.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.9.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.13.1 is-glob: 4.0.3 @@ -6822,7 +6949,7 @@ packages: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.11 + '@types/node': 20.12.12 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.3 @@ -6860,7 +6987,7 @@ packages: create-jest: 29.7.0 exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@20.12.11) + jest-config: 29.7.0(@types/node@20.12.12) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -6871,7 +6998,7 @@ packages: - ts-node dev: true - /jest-config@29.7.0(@types/node@20.12.11): + /jest-config@29.7.0(@types/node@20.12.12): resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -6886,7 +7013,7 @@ packages: '@babel/core': 7.24.5 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.11 + '@types/node': 20.12.12 babel-jest: 29.7.0(@babel/core@7.24.5) chalk: 4.1.2 ci-info: 3.9.0 @@ -6946,7 +7073,7 @@ packages: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.11 + '@types/node': 20.12.12 jest-mock: 29.7.0 jest-util: 29.7.0 dev: true @@ -6962,7 +7089,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 20.12.11 + '@types/node': 20.12.12 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -7013,7 +7140,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.12.11 + '@types/node': 20.12.12 jest-util: 29.7.0 dev: true @@ -7068,7 +7195,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.11 + '@types/node': 20.12.12 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -7099,7 +7226,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.11 + '@types/node': 20.12.12 chalk: 4.1.2 cjs-module-lexer: 1.3.1 collect-v8-coverage: 1.0.2 @@ -7151,7 +7278,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.12.11 + '@types/node': 20.12.12 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -7176,7 +7303,7 @@ packages: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.11 + '@types/node': 20.12.12 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -7188,7 +7315,7 @@ packages: resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@types/node': 20.12.11 + '@types/node': 20.12.12 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -8288,7 +8415,7 @@ packages: dependencies: lilconfig: 3.1.1 postcss: 8.4.38 - ts-node: 10.9.2(@types/node@20.12.11)(typescript@5.4.5) + ts-node: 10.9.2(@types/node@20.12.12)(typescript@5.4.5) yaml: 2.4.2 /postcss-nested@6.0.1(postcss@8.4.38): @@ -9466,7 +9593,7 @@ packages: code-block-writer: 12.0.0 dev: true - /ts-node@10.9.2(@types/node@20.12.11)(typescript@5.4.5): + /ts-node@10.9.2(@types/node@20.12.12)(typescript@5.4.5): resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} hasBin: true peerDependencies: @@ -9485,7 +9612,7 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.12.11 + '@types/node': 20.12.12 acorn: 8.11.3 acorn-walk: 8.3.2 arg: 4.1.3 @@ -9880,7 +10007,7 @@ packages: d3-timer: 3.0.1 dev: false - /vite-node@1.6.0(@types/node@20.12.11): + /vite-node@1.6.0(@types/node@20.12.12): resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -9889,7 +10016,7 @@ packages: debug: 4.3.4 pathe: 1.1.2 picocolors: 1.0.0 - vite: 5.2.11(@types/node@20.12.11) + vite: 5.2.11(@types/node@20.12.12) transitivePeerDependencies: - '@types/node' - less @@ -9908,12 +10035,12 @@ packages: dependencies: '@rollup/plugin-inject': 5.0.5 node-stdlib-browser: 1.2.0 - vite: 5.2.11(@types/node@20.12.11) + vite: 5.2.11(@types/node@20.12.12) transitivePeerDependencies: - rollup dev: false - /vite@5.2.11(@types/node@20.12.11): + /vite@5.2.11(@types/node@20.12.12): resolution: {integrity: sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -9941,14 +10068,14 @@ packages: terser: optional: true dependencies: - '@types/node': 20.12.11 + '@types/node': 20.12.12 esbuild: 0.20.2 postcss: 8.4.38 rollup: 4.17.2 optionalDependencies: fsevents: 2.3.3 - /vitest@1.6.0(@types/node@20.12.11)(jsdom@24.0.0): + /vitest@1.6.0(@types/node@20.12.12)(jsdom@24.0.0): resolution: {integrity: sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -9973,7 +10100,7 @@ packages: jsdom: optional: true dependencies: - '@types/node': 20.12.11 + '@types/node': 20.12.12 '@vitest/expect': 1.6.0 '@vitest/runner': 1.6.0 '@vitest/snapshot': 1.6.0 @@ -9992,8 +10119,8 @@ packages: strip-literal: 2.1.0 tinybench: 2.8.0 tinypool: 0.8.4 - vite: 5.2.11(@types/node@20.12.11) - vite-node: 1.6.0(@types/node@20.12.11) + vite: 5.2.11(@types/node@20.12.12) + vite-node: 1.6.0(@types/node@20.12.12) why-is-node-running: 2.2.2 transitivePeerDependencies: - less diff --git a/ui/package.json b/ui/package.json index cf191695..780eb0a1 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,10 +1,6 @@ { "name": "reti-ui", - "version": "0.8.6", - "author": { - "name": "Doug Richar", - "email": "drichar@gmail.com" - }, + "version": "0.8.7", "private": true, "type": "module", "engines": { diff --git a/ui/src/api/algod.ts b/ui/src/api/algod.ts index bb44f4e9..2d64026b 100644 --- a/ui/src/api/algod.ts +++ b/ui/src/api/algod.ts @@ -38,8 +38,17 @@ export async function fetchAccountBalance( } export async function fetchAsset(assetId: number): Promise { - const asset = await algodClient.getAssetByID(assetId).do() - return asset as Asset + try { + const asset = await algodClient.getAssetByID(assetId).do() + return asset as Asset + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + if (error.message && error.response) { + throw new AlgodHttpError(error.message, error.response) + } else { + throw error + } + } } export async function fetchBalance(address: string | null): Promise { diff --git a/ui/src/api/contracts.ts b/ui/src/api/contracts.ts index ad8aa98c..d6dceb80 100644 --- a/ui/src/api/contracts.ts +++ b/ui/src/api/contracts.ts @@ -2,7 +2,7 @@ import * as algokit from '@algorandfoundation/algokit-utils' import { TransactionSignerAccount } from '@algorandfoundation/algokit-utils/types/account' import { AlgoAmount } from '@algorandfoundation/algokit-utils/types/amount' import algosdk from 'algosdk' -import { isOptedInToAsset } from '@/api/algod' +import { fetchAsset, isOptedInToAsset } from '@/api/algod' import { getSimulateStakingPoolClient, getSimulateValidatorClient, @@ -24,7 +24,6 @@ import { RawConstraints, RawNodePoolAssignmentConfig, RawPoolsInfo, - RawPoolTokenPayoutRatios, RawValidatorConfig, RawValidatorState, Validator, @@ -85,28 +84,19 @@ export async function fetchValidator( try { const validatorClient = client || (await getSimulateValidatorClient()) - const [config, state, validatorPoolData, poolTokenPayoutRatios, nodePoolAssignments] = - await Promise.all([ - callGetValidatorConfig(Number(validatorId), validatorClient), - callGetValidatorState(Number(validatorId), validatorClient), - callGetPools(Number(validatorId), validatorClient), - callGetTokenPayoutRatio(Number(validatorId), validatorClient), - callGetNodePoolAssignments(Number(validatorId), validatorClient), - ]) + const [config, state, validatorPoolData, nodePoolAssignments] = await Promise.all([ + callGetValidatorConfig(Number(validatorId), validatorClient), + callGetValidatorState(Number(validatorId), validatorClient), + callGetPools(Number(validatorId), validatorClient), + callGetNodePoolAssignments(Number(validatorId), validatorClient), + ]) const rawConfig = config.returns?.[0] as RawValidatorConfig const rawState = state.returns?.[0] as RawValidatorState const rawPoolsInfo = validatorPoolData.returns?.[0] as RawPoolsInfo - const rawPoolTokenPayoutRatios = poolTokenPayoutRatios.returns?.[0] as RawPoolTokenPayoutRatios const rawNodePoolAssignment = nodePoolAssignments.returns?.[0] as RawNodePoolAssignmentConfig - if ( - !rawConfig || - !rawState || - !rawPoolsInfo || - !rawPoolTokenPayoutRatios || - !rawNodePoolAssignment - ) { + if (!rawConfig || !rawState || !rawPoolsInfo || !rawNodePoolAssignment) { throw new ValidatorNotFoundError(`Validator with id "${Number(validatorId)}" not found!`) } @@ -115,10 +105,14 @@ export async function fetchValidator( rawConfig, rawState, rawPoolsInfo, - rawPoolTokenPayoutRatios, rawNodePoolAssignment, ) + if (validator.config.rewardTokenId > 0) { + const rewardToken = await fetchAsset(validator.config.rewardTokenId) + validator.rewardToken = rewardToken + } + if (validator.config.nfdForInfo > 0) { const nfd = await fetchNfd(validator.config.nfdForInfo, { view: 'full' }) validator.nfd = nfd @@ -318,29 +312,6 @@ export async function fetchNodePoolAssignments( } } -export function callGetTokenPayoutRatio( - validatorId: number | bigint, - validatorClient: ValidatorRegistryClient, -) { - return validatorClient - .compose() - .getTokenPayoutRatio({ validatorId }) - .simulate({ allowEmptySignatures: true, allowUnnamedResources: true }) -} - -export async function fetchTokenPayoutRatio(validatorId: string | number | bigint) { - try { - const validatorClient = await getSimulateValidatorClient() - - const result = await callGetTokenPayoutRatio(Number(validatorId), validatorClient) - - return result.returns![0] - } catch (error) { - console.error(error) - throw error - } -} - export function callGetMbrAmounts(validatorClient: ValidatorRegistryClient) { return validatorClient .compose() @@ -539,6 +510,8 @@ export async function addStake( suggestedParams, }) + const needsOptInTxn = rewardTokenId > 0 && !(await isOptedInToAsset(activeAddress, rewardTokenId)) + const simulateValidatorClient = await getSimulateValidatorClient(activeAddress, authAddr) const simulateComposer = simulateValidatorClient @@ -556,6 +529,18 @@ export async function addStake( { sendParams: { fee: AlgoAmount.MicroAlgos(240_000) } }, ) + if (needsOptInTxn) { + const rewardTokenOptInTxn = algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({ + from: activeAddress, + to: activeAddress, + amount: 0, + assetIndex: rewardTokenId, + suggestedParams, + }) + + simulateComposer.addTransaction(rewardTokenOptInTxn) + } + const simulateResults = await simulateComposer.simulate({ allowEmptySignatures: true, allowUnnamedResources: true, @@ -568,8 +553,6 @@ export async function addStake( 2000 + 1000 * ((simulateResults.simulateResponse.txnGroups[0].appBudgetAdded as number) / 700), ) - const needsOptInTxn = rewardTokenId > 0 && !(await isOptedInToAsset(activeAddress, rewardTokenId)) - const composer = validatorClient .compose() .gas({}) @@ -834,6 +817,8 @@ export async function removeStake( authAddr, ) + const needsOptInTxn = rewardTokenId > 0 && !(await isOptedInToAsset(activeAddress, rewardTokenId)) + const simulateComposer = stakingPoolSimulateClient .compose() .gas({}, { note: '1', sendParams: { fee: AlgoAmount.MicroAlgos(0) } }) @@ -846,6 +831,18 @@ export async function removeStake( { sendParams: { fee: AlgoAmount.MicroAlgos(240_000) } }, ) + if (needsOptInTxn) { + const rewardTokenOptInTxn = algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({ + from: activeAddress, + to: activeAddress, + amount: 0, + assetIndex: rewardTokenId, + suggestedParams, + }) + + simulateComposer.addTransaction(rewardTokenOptInTxn) + } + const simulateResult = await simulateComposer.simulate({ allowEmptySignatures: true, allowUnnamedResources: true, @@ -859,8 +856,6 @@ export async function removeStake( ), ) - const needsOptInTxn = rewardTokenId > 0 && !(await isOptedInToAsset(activeAddress, rewardTokenId)) - const stakingPoolClient = await getStakingPoolClient(poolAppId, signer, activeAddress) const composer = stakingPoolClient diff --git a/ui/src/components/AddStakeModal.tsx b/ui/src/components/AddStakeModal.tsx index 1b142041..c2a10986 100644 --- a/ui/src/components/AddStakeModal.tsx +++ b/ui/src/components/AddStakeModal.tsx @@ -321,6 +321,7 @@ export function AddStakeModal({ // Invalidate other queries to update UI queryClient.invalidateQueries({ queryKey: ['stakes', { staker: activeAddress }] }) queryClient.invalidateQueries({ queryKey: ['staked-info'] }) + queryClient.invalidateQueries({ queryKey: ['pools-info'] }) router.invalidate() } catch (error) { toast.error('Failed to add stake to pool', { id: toastId }) diff --git a/ui/src/components/AddValidatorForm.tsx b/ui/src/components/AddValidatorForm.tsx index a14bec14..e72b8d0f 100644 --- a/ui/src/components/AddValidatorForm.tsx +++ b/ui/src/components/AddValidatorForm.tsx @@ -12,6 +12,7 @@ import { z } from 'zod' import { addValidator, fetchValidator } from '@/api/contracts' import { fetchNfd } from '@/api/nfd' import { AlgoSymbol } from '@/components/AlgoSymbol' +import { AssetLookup } from '@/components/AssetLookup' import { InfoPopover } from '@/components/InfoPopover' import { Button } from '@/components/ui/button' import { @@ -35,6 +36,7 @@ import { import { Separator } from '@/components/ui/separator' import { GatingType } from '@/constants/gating' import { useBlockTime } from '@/hooks/useBlockTime' +import { Asset } from '@/interfaces/algod' import { Constraints } from '@/interfaces/validator' import { useAuthAddress } from '@/providers/AuthAddressProvider' import { @@ -43,10 +45,11 @@ import { transformEntryGatingAssets, } from '@/utils/contracts' // import { validatorAutoFill } from '@/utils/development' +import { convertToBaseUnits } from '@/utils/format' import { getNfdAppFromViteEnvironment } from '@/utils/network/getNfdConfig' import { isValidName, trimExtension } from '@/utils/nfd' import { cn } from '@/utils/ui' -import { entryGatingRefinement, validatorSchemas } from '@/utils/validation' +import { entryGatingRefinement, rewardTokenRefinement, validatorSchemas } from '@/utils/validation' const nfdAppUrl = getNfdAppFromViteEnvironment() @@ -61,6 +64,8 @@ export function AddValidatorForm({ constraints }: AddValidatorFormProps) { const [isFetchingNfdCreator, setIsFetchingNfdCreator] = React.useState(false) const [nfdParentAppId, setNfdParentAppId] = React.useState(0) const [isFetchingNfdParent, setIsFetchingNfdParent] = React.useState(false) + const [rewardToken, setRewardToken] = React.useState(null) + const [isFetchingRewardToken, setIsFetchingRewardToken] = React.useState(false) const [epochTimeframe, setEpochTimeframe] = React.useState('blocks') const [isSigning, setIsSigning] = React.useState(false) @@ -90,6 +95,7 @@ export function AddValidatorForm({ constraints }: AddValidatorFormProps) { minEntryStake: validatorSchemas.minEntryStake(constraints), poolsPerNode: validatorSchemas.poolsPerNode(constraints), }) + .superRefine((data, ctx) => rewardTokenRefinement(data, ctx, rewardToken?.params.decimals)) .superRefine((data, ctx) => entryGatingRefinement(data, ctx)) type FormValues = z.infer @@ -123,6 +129,9 @@ export function AddValidatorForm({ constraints }: AddValidatorFormProps) { name: 'entryGatingAssets', }) + const $rewardTokenId = form.watch('rewardTokenId') + const isRewardTokenInvalid = Number($rewardTokenId) > 0 && (isFetchingRewardToken || !rewardToken) + const $entryGatingType = form.watch('entryGatingType') const showCreatorAddressField = $entryGatingType === String(GatingType.CreatorAccount) @@ -242,6 +251,11 @@ export function AddValidatorForm({ constraints }: AddValidatorFormProps) { toast.loading('Sign transactions to add validator...', { id: toastId }) + const rewardPerPayout = convertToBaseUnits( + Number(values.rewardPerPayout), + Number(rewardToken?.params.decimals || 0), + ) + const epochRoundLength = getEpochLengthBlocks( values.epochRoundLength, epochTimeframe, @@ -257,6 +271,7 @@ export function AddValidatorForm({ constraints }: AddValidatorFormProps) { const newValues = { ...values, + rewardPerPayout: String(rewardPerPayout), epochRoundLength: String(epochRoundLength), entryGatingAssets, } @@ -672,18 +687,15 @@ export function AddValidatorForm({ constraints }: AddValidatorFormProps) {

Reward token to be paid out to stakers (optional)

- ( - - Asset ID - - - - {errors.rewardTokenId?.message} - - )} + label="Asset ID" + asset={rewardToken} + setAsset={setRewardToken} + isFetching={isFetchingRewardToken} + setIsFetching={setIsFetchingRewardToken} + errorMessage={errors.rewardTokenId?.message} /> diff --git a/ui/src/components/AssetLookup.tsx b/ui/src/components/AssetLookup.tsx new file mode 100644 index 00000000..c60dc9e0 --- /dev/null +++ b/ui/src/components/AssetLookup.tsx @@ -0,0 +1,144 @@ +import { Check } from 'lucide-react' +import { FieldPath, FieldValues, UseFormReturn } from 'react-hook-form' +import { useDebouncedCallback } from 'use-debounce' +import { fetchAsset as fetchAssetInformation } from '@/api/algod' +import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form' +import { Input } from '@/components/ui/input' +import { AlgodHttpError, Asset } from '@/interfaces/algod' +import { cn } from '@/utils/ui' + +const ERROR_NOT_FOUND = 'Asset not found' +const ERROR_FAILED = 'Failed to fetch asset' +const ERROR_UNKNOWN = 'An unknown error occurred' + +interface AssetLookupProps< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +> { + form: UseFormReturn + name: TName + asset: Asset | null + setAsset: (asset: Asset | null) => void + isFetching: boolean + setIsFetching: (isFetching: boolean) => void + errorMessage?: string + label?: string + className?: string +} + +export function AssetLookup< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +>({ + form, + name, + asset, + setAsset, + isFetching, + setIsFetching, + errorMessage, + label, + className = '', +}: AssetLookupProps) { + const fetchAsset = async (value: string) => { + try { + const asset = await fetchAssetInformation(Number(value)) + + form.clearErrors(name) + setAsset(asset) + } catch (error: unknown) { + let message: string + if (error instanceof AlgodHttpError && error.response) { + // Handle HTTP errors + if (error.response.status === 404) { + message = ERROR_NOT_FOUND + } else { + console.error(error) + message = ERROR_FAILED + } + } else if (error instanceof Error) { + // Handle non-HTTP errors + console.error(error) + message = error.message + } else { + // Handle unknown errors + console.error(error) + message = ERROR_UNKNOWN + } + form.setError(name, { type: 'manual', message }) + } finally { + setIsFetching(false) + } + } + + const debouncedFetchAsset = useDebouncedCallback(async (value) => { + const isValid = await form.trigger(name) + if (isValid) { + await fetchAsset(value) + } else { + setIsFetching(false) + } + }, 500) + + return ( + ( + + {label && {label}} +
+
+ + { + field.onChange(e) // Inform react-hook-form of the change + setAsset(null) // Reset asset + setIsFetching(true) // Set fetching state + debouncedFetchAsset(e.target.value) // Perform debounced validation + }} + /> + +
+ {isFetching ? ( + + ) : asset ? ( +
+ + {asset.params['unit-name']} + + +
+ ) : null} +
+
+
+ {errorMessage} +
+ )} + /> + ) +} diff --git a/ui/src/components/UnstakeModal.tsx b/ui/src/components/UnstakeModal.tsx index d5d17246..1b68743c 100644 --- a/ui/src/components/UnstakeModal.tsx +++ b/ui/src/components/UnstakeModal.tsx @@ -216,6 +216,7 @@ export function UnstakeModal({ validator, setValidator, stakesByValidator }: Uns // Invalidate other queries to update UI queryClient.invalidateQueries({ queryKey: ['stakes', { staker: activeAddress }] }) queryClient.invalidateQueries({ queryKey: ['staked-info'] }) + queryClient.invalidateQueries({ queryKey: ['pools-info'] }) router.invalidate() } catch (error) { toast.error('Failed to remove stake from pool', { id: toastId }) diff --git a/ui/src/components/ValidatorDetails/Details.tsx b/ui/src/components/ValidatorDetails/Details.tsx index ea9588d4..045121c5 100644 --- a/ui/src/components/ValidatorDetails/Details.tsx +++ b/ui/src/components/ValidatorDetails/Details.tsx @@ -14,7 +14,7 @@ import { Validator } from '@/interfaces/validator' import { dayjs } from '@/utils/dayjs' import { ellipseAddressJsx } from '@/utils/ellipseAddress' import { ExplorerLink } from '@/utils/explorer' -import { formatAssetAmount, formatNumber } from '@/utils/format' +import { convertFromBaseUnits, formatAssetAmount, formatNumber } from '@/utils/format' import { getNfdAppFromViteEnvironment } from '@/utils/network/getNfdConfig' const nfdAppUrl = getNfdAppFromViteEnvironment() @@ -28,6 +28,59 @@ export function Details({ validator }: DetailsProps) { const isOwner = activeAddress === validator.config.owner + const renderRewardToken = () => { + if (validator.config.rewardTokenId === 0) { + return null + } + + const { rewardToken } = validator + + if (!rewardToken) { + return validator.config.rewardTokenId + } + + const { name, 'unit-name': unitName } = rewardToken.params + + if (name) { + return unitName ? ( + <> + {name} ({unitName}) + + ) : ( + name + ) + } + + if (unitName) { + return {unitName} + } + + return validator.config.rewardTokenId + } + + const renderRewardPerPayout = () => { + if (validator.config.rewardPerPayout === 0n) { + return -- + } + + if (!validator.rewardToken) { + return ( + {Number(validator.config.rewardPerPayout)} + ) + } + + const convertedAmount = convertFromBaseUnits( + Number(validator.config.rewardPerPayout), + Number(validator.rewardToken.params.decimals), + ) + + return ( + + {formatNumber(convertedAmount)} {validator.rewardToken.params['unit-name']} + + ) + } + const renderEntryGating = () => { const { entryGatingType, entryGatingAddress, entryGatingAssets } = validator.config @@ -228,7 +281,14 @@ export function Details({ validator }: DetailsProps) { {validator.config.rewardTokenId === 0 ? ( -- ) : ( - validator.config.rewardTokenId + + {renderRewardToken()} + )} @@ -237,11 +297,7 @@ export function Details({ validator }: DetailsProps) { Reward Per Payout
- {Number(validator.config.rewardPerPayout) === 0 ? ( - -- - ) : ( - Number(validator.config.rewardPerPayout) - )} + {renderRewardPerPayout()} {isOwner && validator.config.rewardTokenId > 0 && ( )} diff --git a/ui/src/interfaces/validator.ts b/ui/src/interfaces/validator.ts index 4f1404d6..ea7e3f66 100644 --- a/ui/src/interfaces/validator.ts +++ b/ui/src/interfaces/validator.ts @@ -1,3 +1,4 @@ +import { Asset } from '@/interfaces/algod' import { Nfd } from '@/interfaces/nfd' import { ToStringTypes } from '@/interfaces/utils' @@ -69,13 +70,6 @@ export interface PoolInfo { algodVersion?: string } -export type RawPoolTokenPayoutRatios = [[bigint, ...bigint[]], bigint] - -export interface PoolTokenPayoutRatio { - poolPctOfWhole: number[] - updatedForPayout: number -} - export type NodeConfig = [bigint, ...bigint[]] export type RawNodePoolAssignmentConfig = [[NodeConfig][]] export type NodePoolAssignmentConfig = NodeConfig[] @@ -90,8 +84,8 @@ export type Validator = { config: Omit state: ValidatorState pools: PoolInfo[] - tokenPayoutRatio: number[] nodePoolAssignment: NodePoolAssignmentConfig + rewardToken?: Asset nfd?: Nfd } diff --git a/ui/src/utils/contracts.ts b/ui/src/utils/contracts.ts index 51891f4f..2ad082a7 100644 --- a/ui/src/utils/contracts.ts +++ b/ui/src/utils/contracts.ts @@ -12,10 +12,8 @@ import { NodeInfo, NodePoolAssignmentConfig, PoolInfo, - PoolTokenPayoutRatio, RawNodePoolAssignmentConfig, RawPoolsInfo, - RawPoolTokenPayoutRatios, RawValidatorConfig, RawValidatorState, Validator, @@ -90,28 +88,11 @@ export function transformNodePoolAssignment( return rawConfig[0].flat() } -/** - * Transform raw pool token payout ratio data (from `callGetTokenPayoutRatio`) into a flat array of payout ratios - * @param {RawPoolTokenPayoutRatios} rawData - Raw pool token payout ratio data - * @returns {number[]} Array of pool token payout ratios - */ -export function transformPoolTokenPayoutRatio(rawData: RawPoolTokenPayoutRatios): number[] { - const [poolPctOfWhole, updatedForPayout] = rawData - - const poolTokenPayoutRatio: PoolTokenPayoutRatio = { - poolPctOfWhole: poolPctOfWhole.map((poolPct) => Number(poolPct)), - updatedForPayout: Number(updatedForPayout), - } - - return poolTokenPayoutRatio.poolPctOfWhole -} - /** * Transform raw validator data from multiple ABI method calls into a structured `Validator` object * @param {RawValidatorConfig} rawConfig - Raw validator configuration data * @param {RawValidatorState} rawState - Raw validator state data * @param {RawPoolsInfo} rawPoolsInfo - Raw staking pool data - * @param {RawPoolTokenPayoutRatios} rawPoolTokenPayoutRatios - Raw pool token payout ratio data * @param {RawNodePoolAssignmentConfig} rawNodePoolAssignment - Raw node pool assignment configuration data * @returns {Validator} Structured validator object */ @@ -119,13 +100,11 @@ export function transformValidatorData( rawConfig: RawValidatorConfig, rawState: RawValidatorState, rawPoolsInfo: RawPoolsInfo, - rawPoolTokenPayoutRatios: RawPoolTokenPayoutRatios, rawNodePoolAssignment: RawNodePoolAssignmentConfig, ): Validator { const { id, ...config } = transformValidatorConfig(rawConfig) const state = transformValidatorState(rawState) const pools = transformPoolsInfo(rawPoolsInfo) - const tokenPayoutRatio = transformPoolTokenPayoutRatio(rawPoolTokenPayoutRatios) const nodePoolAssignment = transformNodePoolAssignment(rawNodePoolAssignment) return { @@ -133,7 +112,6 @@ export function transformValidatorData( config, state, pools, - tokenPayoutRatio, nodePoolAssignment, } } diff --git a/ui/src/utils/format.spec.ts b/ui/src/utils/format.spec.ts index c68fb51e..50d42dc6 100644 --- a/ui/src/utils/format.spec.ts +++ b/ui/src/utils/format.spec.ts @@ -99,6 +99,11 @@ describe('formatNumber', () => { expect(result).toBe('12,345.68') }) + it('should include all decimal places if precision is undefined', () => { + const result = formatNumber(12345.6789) + expect(result).toBe('12,345.6789') + }) + it('should format a number in compact notation with precision', () => { const result = formatNumber(1234567, { compact: true, precision: 2 }) expect(result).toBe('1.23M') diff --git a/ui/src/utils/format.ts b/ui/src/utils/format.ts index 722afc5e..5ed39f9a 100644 --- a/ui/src/utils/format.ts +++ b/ui/src/utils/format.ts @@ -210,7 +210,7 @@ export function formatNumber( amount: number | bigint | string, options: FormatNumberOptions = {}, ): string { - const { compact = false, precision = 0, trim = true } = options + const { compact = false, precision, trim = true } = options // Handle BigInt separately to preserve precision if (typeof amount === 'bigint') { @@ -221,7 +221,7 @@ export function formatNumber( const numericAmount = parseFloat(String(amount)) if (compact) { - return formatWithPrecision(numericAmount, precision) + return formatWithPrecision(numericAmount, precision || 0) } const bigAmount = new Big(numericAmount).toFixed(precision) diff --git a/ui/src/utils/tests/fixtures/validators.ts b/ui/src/utils/tests/fixtures/validators.ts index d86b289b..704f549c 100644 --- a/ui/src/utils/tests/fixtures/validators.ts +++ b/ui/src/utils/tests/fixtures/validators.ts @@ -114,8 +114,6 @@ export const MOCK_VALIDATOR_2_POOL_ASSIGNMENT: NodeConfig[] = createStaticArray< 8, ) -export const MOCK_TOKEN_PAYOUT_RATIO = new Array(24).fill(0) - const { id: validator1Id, ...validator1Config } = MOCK_VALIDATOR_1_CONFIG export const MOCK_VALIDATOR_1: Validator = { @@ -123,7 +121,6 @@ export const MOCK_VALIDATOR_1: Validator = { config: validator1Config, state: MOCK_VALIDATOR_1_STATE, pools: MOCK_VALIDATOR_1_POOLS, - tokenPayoutRatio: MOCK_TOKEN_PAYOUT_RATIO, nodePoolAssignment: MOCK_VALIDATOR_1_POOL_ASSIGNMENT, } @@ -134,7 +131,6 @@ export const MOCK_VALIDATOR_2: Validator = { config: validator2Config, state: MOCK_VALIDATOR_2_STATE, pools: MOCK_VALIDATOR_2_POOLS, - tokenPayoutRatio: MOCK_TOKEN_PAYOUT_RATIO, nodePoolAssignment: MOCK_VALIDATOR_2_POOL_ASSIGNMENT, } diff --git a/ui/src/utils/validation.ts b/ui/src/utils/validation.ts index a6796893..afbaef3f 100644 --- a/ui/src/utils/validation.ts +++ b/ui/src/utils/validation.ts @@ -227,6 +227,49 @@ export const validatorSchemas = { }, } +/** + * Validator schema refinement for reward token + * @param {any} data - The form data + * @param {RefinementCtx} ctx - The refinement context + * @param {number | bigint} decimals - The reward token's decimals value + */ +export const rewardTokenRefinement = ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data: any, + ctx: RefinementCtx, + decimals?: number | bigint, +) => { + const { rewardTokenId, rewardPerPayout } = data + + if (Number(rewardTokenId) > 0) { + if (rewardPerPayout === '') { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ['rewardPerPayout'], + message: 'Required field', + }) + } else if (decimals) { + const regex = new RegExp(`^\\d+(\\.\\d{1,${Number(decimals)}})?$`) + + if (!regex.test(rewardPerPayout)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ['rewardPerPayout'], + message: `Cannot have more than ${decimals} decimal places`, + }) + } + } + } + + if (Number(rewardTokenId) > 0 && rewardPerPayout === '') { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ['rewardPerPayout'], + message: 'Reward per payout must be set when reward token is enabled', + }) + } +} + /** * Validator schema refinement for entry gating * @param {any} data - The form data