From 7bb4a473baf6f409fae72d33166e1ae4a3e6dafd Mon Sep 17 00:00:00 2001 From: Uncle Fatso Date: Tue, 11 Nov 2025 17:53:08 +0300 Subject: [PATCH] self applause added as page with batteries included Signed-off-by: Uncle Fatso --- package.json | 7 +- pnpm-lock.yaml | 377 +++++++++++++++++++++++ src/components/Header.tsx | 2 + src/components/Sidebar.tsx | 8 +- src/containers/App.tsx | 2 + src/containers/SelfApplause.tsx | 287 +++++++++++++++++ src/hooks/index.ts | 5 + src/hooks/useApplausesForTransaction.tsx | 54 ++++ src/hooks/useAuthorities.tsx | 46 +++ src/hooks/useCalldata.tsx | 42 ++- src/hooks/useClapsInSession.tsx | 51 +++ src/hooks/useConstants.tsx | 18 ++ src/hooks/useCurrentIndex.tsx | 42 +++ src/hooks/useReceivedClaps.tsx | 54 ++++ 14 files changed, 989 insertions(+), 6 deletions(-) create mode 100644 src/containers/SelfApplause.tsx create mode 100644 src/hooks/useApplausesForTransaction.tsx create mode 100644 src/hooks/useAuthorities.tsx create mode 100644 src/hooks/useClapsInSession.tsx create mode 100644 src/hooks/useCurrentIndex.tsx create mode 100644 src/hooks/useReceivedClaps.tsx diff --git a/package.json b/package.json index 3b6133e..d4a4a71 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghost-lite", - "version": "0.1.7", + "version": "0.1.8", "description": "Web application for Ghost and Casper chain.", "author": "Uncle f4ts0 ", "maintainers": [ @@ -28,6 +28,7 @@ "@polkadot-api/utils": "~0.1.2", "@polkadot-api/view-builder": "~0.4.3", "@polkadot-labs/hdkd-helpers": "^0.0.11", + "@polkadot/util-crypto": "13.5.5", "@radix-ui/react-accordion": "^1.2.3", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-select": "^2.1.6", @@ -47,9 +48,11 @@ "react-icons": "^5.5.0", "react-router-dom": "^7.7.0", "rxjs": "^7.8.1", + "scale-ts": "1.6.1", "swr": "^2.2.5", "tailwind-merge": "^3.3.1", - "typescript": "^5.6.2" + "typescript": "^5.6.2", + "viem": "2.31.0" }, "devDependencies": { "@tailwindcss/postcss": "^4.1.11", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e78501d..8c6cbe7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: '@polkadot-labs/hdkd-helpers': specifier: ^0.0.11 version: 0.0.11 + '@polkadot/util-crypto': + specifier: 13.5.5 + version: 13.5.5(@polkadot/util@13.5.8) '@radix-ui/react-accordion': specifier: ^1.2.3 version: 1.2.11(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -89,6 +92,9 @@ importers: rxjs: specifier: ^7.8.1 version: 7.8.2 + scale-ts: + specifier: 1.6.1 + version: 1.6.1 swr: specifier: ^2.2.5 version: 2.3.4(react@18.3.1) @@ -98,6 +104,9 @@ importers: typescript: specifier: ^5.6.2 version: 5.6.2 + viem: + specifier: 2.31.0 + version: 2.31.0(typescript@5.6.2) devDependencies: '@tailwindcss/postcss': specifier: ^4.1.11 @@ -150,6 +159,9 @@ importers: packages: + '@adraffy/ens-normalize@1.11.1': + resolution: {integrity: sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==} + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -1179,10 +1191,18 @@ packages: '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} + '@noble/ciphers@1.3.0': + resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} + engines: {node: ^14.21.3 || >=16} + '@noble/curves@1.8.2': resolution: {integrity: sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==} engines: {node: ^14.21.3 || >=16} + '@noble/curves@1.9.1': + resolution: {integrity: sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==} + engines: {node: ^14.21.3 || >=16} + '@noble/curves@1.9.2': resolution: {integrity: sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g==} engines: {node: ^14.21.3 || >=16} @@ -1343,6 +1363,102 @@ packages: '@polkadot-labs/hdkd-helpers@0.0.11': resolution: {integrity: sha512-qPlWqC3NNV/2NYc5GEy+Ovi4UBAgkMGvMfyiYuj2BQN4lW59Q1T9coNx0Yp6XzsnJ1ddaF9PWaUtxj3LdM0IDw==} + '@polkadot/networks@13.5.5': + resolution: {integrity: sha512-gTaKVSDRxjNAQ/oFMA83DXOo8A+/LP4XePbEHxNCku/Ox5R3IYGKTeZhlHgYtUZvdZgK+miyroEyz1Eq6Z9p+Q==} + engines: {node: '>=18'} + + '@polkadot/util-crypto@13.5.5': + resolution: {integrity: sha512-LAHarViiPwjrXl05fXOV5pW6jvK8A0Y6uIJnttSSERjTKqG5O4VtgRAcqLXShTp1rEVE5T4DaIX5xZd7azBHyg==} + engines: {node: '>=18'} + peerDependencies: + '@polkadot/util': 13.5.5 + + '@polkadot/util@13.5.5': + resolution: {integrity: sha512-O3sGI8vWmv5o1cd8fDkc+cZGpUsG+ZUFAOitgv6bRt5llaBqS5VpTrUANEjfgUMgUuTn7Y2cPKGDLItYr5WnUg==} + engines: {node: '>=18'} + + '@polkadot/util@13.5.8': + resolution: {integrity: sha512-5xEfNoum/Ct+gYWN3AYvBQ8vq8KiS4HsY3BexPUPXvSXSx3Id/JYA5oFLYnkuRp8hwoQGjX0wqUJ6Hp8D8LHKw==} + engines: {node: '>=18'} + + '@polkadot/wasm-bridge@7.5.1': + resolution: {integrity: sha512-E+N3CSnX3YaXpAmfIQ+4bTyiAqJQKvVcMaXjkuL8Tp2zYffClWLG5e+RY15Uh+EWfUl9If4y6cLZi3D5NcpAGQ==} + engines: {node: '>=18'} + peerDependencies: + '@polkadot/util': '*' + '@polkadot/x-randomvalues': '*' + + '@polkadot/wasm-crypto-asmjs@7.5.1': + resolution: {integrity: sha512-jAg7Uusk+xeHQ+QHEH4c/N3b1kEGBqZb51cWe+yM61kKpQwVGZhNdlWetW6U23t/BMyZArIWMsZqmK/Ij0PHog==} + engines: {node: '>=18'} + peerDependencies: + '@polkadot/util': '*' + + '@polkadot/wasm-crypto-init@7.5.1': + resolution: {integrity: sha512-Obu4ZEo5jYO6sN31eqCNOXo88rPVkP9TrUOyynuFCnXnXr8V/HlmY/YkAd9F87chZnkTJRlzak17kIWr+i7w3A==} + engines: {node: '>=18'} + peerDependencies: + '@polkadot/util': '*' + '@polkadot/x-randomvalues': '*' + + '@polkadot/wasm-crypto-wasm@7.5.1': + resolution: {integrity: sha512-S2yQSGbOGTcaV6UdipFVyEGanJvG6uD6Tg7XubxpiGbNAblsyYKeFcxyH1qCosk/4qf+GIUwlOL4ydhosZflqg==} + engines: {node: '>=18'} + peerDependencies: + '@polkadot/util': '*' + + '@polkadot/wasm-crypto@7.5.1': + resolution: {integrity: sha512-acjt4VJ3w19v7b/SIPsV/5k9s6JsragHKPnwoZ0KTfBvAFXwzz80jUzVGxA06SKHacfCUe7vBRlz7M5oRby1Pw==} + engines: {node: '>=18'} + peerDependencies: + '@polkadot/util': '*' + '@polkadot/x-randomvalues': '*' + + '@polkadot/wasm-util@7.5.1': + resolution: {integrity: sha512-sbvu71isFhPXpvMVX+EkRnUg/+54Tx7Sf9BEMqxxoPj7cG1I/MKeDEwbQz6MaU4gm7xJqvEWCAemLFcXfHQ/2A==} + engines: {node: '>=18'} + peerDependencies: + '@polkadot/util': '*' + + '@polkadot/x-bigint@13.5.5': + resolution: {integrity: sha512-SAd7Lfdgp6mz+utkoML8MN9FqTMCuPfk7v5rLJnm9vHgXw5uYnycbjH5Uc7ZgQIQWtMXJV3thrlltMan5DUXtA==} + engines: {node: '>=18'} + + '@polkadot/x-bigint@13.5.8': + resolution: {integrity: sha512-4ltTNgFDZoPnuQBrP7Z3m3imQ3xKb7jKScAT/Gy89h9siLYyJdZ+qawZfO1cll6fqYlka+k7USqGeyOEqoyCfg==} + engines: {node: '>=18'} + + '@polkadot/x-global@13.5.5': + resolution: {integrity: sha512-fw+VM191bodacSeieMm8Vmrym4jjevX08IINDcQTd1gIOjtE5CriJhwfBbAF4WnlTp/11jhhbX4/SvWMubXAzQ==} + engines: {node: '>=18'} + + '@polkadot/x-global@13.5.8': + resolution: {integrity: sha512-KDK3CEG/RvfCu3w4HZ/iv6c49XrN5Hz/3mXUQdLfR+TFKADdNCoIhMZ9f7vHYgdnB9tlY9s6Dn2svY99h1wRiw==} + engines: {node: '>=18'} + + '@polkadot/x-randomvalues@13.5.5': + resolution: {integrity: sha512-W0AoNgr/NEVsHWegJUjUyI9Q1IoTHILIb/bkjyTcXTU3+2YFxP12ophhsI1dMaNbXqFotNyts7mNOsTVDnQNXA==} + engines: {node: '>=18'} + peerDependencies: + '@polkadot/util': 13.5.5 + '@polkadot/wasm-util': '*' + + '@polkadot/x-textdecoder@13.5.5': + resolution: {integrity: sha512-KkZ1rqdJZ8tsRY0D5pLqfU8B/BrSQVEPMKHj4s/oc8dTrikfEUC+ELaH2jdrUqsZX6K/OTHjaF0J31YZcr7rCg==} + engines: {node: '>=18'} + + '@polkadot/x-textdecoder@13.5.8': + resolution: {integrity: sha512-Uzz6spRDzzQDQBN6PNpjz0HVp2kqhQVJRh1ShLP9rBg+nH4we9VGriWGG5stkgNKjRGT0Z7cvx0FupRQoNDU4A==} + engines: {node: '>=18'} + + '@polkadot/x-textencoder@13.5.5': + resolution: {integrity: sha512-yEgUUojBb4goYf4V5I7urdJ+W+1aI13U1kZmUwMc+/G2YQz8pX3s/Tyb/iuxU5MlFh0AZZXP5NqUnFol+vwNEg==} + engines: {node: '>=18'} + + '@polkadot/x-textencoder@13.5.8': + resolution: {integrity: sha512-2jcVte6mUy+GXjpZsS7dFca8C2r8EGgaG5o7mVQZ+PjauD06O/UP2g48UuDJHGe1QCJN0f0WaoD+RNw9tOF2yQ==} + engines: {node: '>=18'} + '@radix-ui/number@1.1.1': resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} @@ -1780,6 +1896,12 @@ packages: '@scure/base@1.2.6': resolution: {integrity: sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==} + '@scure/bip32@1.7.0': + resolution: {integrity: sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==} + + '@scure/bip39@1.6.0': + resolution: {integrity: sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==} + '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} @@ -1804,6 +1926,9 @@ packages: peerDependencies: smoldot: ^2 + '@substrate/ss58-registry@1.51.0': + resolution: {integrity: sha512-TWDurLiPxndFgKjVavCniytBIw+t4ViOi7TYp9h/D0NMmkEc9klFTo+827eyEJ0lELpqO207Ey7uGxUa+BS1jQ==} + '@swc/core-darwin-arm64@1.12.11': resolution: {integrity: sha512-J19Jj9Y5x/N0loExH7W0OI9OwwoVyxutDdkyq1o/kgXyBqmmzV7Y/Q9QekI2Fm/qc5mNeAdP7aj4boY4AY/JPw==} engines: {node: '>=10'} @@ -1970,6 +2095,9 @@ packages: '@total-typescript/tsconfig@1.0.4': resolution: {integrity: sha512-fO4ctMPGz1kOFOQ4RCPBRBfMy3gDn+pegUfrGyUFRMv/Rd0ZM3/SHH3hFCYG4u6bPLG8OlmOGcBLDexvyr3A5w==} + '@types/bn.js@5.2.0': + resolution: {integrity: sha512-DLbJ1BPqxvQhIGbeu8VbUC1DiAiahHtAYvA0ZEAa4P31F7IaArc8z3C3BRQdWX4mtLQuABG4yzp76ZrS02Ui1Q==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -2162,6 +2290,17 @@ packages: '@zag-js/utils@0.48.0': resolution: {integrity: sha512-VxNfAY3qMBm+VEsbM9+GmXTV9Ks2MxrLcXIcTK4qaGBl0y+DZZHN+b8DVMTLZpmkQK6wkJnkEnFC1Sv9dsQYkA==} + abitype@1.0.8: + resolution: {integrity: sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg==} + peerDependencies: + typescript: '>=5.0.4' + zod: ^3 >=3.22.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -2295,6 +2434,9 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + bn.js@5.2.2: + resolution: {integrity: sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==} + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -2710,6 +2852,9 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + execa@9.6.0: resolution: {integrity: sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==} engines: {node: ^18.19.0 || >=20.5.0} @@ -3064,6 +3209,11 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isows@1.0.7: + resolution: {integrity: sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==} + peerDependencies: + ws: '*' + iterator.prototype@1.1.5: resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} engines: {node: '>= 0.4'} @@ -3380,6 +3530,14 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} + ox@0.7.1: + resolution: {integrity: sha512-+k9fY9PRNuAMHRFIUbiK9Nt5seYHHzSQs9Bj+iMETcGtlpS7SmBzcGSVUQO3+nqGLEiNK4598pHNFlVRaZbRsg==} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -4056,6 +4214,14 @@ packages: validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + viem@2.31.0: + resolution: {integrity: sha512-U7OMQ6yqK+bRbEIarf2vqxL7unSEQvNxvML/1zG7suAmKuJmipqdVTVJGKBCJiYsm/EremyO2FS4dHIPpGv+eA==} + peerDependencies: + typescript: '>=5.0.4' + peerDependenciesMeta: + typescript: + optional: true + vite-tsconfig-paths@5.1.4: resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} peerDependencies: @@ -4149,6 +4315,18 @@ packages: resolution: {integrity: sha512-DqUx8GI3r9BFWwU2DPKddL1E7xWfbFED82mLVhGXKlFEPe8IkBftzO7WfNwHtk7oGDHDeuH/o8VMpzzfMwmLUA==} engines: {node: '>=18'} + ws@8.18.2: + resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==} + 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 + ws@8.18.3: resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} engines: {node: '>=10.0.0'} @@ -4182,6 +4360,8 @@ packages: snapshots: + '@adraffy/ens-normalize@1.11.1': {} + '@alloc/quick-lru@5.2.0': {} '@ampproject/remapping@2.3.0': @@ -5267,10 +5447,16 @@ snapshots: dependencies: eslint-scope: 5.1.1 + '@noble/ciphers@1.3.0': {} + '@noble/curves@1.8.2': dependencies: '@noble/hashes': 1.7.2 + '@noble/curves@1.9.1': + dependencies: + '@noble/hashes': 1.8.0 + '@noble/curves@1.9.2': dependencies: '@noble/hashes': 1.8.0 @@ -5556,6 +5742,134 @@ snapshots: micro-sr25519: 0.1.3 scale-ts: 1.6.1 + '@polkadot/networks@13.5.5': + dependencies: + '@polkadot/util': 13.5.5 + '@substrate/ss58-registry': 1.51.0 + tslib: 2.8.1 + + '@polkadot/util-crypto@13.5.5(@polkadot/util@13.5.8)': + dependencies: + '@noble/curves': 1.9.2 + '@noble/hashes': 1.8.0 + '@polkadot/networks': 13.5.5 + '@polkadot/util': 13.5.8 + '@polkadot/wasm-crypto': 7.5.1(@polkadot/util@13.5.8)(@polkadot/x-randomvalues@13.5.5(@polkadot/util@13.5.8)(@polkadot/wasm-util@7.5.1(@polkadot/util@13.5.8))) + '@polkadot/wasm-util': 7.5.1(@polkadot/util@13.5.8) + '@polkadot/x-bigint': 13.5.5 + '@polkadot/x-randomvalues': 13.5.5(@polkadot/util@13.5.8)(@polkadot/wasm-util@7.5.1(@polkadot/util@13.5.8)) + '@scure/base': 1.2.6 + tslib: 2.8.1 + + '@polkadot/util@13.5.5': + dependencies: + '@polkadot/x-bigint': 13.5.5 + '@polkadot/x-global': 13.5.5 + '@polkadot/x-textdecoder': 13.5.5 + '@polkadot/x-textencoder': 13.5.5 + '@types/bn.js': 5.2.0 + bn.js: 5.2.2 + tslib: 2.8.1 + + '@polkadot/util@13.5.8': + dependencies: + '@polkadot/x-bigint': 13.5.8 + '@polkadot/x-global': 13.5.8 + '@polkadot/x-textdecoder': 13.5.8 + '@polkadot/x-textencoder': 13.5.8 + '@types/bn.js': 5.2.0 + bn.js: 5.2.2 + tslib: 2.8.1 + + '@polkadot/wasm-bridge@7.5.1(@polkadot/util@13.5.8)(@polkadot/x-randomvalues@13.5.5(@polkadot/util@13.5.8)(@polkadot/wasm-util@7.5.1(@polkadot/util@13.5.8)))': + dependencies: + '@polkadot/util': 13.5.8 + '@polkadot/wasm-util': 7.5.1(@polkadot/util@13.5.8) + '@polkadot/x-randomvalues': 13.5.5(@polkadot/util@13.5.8)(@polkadot/wasm-util@7.5.1(@polkadot/util@13.5.8)) + tslib: 2.8.1 + + '@polkadot/wasm-crypto-asmjs@7.5.1(@polkadot/util@13.5.8)': + dependencies: + '@polkadot/util': 13.5.8 + tslib: 2.8.1 + + '@polkadot/wasm-crypto-init@7.5.1(@polkadot/util@13.5.8)(@polkadot/x-randomvalues@13.5.5(@polkadot/util@13.5.8)(@polkadot/wasm-util@7.5.1(@polkadot/util@13.5.8)))': + dependencies: + '@polkadot/util': 13.5.8 + '@polkadot/wasm-bridge': 7.5.1(@polkadot/util@13.5.8)(@polkadot/x-randomvalues@13.5.5(@polkadot/util@13.5.8)(@polkadot/wasm-util@7.5.1(@polkadot/util@13.5.8))) + '@polkadot/wasm-crypto-asmjs': 7.5.1(@polkadot/util@13.5.8) + '@polkadot/wasm-crypto-wasm': 7.5.1(@polkadot/util@13.5.8) + '@polkadot/wasm-util': 7.5.1(@polkadot/util@13.5.8) + '@polkadot/x-randomvalues': 13.5.5(@polkadot/util@13.5.8)(@polkadot/wasm-util@7.5.1(@polkadot/util@13.5.8)) + tslib: 2.8.1 + + '@polkadot/wasm-crypto-wasm@7.5.1(@polkadot/util@13.5.8)': + dependencies: + '@polkadot/util': 13.5.8 + '@polkadot/wasm-util': 7.5.1(@polkadot/util@13.5.8) + tslib: 2.8.1 + + '@polkadot/wasm-crypto@7.5.1(@polkadot/util@13.5.8)(@polkadot/x-randomvalues@13.5.5(@polkadot/util@13.5.8)(@polkadot/wasm-util@7.5.1(@polkadot/util@13.5.8)))': + dependencies: + '@polkadot/util': 13.5.8 + '@polkadot/wasm-bridge': 7.5.1(@polkadot/util@13.5.8)(@polkadot/x-randomvalues@13.5.5(@polkadot/util@13.5.8)(@polkadot/wasm-util@7.5.1(@polkadot/util@13.5.8))) + '@polkadot/wasm-crypto-asmjs': 7.5.1(@polkadot/util@13.5.8) + '@polkadot/wasm-crypto-init': 7.5.1(@polkadot/util@13.5.8)(@polkadot/x-randomvalues@13.5.5(@polkadot/util@13.5.8)(@polkadot/wasm-util@7.5.1(@polkadot/util@13.5.8))) + '@polkadot/wasm-crypto-wasm': 7.5.1(@polkadot/util@13.5.8) + '@polkadot/wasm-util': 7.5.1(@polkadot/util@13.5.8) + '@polkadot/x-randomvalues': 13.5.5(@polkadot/util@13.5.8)(@polkadot/wasm-util@7.5.1(@polkadot/util@13.5.8)) + tslib: 2.8.1 + + '@polkadot/wasm-util@7.5.1(@polkadot/util@13.5.8)': + dependencies: + '@polkadot/util': 13.5.8 + tslib: 2.8.1 + + '@polkadot/x-bigint@13.5.5': + dependencies: + '@polkadot/x-global': 13.5.5 + tslib: 2.8.1 + + '@polkadot/x-bigint@13.5.8': + dependencies: + '@polkadot/x-global': 13.5.8 + tslib: 2.8.1 + + '@polkadot/x-global@13.5.5': + dependencies: + tslib: 2.8.1 + + '@polkadot/x-global@13.5.8': + dependencies: + tslib: 2.8.1 + + '@polkadot/x-randomvalues@13.5.5(@polkadot/util@13.5.8)(@polkadot/wasm-util@7.5.1(@polkadot/util@13.5.8))': + dependencies: + '@polkadot/util': 13.5.8 + '@polkadot/wasm-util': 7.5.1(@polkadot/util@13.5.8) + '@polkadot/x-global': 13.5.5 + tslib: 2.8.1 + + '@polkadot/x-textdecoder@13.5.5': + dependencies: + '@polkadot/x-global': 13.5.5 + tslib: 2.8.1 + + '@polkadot/x-textdecoder@13.5.8': + dependencies: + '@polkadot/x-global': 13.5.8 + tslib: 2.8.1 + + '@polkadot/x-textencoder@13.5.5': + dependencies: + '@polkadot/x-global': 13.5.5 + tslib: 2.8.1 + + '@polkadot/x-textencoder@13.5.8': + dependencies: + '@polkadot/x-global': 13.5.8 + tslib: 2.8.1 + '@radix-ui/number@1.1.1': {} '@radix-ui/primitive@1.1.2': {} @@ -5917,6 +6231,17 @@ snapshots: '@scure/base@1.2.6': {} + '@scure/bip32@1.7.0': + dependencies: + '@noble/curves': 1.9.2 + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 + + '@scure/bip39@1.6.0': + dependencies: + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 + '@sec-ant/readable-stream@0.4.1': {} '@sindresorhus/merge-streams@4.0.0': {} @@ -5949,6 +6274,8 @@ snapshots: rxjs: 7.8.2 smoldot: 2.0.36 + '@substrate/ss58-registry@1.51.0': {} + '@swc/core-darwin-arm64@1.12.11': optional: true @@ -6075,6 +6402,10 @@ snapshots: '@total-typescript/tsconfig@1.0.4': {} + '@types/bn.js@5.2.0': + dependencies: + '@types/node': 24.0.15 + '@types/estree@1.0.8': {} '@types/json-schema@7.0.15': {} @@ -6337,6 +6668,10 @@ snapshots: '@zag-js/utils@0.48.0': {} + abitype@1.0.8(typescript@5.6.2): + optionalDependencies: + typescript: 5.6.2 + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -6517,6 +6852,8 @@ snapshots: balanced-match@1.0.2: {} + bn.js@5.2.2: {} + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -7146,6 +7483,8 @@ snapshots: esutils@2.0.3: {} + eventemitter3@5.0.1: {} + execa@9.6.0: dependencies: '@sindresorhus/merge-streams': 4.0.0 @@ -7511,6 +7850,10 @@ snapshots: isexe@2.0.0: {} + isows@1.0.7(ws@8.18.2): + dependencies: + ws: 8.18.2 + iterator.prototype@1.1.5: dependencies: define-data-property: 1.1.4 @@ -7811,6 +8154,21 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 + ox@0.7.1(typescript@5.6.2): + dependencies: + '@adraffy/ens-normalize': 1.11.1 + '@noble/ciphers': 1.3.0 + '@noble/curves': 1.9.2 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.0.8(typescript@5.6.2) + eventemitter3: 5.0.1 + optionalDependencies: + typescript: 5.6.2 + transitivePeerDependencies: + - zod + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -8559,6 +8917,23 @@ snapshots: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 + viem@2.31.0(typescript@5.6.2): + dependencies: + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.0.8(typescript@5.6.2) + isows: 1.0.7(ws@8.18.2) + ox: 0.7.1(typescript@5.6.2) + ws: 8.18.2 + optionalDependencies: + typescript: 5.6.2 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod + vite-tsconfig-paths@5.1.4(typescript@5.6.2)(vite@5.4.19(@types/node@24.0.15)(lightningcss@1.30.1)): dependencies: debug: 4.4.1 @@ -8669,6 +9044,8 @@ snapshots: type-fest: 4.41.0 write-json-file: 6.0.0 + ws@8.18.2: {} + ws@8.18.3: {} yallist@3.1.1: {} diff --git a/src/components/Header.tsx b/src/components/Header.tsx index b0437d0..b3eae3a 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -14,6 +14,8 @@ export const Header = () => { return "Address Book" case "nominations": return "Nominations" + case "applause": + return "Self Applause" default: return "Health Check"; } diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 86491a6..91807c4 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,4 +1,4 @@ -import { HeartPulse, SendToBack, Book, Users } from "lucide-react" +import { HeartPulse, SendToBack, Book, Users, Handshake } from "lucide-react" import { FaGithub } from "react-icons/fa" import { Link, useLocation } from "react-router-dom" import { useEffect } from "react" @@ -124,6 +124,12 @@ export const Sidebar = () => { + +
+ + +
+
diff --git a/src/containers/App.tsx b/src/containers/App.tsx index 6205c0c..bd137d3 100644 --- a/src/containers/App.tsx +++ b/src/containers/App.tsx @@ -13,6 +13,7 @@ const HealthCheck = lazy(() => import("./HealthCheck").then(module => ({ default const Transactions = lazy(() => import("./Transactions").then(module => ({ default: module.Transactions }))) const Nominations = lazy(() => import("./Nominations").then(module => ({ default: module.Nominations }))) const AddressBook = lazy(() => import("./AddressBook").then(module => ({ default: module.AddressBook }))) +const SelfApplause = lazy(() => import("./SelfApplause").then(module => ({ default: module.SelfApplause }))) export const App = () => { return ( @@ -29,6 +30,7 @@ export const App = () => { } /> } /> } /> + } /> } /> } /> diff --git a/src/containers/SelfApplause.tsx b/src/containers/SelfApplause.tsx new file mode 100644 index 0000000..537e8a9 --- /dev/null +++ b/src/containers/SelfApplause.tsx @@ -0,0 +1,287 @@ +import { useState, useEffect, useMemo } from "react" + +import { useSearchParams } from "react-router-dom" +import { Hand, BadgeAlert, BadgeCheck, ArrowBigRightDash } from "lucide-react" +import { u64, u128 } from "scale-ts" +import { keccak256 } from "viem" +import { decodeAddress } from "@polkadot/util-crypto" + +import { Row } from "./Row" +import { Sender } from "./Accounts" +import { Input } from "../components/ui/input" +import { Button } from "../components/ui/button" + +// http://localhost:5173/#/applause?networkId=11155111&sessionIndex=591&receiver=sfGvT6dSpR1Sodpu7XPy2oa4Pbu9UaKjJ6mnLP8JmdSktnde7&amount=0.010000000&transactionHash=0x29e80e04eef8db2eda43e36465fb3adda79b19eb22d695303686b64bd1055a21 + +import { + useChainSpecV1, + useSystemAccount, + useUnstableProvider, + useApplauseThreshold, + useCurrentIndex, + useAuthorities, + useClapsInSession, + useReceivedClaps, + useApplausesForTransaction, + useSelfApplauseCalldata, + useTransactionStatusProvider, + SessionAuthorityInfo +} from "../hooks" + +export const SelfApplause = () => { + const [searchParams] = useSearchParams() + + const [networkId, setNetworkId] = useState("") + const [sessionIndex, setSessionIndex] = useState("") + const [transactionHash, setTransactionHash] = useState("") + const [receiver, setReceiver] = useState("") + const [bridgedAmount, setBridgedAmount] = useState("") + + const { account, accounts, connectAccount } = useUnstableProvider() + const chainSpecV1 = useChainSpecV1() + + const tokenDecimals: number = chainSpecV1?.properties?.tokenDecimals ?? 0 + const tokenSymbol: string = chainSpecV1?.properties?.tokenSymbol ?? "" + const ss58Format: number = chainSpecV1?.properties?.ss58Format ?? 1995 + + const nextSessionIndex = useMemo(() => { + const number = +sessionIndex + return isNaN(number) ? undefined : number + 1 + }, [sessionIndex]) + + const amountConverted = useMemo(() => { + const amountNum = parseFloat(bridgedAmount); + if (isNaN(amountNum)) { + return 0n; + } + return BigInt(Math.floor(amountNum * Math.pow(10, tokenDecimals))) + }, [bridgedAmount, tokenDecimals]) + + const hashedArguments = useMemo(() => { + try { + const amountEncoded = u128.enc(amountConverted) + const networkIdEncoded = u64.enc(BigInt(networkId)) + const addressEncoded = decodeAddress(receiver, false, ss58Format) + + const clapArgsStr = new Uint8Array([ + ...addressEncoded, + ...amountEncoded, + ...networkIdEncoded + ]) + return keccak256(clapArgsStr) + } catch { + return undefined + } + }, [receiver, amountConverted, ss58Format, networkId]) + + const { + isSubmittingTransaction, + handleTransaction, + renderStatus, + } = useTransactionStatusProvider() + + const appluseThreshold = useApplauseThreshold() + const currentSession = useCurrentIndex() + + const authorities = useAuthorities({ currentSession: Number(sessionIndex) }) + const authoritiesNext = useAuthorities({ currentSession: nextSessionIndex }) + const clapsInSession = useClapsInSession({ + currentSession: Number(sessionIndex) + }) + const clapsInNextSession = useClapsInSession({ + currentSession: nextSessionIndex + }) + const receivedClaps = useReceivedClaps({ + currentSession: Number(sessionIndex), + txHash: transactionHash, + argsHash: hashedArguments + }) + const receivedClapsNext = useReceivedClaps({ + currentSession: nextSessionIndex, + txHash: transactionHash, + argsHash: hashedArguments + }) + const transactionApplaused = useApplausesForTransaction({ + currentSession: Number(sessionIndex), + txHash: transactionHash, + argsHash: hashedArguments + }) + + useEffect(() => { + setNetworkId(searchParams.get("networkId") ?? "") + setSessionIndex(searchParams.get("sessionIndex") ?? "") + setTransactionHash(searchParams.get("transactionHash") ?? "") + setReceiver(searchParams.get("receiver") ?? "") + setBridgedAmount(searchParams.get("amount") ?? "") + }, [searchParams]) + + const senderAccount = useSystemAccount({ + account: account + ? account.address + : undefined + }) + + const disabledInSession = useMemo(() => { + return clapsInSession?.filter((info: SessionAuthorityInfo) => info.disabled) ?? [] + }, [clapsInSession]) + + const totalDisabled = useMemo(() => { + let numberOfDisabled = clapsInSession?.filter((info: SessionAuthorityInfo) => info.disabled).length ?? 0 + clapsInNextSession?.forEach((info: SessionAuthorityInfo, index: number) => { + if (info.disabled) { + const authority = authoritiesNext?.at(index) + if (authority) { + const myIndex = authorities?.indexOf(authority) + if (myIndex) { + numberOfDisabled += 1 + } + } + } + }) + return numberOfDisabled + }, [clapsInSession, clapsInNextSession, authoritiesNext, authorities]) + + const totalReceivedClaps = useMemo(() => { + let totalClaps = receivedClaps?.length ?? 0 + for (const index of (receivedClapsNext ?? [])) { + const authority = authoritiesNext[index]; + if (authority) { + const myIndex = authorities?.indexOf(authority) + if (myIndex) { + totalClaps += 1 + } + } + } + return totalClaps + }, [authorities, receivedClaps, receivedClapsNext, authoritiesNext]) + + const applausePossible = useMemo(() => { + const value = totalReceivedClaps * 100 / (authorities?.length ?? 0) - totalDisabled + return value > appluseThreshold + }, [authorities, totalReceivedClaps, totalDisabled, appluseThreshold]) + + const loadedCorrectly = useMemo(() => { + return clapsInSession && authorities && disabledInSession + }, [clapsInSession, authorities, disabledInSession]) + + const applyDecimals = (value = 0n, decimals = 0, tokenSymbol = "CSPR") => { + if (!value) return `0 ${tokenSymbol}` + const numberValue = Number(value) / Math.pow(10, decimals) + const formatter = new Intl.NumberFormat("en-US", { + minimumFractionDigits: 6, + maximumFractionDigits: 6, + }) + return `${formatter.format(numberValue)} ${tokenSymbol}` + } + + const calldata = useSelfApplauseCalldata( + Number(networkId), + Number(sessionIndex), + transactionHash, + receiver, + amountConverted + ) + const handleOnSelfApplause = () => handleTransaction({ calldata: calldata, txName: "self_applause" }) + + return ( +
+
+ acc?.address ?? "") ?? []} + senderAccount={senderAccount} + senderBalance={applyDecimals(senderAccount?.data.free ?? 0n, tokenDecimals, tokenSymbol)} + tokenDecimals={tokenDecimals} + tokenSymbol={tokenSymbol} + connectAccount={connectAccount} + applyDecimals={applyDecimals} + /> + setNetworkId(e.target.value)} + value={networkId} + aria-label="NetworkId" + type="text" + className="sm:w-[300px] w-full" + placeholder={"0"} + />} /> + setSessionIndex(e.target.value)} + value={sessionIndex} + aria-label="SessionIndex" + type="text" + className="sm:w-[300px] w-full" + placeholder={"0"} + />} /> + setTransactionHash(e.target.value)} + value={transactionHash} + aria-label="TransactionHash" + type="text" + className="sm:w-[300px] w-full" + placeholder={"0x..."} + />} /> + setReceiver(e.target.value)} + value={receiver} + aria-label="ReceiverAddress" + type="text" + className="sm:w-[300px] w-full" + placeholder={"sf..."} + />} /> + setBridgedAmount(e.target.value)} + value={bridgedAmount} + aria-label="BridgedAmount" + type="text" + className="sm:w-[300px] w-full" + placeholder={"0"} + />} /> + {loadedCorrectly && +
+
+
+ Actual +
+ +
+ {clapsInSession.length} / {authorities.length - disabledInSession.length} +
+
+ +
+
+ Possible +
+ +
+ {totalReceivedClaps} / {authorities ? authorities.length - totalDisabled : 0} +
+
+
+ } + + {renderStatus()} +
+
+ ) +} diff --git a/src/hooks/index.ts b/src/hooks/index.ts index a6b21a6..55f9a09 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -18,3 +18,8 @@ export * from "./useNominations" export * from "./useLedger" export * from "./usePayee" export * from "./useSlasingSpans" +export * from "./useApplausesForTransaction" +export * from "./useAuthorities" +export * from "./useClapsInSession" +export * from "./useReceivedClaps" +export * from "./useCurrentIndex" diff --git a/src/hooks/useApplausesForTransaction.tsx b/src/hooks/useApplausesForTransaction.tsx new file mode 100644 index 0000000..e982520 --- /dev/null +++ b/src/hooks/useApplausesForTransaction.tsx @@ -0,0 +1,54 @@ +import useSWRSubscription from "swr/subscription" +import { getDynamicBuilder, getLookupFn } from "@polkadot-api/metadata-builders" +import type { BlockInfo } from "@polkadot-api/observable-client" +import { distinct, filter, map, mergeMap } from "rxjs" +import { fromHex } from "@polkadot-api/utils"; + +import { useUnstableProvider } from "./useUnstableProvider" +import { useMetadata } from "./useMetadata" + +interface ApplausesForTransactionInterface { + currentSession?: Number; + txHash?: string; + argsHash?: string; +} + +export const useApplausesForTransaction = ({ currentSession, txHash, argsHash }: ApplausesForTransactionInterface) => { + const { chainHead$, chainId } = useUnstableProvider() + const metadata = useMetadata() + const { data: applausesForTransaction } = useSWRSubscription( + chainHead$ && txHash && argsHash && currentSession && chainId && metadata + ? ["applausesForTransaction", chainHead$, txHash, argsHash, currentSession, chainId, metadata] + : null, + ([_, chainHead$, txHash, argsHash, currentSession, chainId, metadata], { next }) => { + const { finalized$, storage$ } = chainHead$ + const subscription = finalized$.pipe( + filter(Boolean), + mergeMap((blockInfo: BlockInfo) => { + const builder = getDynamicBuilder(getLookupFn(metadata)) + const applausesForTransaction = builder.buildStorage("GhostSlowClaps", "ApplausesForTransaction") + + return storage$(blockInfo?.hash, "value", () => + applausesForTransaction?.keys.enc( + currentSession, + { asBytes: () => fromHex(txHash) }, + { asBytes: () => fromHex(argsHash) }, + ) + ).pipe( + filter(Boolean), + distinct(), + map((value: string) => applausesForTransaction?.value.dec(value) as boolean) + ) + }), + ) + .subscribe({ + next(applausesForTransaction: boolean) { + next(null, applausesForTransaction) + }, + error: next, + }) + return () => subscription.unsubscribe() + } + ) + return applausesForTransaction +} diff --git a/src/hooks/useAuthorities.tsx b/src/hooks/useAuthorities.tsx new file mode 100644 index 0000000..6fa7430 --- /dev/null +++ b/src/hooks/useAuthorities.tsx @@ -0,0 +1,46 @@ +import useSWRSubscription from "swr/subscription" +import { getDynamicBuilder, getLookupFn } from "@polkadot-api/metadata-builders" +import type { BlockInfo } from "@polkadot-api/observable-client" +import { distinct, filter, map, mergeMap } from "rxjs" + +import { useUnstableProvider } from "./useUnstableProvider" +import { useMetadata } from "./useMetadata" + +interface AuthoritiesInterface { + currentSession?: Number; +} + +export const useAuthorities = ({ currentSession }: AuthoritiesInterface) => { + const { chainHead$, chainId } = useUnstableProvider() + const metadata = useMetadata() + const { data: slowClapAuthorities } = useSWRSubscription( + chainHead$ && chainId && metadata + ? ["slowClapAuthorities", chainHead$, currentSession, chainId, metadata] + : null, + ([_, chainHead$, currentSession, chainId, metadata], { next }) => { + const { finalized$, storage$ } = chainHead$ + const subscription = finalized$.pipe( + filter(Boolean), + mergeMap((blockInfo: BlockInfo) => { + const builder = getDynamicBuilder(getLookupFn(metadata)) + const slowClapAuthorities = builder.buildStorage("GhostSlowClaps", "Authorities") + return storage$(blockInfo?.hash, "value", () => + slowClapAuthorities?.keys.enc(currentSession) + ).pipe( + filter(Boolean), + distinct(), + map((value: string) => slowClapAuthorities?.value.dec(value) as any) + ) + }), + ) + .subscribe({ + next(slowClapAuthorities: any) { + next(null, slowClapAuthorities) + }, + error: next, + }) + return () => subscription.unsubscribe() + } + ) + return slowClapAuthorities?.map((authority: any) => authority?.asHex()) +} diff --git a/src/hooks/useCalldata.tsx b/src/hooks/useCalldata.tsx index aaf5c7c..01c95cd 100644 --- a/src/hooks/useCalldata.tsx +++ b/src/hooks/useCalldata.tsx @@ -1,8 +1,8 @@ import useSWR from "swr" import { getDynamicBuilder, getLookupFn } from "@polkadot-api/metadata-builders" -import { mergeUint8, toHex } from "@polkadot-api/utils" -import { type SS58String, Enum } from "@polkadot-api/substrate-bindings" +import { mergeUint8, toHex, fromHex } from "@polkadot-api/utils" +import { type SS58String, Binary, Enum } from "@polkadot-api/substrate-bindings" import { useUnstableProvider } from "./useUnstableProvider" import { useMetadata } from "./useMetadata" @@ -56,7 +56,7 @@ export const useTransferCalldata = (destination: SS58String | undefined, amount: const metadata = useMetadata() const { data: calldata } = useSWR( client && chainId && destination && amount && metadata - ? ["metadata", client, chainId, metadata, destination, amount] + ? ["transfer_allow_death", client, chainId, metadata, destination, amount] : null, ([_, client, _chainId, metadata, destination, amount]) => { const builder = getDynamicBuilder(getLookupFn(metadata)) @@ -227,3 +227,39 @@ export const usePayeeCalldata = (expectedPayee: string | undefined, destinationR ) return calldata } + +export const useSelfApplauseCalldata = ( + networkId?: Number, + sessionIndex?: Number, + transactionHash?: string, + receiver?: string, + amount?: bigint +) => { + const { client, chainId } = useUnstableProvider() + const metadata = useMetadata() + + const { data: calldata } = useSWR( + client && chainId && networkId && sessionIndex && transactionHash && receiver && amount && metadata + ? ["self_applause", client, chainId, metadata, networkId, sessionIndex, transactionHash, receiver, amount] + : null, + ([_, client, _chainId, metadata, networkId, sessionIndex, transactionHash, receiver, amount]) => { + const builder = getDynamicBuilder(getLookupFn(metadata)) + const { codec, location } = builder.buildCall("GhostSlowClaps", "self_applause") + + const txHash = new Binary(fromHex(transactionHash)) + return toHex( + mergeUint8( + new Uint8Array(location), + codec.enc({ + network_id: BigInt(networkId?.toString() ?? "0"), + prev_session_index: sessionIndex, + transaction_hash: txHash, + receiver: receiver, + amount: amount, + }), + ) + ) + } + ) + return calldata +} diff --git a/src/hooks/useClapsInSession.tsx b/src/hooks/useClapsInSession.tsx new file mode 100644 index 0000000..9f4b7aa --- /dev/null +++ b/src/hooks/useClapsInSession.tsx @@ -0,0 +1,51 @@ +import useSWRSubscription from "swr/subscription" +import { getDynamicBuilder, getLookupFn } from "@polkadot-api/metadata-builders" +import type { BlockInfo } from "@polkadot-api/observable-client" +import { distinct, filter, map, mergeMap } from "rxjs" + +import { useUnstableProvider } from "./useUnstableProvider" +import { useMetadata } from "./useMetadata" + +export type SessionAuthorityInfo = { + claps: number + disabled: boolean +} + +interface ClapsInSessionInterface { + currentSession?: Number; +} + +export const useClapsInSession = ({ currentSession }: ClapsInSessionInterface) => { + const { chainHead$, chainId } = useUnstableProvider() + const metadata = useMetadata() + const { data: clapsInSession } = useSWRSubscription( + chainHead$ && chainId && metadata + ? ["clapsInSession", chainHead$, currentSession, chainId, metadata] + : null, + ([_, chainHead$, currentSession, chainId, metadata], { next }) => { + const { finalized$, storage$ } = chainHead$ + const subscription = finalized$.pipe( + filter(Boolean), + mergeMap((blockInfo: BlockInfo) => { + const builder = getDynamicBuilder(getLookupFn(metadata)) + const clapsInSession = builder.buildStorage("GhostSlowClaps", "ClapsInSession") + return storage$(blockInfo?.hash, "value", () => + clapsInSession?.keys.enc(currentSession) + ).pipe( + filter(Boolean), + distinct(), + map((value: string) => clapsInSession?.value.dec(value) as SessionAuthorityInfo) + ) + }), + ) + .subscribe({ + next(clapsInSession: SessionAuthorityInfo) { + next(null, clapsInSession) + }, + error: next, + }) + return () => subscription.unsubscribe() + } + ) + return clapsInSession +} diff --git a/src/hooks/useConstants.tsx b/src/hooks/useConstants.tsx index a9a3ff5..3b24975 100644 --- a/src/hooks/useConstants.tsx +++ b/src/hooks/useConstants.tsx @@ -21,3 +21,21 @@ export const useExistentialDeposit = () => { ) return existentialDeposit } + +export const useApplauseThreshold = () => { + const { chainId } = useUnstableProvider() + const metadata = useMetadata() + const { data: existentialDeposit } = useSWR( + chainId && metadata + ? ["ApplauseThreshold", chainId, metadata] + : null, + ([_, chainId, metadata]) => { + const builder = getDynamicBuilder(getLookupFn(metadata)) + const codec = builder.buildConstant("GhostSlowClaps", "ApplauseThreshold") + const constants = metadata?.pallets?.find(obj => obj.name === "GhostSlowClaps")?.constants + const value = constants?.find(obj => obj.name === "ApplauseThreshold")?.value + return value ? codec.dec(value) : undefined + } + ) + return existentialDeposit +} diff --git a/src/hooks/useCurrentIndex.tsx b/src/hooks/useCurrentIndex.tsx new file mode 100644 index 0000000..42ff963 --- /dev/null +++ b/src/hooks/useCurrentIndex.tsx @@ -0,0 +1,42 @@ +import useSWRSubscription from "swr/subscription" +import { getDynamicBuilder, getLookupFn } from "@polkadot-api/metadata-builders" +import type { BlockInfo } from "@polkadot-api/observable-client" +import { distinct, filter, map, mergeMap } from "rxjs" + +import { useUnstableProvider } from "./useUnstableProvider" +import { useMetadata } from "./useMetadata" + +export const useCurrentIndex = () => { + const { chainHead$, chainId } = useUnstableProvider() + const metadata = useMetadata() + const { data: currentIndex } = useSWRSubscription( + chainHead$ && chainId && metadata + ? ["currentIndex", chainHead$, chainId, metadata] + : null, + ([_, chainHead$, chainId, metadata], { next }) => { + const { finalized$, storage$ } = chainHead$ + const subscription = finalized$.pipe( + filter(Boolean), + mergeMap((blockInfo: BlockInfo) => { + const builder = getDynamicBuilder(getLookupFn(metadata)) + const currentIndex = builder.buildStorage("Session", "CurrentIndex") + return storage$(blockInfo?.hash, "value", () => + currentIndex?.keys.enc() + ).pipe( + filter(Boolean), + distinct(), + map((value: string) => currentIndex?.value.dec(value) as Number) + ) + }), + ) + .subscribe({ + next(currentIndex: Number) { + next(null, currentIndex) + }, + error: next, + }) + return () => subscription.unsubscribe() + } + ) + return currentIndex +} diff --git a/src/hooks/useReceivedClaps.tsx b/src/hooks/useReceivedClaps.tsx new file mode 100644 index 0000000..4cd0300 --- /dev/null +++ b/src/hooks/useReceivedClaps.tsx @@ -0,0 +1,54 @@ +import useSWRSubscription from "swr/subscription" +import { getDynamicBuilder, getLookupFn } from "@polkadot-api/metadata-builders" +import type { BlockInfo } from "@polkadot-api/observable-client" +import { distinct, filter, map, mergeMap } from "rxjs" +import { fromHex } from "@polkadot-api/utils"; + +import { useUnstableProvider } from "./useUnstableProvider" +import { useMetadata } from "./useMetadata" + +interface ReceivedClapsInterface { + currentSession?: Number; + txHash?: string; + argsHash?: string; +} + +export const useReceivedClaps = ({ currentSession, txHash, argsHash }: ReceivedClapsInterface) => { + const { chainHead$, chainId } = useUnstableProvider() + const metadata = useMetadata() + const { data: receivedClaps } = useSWRSubscription( + chainHead$ && txHash && argsHash && currentSession && chainId && metadata + ? ["receivedClaps", chainHead$, txHash, argsHash, currentSession, chainId, metadata] + : null, + ([_, chainHead$, txHash, argsHash, currentSession, chainId, metadata], { next }) => { + const { finalized$, storage$ } = chainHead$ + const subscription = finalized$.pipe( + filter(Boolean), + mergeMap((blockInfo: BlockInfo) => { + const builder = getDynamicBuilder(getLookupFn(metadata)) + const receivedClaps = builder.buildStorage("GhostSlowClaps", "ReceivedClaps") + + return storage$(blockInfo?.hash, "value", () => + receivedClaps?.keys.enc( + currentSession, + { asBytes: () => fromHex(txHash) }, + { asBytes: () => fromHex(argsHash) }, + ) + ).pipe( + filter(Boolean), + distinct(), + map((value: string) => receivedClaps?.value.dec(value) as Number[]) + ) + }), + ) + .subscribe({ + next(receivedClaps: Number[]) { + next(null, receivedClaps) + }, + error: next, + }) + return () => subscription.unsubscribe() + } + ) + return receivedClaps +}