diff --git a/package.json b/package.json
index bd7fb17..91ccdd3 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "ghost-dao-interface",
"private": true,
- "version": "0.2.13",
+ "version": "0.2.14",
"type": "module",
"scripts": {
"dev": "vite",
@@ -18,8 +18,14 @@
"@mui/icons-material": "^6.4.7",
"@mui/material": "^6.4.7",
"@mui/utils": "^6.4.6",
+ "@polkadot-api/metadata-builders": "0.13.0",
+ "@polkadot-api/observable-client": "~0.8.6",
+ "@polkadot-api/substrate-bindings": "0.15.0",
+ "@polkadot-api/substrate-client": "~0.3.0",
"@polkadot-api/utils": "~0.1.2",
"@polkadot-labs/hdkd-helpers": "^0.0.20",
+ "@polkadot/util-crypto": "^13.5.5",
+ "@substrate/connect-discovery": "^0.2.2",
"@tanstack/react-query": "^5.67.2",
"@tanstack/react-query-devtools": "^5.67.2",
"@wagmi/core": "^2.17.3",
@@ -29,7 +35,10 @@
"react-helmet": "^6.1.0",
"react-hot-toast": "^2.5.2",
"react-router-dom": "^7.3.0",
+ "rxjs": "7.8.1",
+ "scale-ts": "^1.6.1",
"scss": "^0.2.4",
+ "swr": "2.2.5",
"viem": "^2.31.0",
"wagmi": "^2.15.6"
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d27005f..183862c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -32,12 +32,30 @@ importers:
'@mui/utils':
specifier: ^6.4.6
version: 6.4.6(@types/react@19.0.10)(react@19.0.0)
+ '@polkadot-api/metadata-builders':
+ specifier: 0.13.0
+ version: 0.13.0
+ '@polkadot-api/observable-client':
+ specifier: ~0.8.6
+ version: 0.8.6(@polkadot-api/substrate-client@0.3.0)(rxjs@7.8.1)
+ '@polkadot-api/substrate-bindings':
+ specifier: 0.15.0
+ version: 0.15.0
+ '@polkadot-api/substrate-client':
+ specifier: ~0.3.0
+ version: 0.3.0
'@polkadot-api/utils':
- specifier: 0.1.2
+ specifier: ~0.1.2
version: 0.1.2
'@polkadot-labs/hdkd-helpers':
specifier: ^0.0.20
version: 0.0.20
+ '@polkadot/util-crypto':
+ specifier: ^13.5.5
+ version: 13.5.5(@polkadot/util@13.5.5)
+ '@substrate/connect-discovery':
+ specifier: ^0.2.2
+ version: 0.2.2
'@tanstack/react-query':
specifier: ^5.67.2
version: 5.67.2(react@19.0.0)
@@ -65,9 +83,18 @@ importers:
react-router-dom:
specifier: ^7.3.0
version: 7.3.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ rxjs:
+ specifier: 7.8.1
+ version: 7.8.1
+ scale-ts:
+ specifier: ^1.6.1
+ version: 1.6.1
scss:
specifier: ^0.2.4
version: 0.2.4
+ swr:
+ specifier: 2.2.5
+ version: 2.2.5(react@19.0.0)
viem:
specifier: ^2.31.0
version: 2.31.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)(zod@3.22.4)
@@ -725,10 +752,6 @@ packages:
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}
-
'@noble/curves@1.9.6':
resolution: {integrity: sha512-GIKz/j99FRthB8icyJQA51E8Uk5hXmdyThjgQXRKiv9h0zeRlzSCLIzFw6K1LotZ3XuB7yzlf76qk7uBmTdFqA==}
engines: {node: ^14.21.3 || >=16}
@@ -753,12 +776,115 @@ packages:
resolution: {integrity: sha512-IHnV6A+zxU7XwmKFinmYjUcwlyK9+xkG3/s9KcQhI9BjQKycrJ1JRO+FbNYPwZiPKW3je/DR0k7w8/gLa5eaxQ==}
deprecated: 'The package is now available as "qr": npm install qr'
+ '@polkadot-api/json-rpc-provider@0.0.4':
+ resolution: {integrity: sha512-9cDijLIxzHOBuq6yHqpqjJ9jBmXrctjc1OFqU+tQrS96adQze3mTIH6DTgfb/0LMrqxzxffz1HQGrIlEH00WrA==}
+
+ '@polkadot-api/metadata-builders@0.10.2':
+ resolution: {integrity: sha512-rtdihBFd25oT9/71Q+EOR9q6E6mCl1pPe/2He/LtlY0TyHiYqO2KpMZNXkoGcw1RHvrV+CAtDFMvK1j3n8aW8w==}
+
+ '@polkadot-api/metadata-builders@0.13.0':
+ resolution: {integrity: sha512-z8AT0mbOniHmiI0sNjQSUtOXGLHTqFNV8bqdyes1WAMbpBEIcnTlQxhIrJkpPEQCh7kLNTHqa3OQjSIrOH7X1Q==}
+
+ '@polkadot-api/observable-client@0.8.6':
+ resolution: {integrity: sha512-ci5HC8TYjGxoTG/QM+LLuGrfIsn+dtR7BBQz483c/ML8K/Hxl9v+evgZzPi9xNMwZ25mytn9lhA5dovYSEauSA==}
+ peerDependencies:
+ '@polkadot-api/substrate-client': 0.3.0
+ rxjs: '>=7.8.0'
+
+ '@polkadot-api/substrate-bindings@0.11.1':
+ resolution: {integrity: sha512-+oqAZB7y18KrP/DqKmU2P3nNmRzjCY7edtW7tyA1g1jPouF7HhRr/Q13lJseDX9sdE2FZGrKZtivzsw8XeXBng==}
+
+ '@polkadot-api/substrate-bindings@0.15.0':
+ resolution: {integrity: sha512-w/GSQeEYfFqyM50s2c3KDfl/qumtLnk7EUALXER/eFWSawXYVeL3PCg95GZfDFrc7MODVmq+XeXUoZBFGHvodA==}
+
+ '@polkadot-api/substrate-client@0.3.0':
+ resolution: {integrity: sha512-0hEvQLKH2zhaFzE8DPkWehvJilec8u2O2wbIEUStm0OJ8jIFtJ40MFjXQfB01dXBWUz1KaVBqS6xd3sZA90Dpw==}
+
'@polkadot-api/utils@0.1.2':
resolution: {integrity: sha512-yhs5k2a8N1SBJcz7EthZoazzLQUkZxbf+0271Xzu42C5AEM9K9uFLbsB+ojzHEM72O5X8lPtSwGKNmS7WQyDyg==}
+ '@polkadot-api/utils@0.2.0':
+ resolution: {integrity: sha512-nY3i5fQJoAxU4n3bD7Fs208/KR2J95SGfVc58kDjbRYN5a84kWaGEqzjBNtP9oqht49POM8Bm9mbIrkvC1Bzuw==}
+
'@polkadot-labs/hdkd-helpers@0.0.20':
resolution: {integrity: sha512-P3o1FpPqLACaHhDT/J6O3xYQIBdOs0FDJtZQI8/LGotgIGp85mKDnH/cSSK3QC2i67ZY/d/POs8K0jEspLMiGg==}
+ '@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/wasm-bridge@7.4.1':
+ resolution: {integrity: sha512-tdkJaV453tezBxhF39r4oeG0A39sPKGDJmN81LYLf+Fihb7astzwju+u75BRmDrHZjZIv00un3razJEWCxze6g==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@polkadot/util': '*'
+ '@polkadot/x-randomvalues': '*'
+
+ '@polkadot/wasm-crypto-asmjs@7.4.1':
+ resolution: {integrity: sha512-pwU8QXhUW7IberyHJIQr37IhbB6DPkCG5FhozCiNTq4vFBsFPjm9q8aZh7oX1QHQaiAZa2m2/VjIVE+FHGbvHQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@polkadot/util': '*'
+
+ '@polkadot/wasm-crypto-init@7.4.1':
+ resolution: {integrity: sha512-AVka33+f7MvXEEIGq5U0dhaA2SaXMXnxVCQyhJTaCnJ5bRDj0Xlm3ijwDEQUiaDql7EikbkkRtmlvs95eSUWYQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@polkadot/util': '*'
+ '@polkadot/x-randomvalues': '*'
+
+ '@polkadot/wasm-crypto-wasm@7.4.1':
+ resolution: {integrity: sha512-PE1OAoupFR0ZOV2O8tr7D1FEUAwaggzxtfs3Aa5gr+yxlSOaWUKeqsOYe1KdrcjmZVV3iINEAXxgrbzCmiuONg==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@polkadot/util': '*'
+
+ '@polkadot/wasm-crypto@7.4.1':
+ resolution: {integrity: sha512-kHN/kF7hYxm1y0WeFLWeWir6oTzvcFmR4N8fJJokR+ajYbdmrafPN+6iLgQVbhZnDdxyv9jWDuRRsDnBx8tPMQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@polkadot/util': '*'
+ '@polkadot/x-randomvalues': '*'
+
+ '@polkadot/wasm-util@7.4.1':
+ resolution: {integrity: sha512-RAcxNFf3zzpkr+LX/ItAsvj+QyM56TomJ0xjUMo4wKkHjwsxkz4dWJtx5knIgQz/OthqSDMR59VNEycQeNuXzA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@polkadot/util': '*'
+
+ '@polkadot/x-bigint@13.5.5':
+ resolution: {integrity: sha512-SAd7Lfdgp6mz+utkoML8MN9FqTMCuPfk7v5rLJnm9vHgXw5uYnycbjH5Uc7ZgQIQWtMXJV3thrlltMan5DUXtA==}
+ engines: {node: '>=18'}
+
+ '@polkadot/x-global@13.5.5':
+ resolution: {integrity: sha512-fw+VM191bodacSeieMm8Vmrym4jjevX08IINDcQTd1gIOjtE5CriJhwfBbAF4WnlTp/11jhhbX4/SvWMubXAzQ==}
+ 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-textencoder@13.5.5':
+ resolution: {integrity: sha512-yEgUUojBb4goYf4V5I7urdJ+W+1aI13U1kZmUwMc+/G2YQz8pX3s/Tyb/iuxU5MlFh0AZZXP5NqUnFol+vwNEg==}
+ engines: {node: '>=18'}
+
'@popperjs/core@2.11.8':
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
@@ -935,6 +1061,15 @@ packages:
'@socket.io/component-emitter@3.1.2':
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
+ '@substrate/connect-discovery@0.2.2':
+ resolution: {integrity: sha512-IaW1VnSiNKE6ITzMaWu6kph52jIpSswegV4S6id/XnVpC5gqp/iBidmMy88REecatyktPZKBCwfyFxRDHeiijQ==}
+
+ '@substrate/discovery@0.2.2':
+ resolution: {integrity: sha512-vD5B1LqIaiDBWpJ7h1b89jzhU0AKjOySFAt2zbTDz+gpRdcOn0I8PoxhOrent4LXFX9O/ch8bGG/JYDtr2B2QA==}
+
+ '@substrate/ss58-registry@1.51.0':
+ resolution: {integrity: sha512-TWDurLiPxndFgKjVavCniytBIw+t4ViOi7TYp9h/D0NMmkEc9klFTo+827eyEJ0lELpqO207Ey7uGxUa+BS1jQ==}
+
'@svgr/babel-plugin-add-jsx-attribute@8.0.0':
resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==}
engines: {node: '>=14'}
@@ -1032,6 +1167,9 @@ packages:
'@types/babel__traverse@7.20.6':
resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==}
+ '@types/bn.js@5.2.0':
+ resolution: {integrity: sha512-DLbJ1BPqxvQhIGbeu8VbUC1DiAiahHtAYvA0ZEAa4P31F7IaArc8z3C3BRQdWX4mtLQuABG4yzp76ZrS02Ui1Q==}
+
'@types/cookie@0.6.0':
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
@@ -1319,6 +1457,9 @@ packages:
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
engines: {node: '>= 14.16.0'}
+ client-only@0.0.1:
+ resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
+
cliui@6.0.0:
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
@@ -2252,8 +2393,8 @@ packages:
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
- rxjs@7.8.2:
- resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
+ rxjs@7.8.1:
+ resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
safe-buffer@5.1.2:
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
@@ -2512,6 +2653,11 @@ packages:
svg-parser@2.0.4:
resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==}
+ swr@2.2.5:
+ resolution: {integrity: sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==}
+ peerDependencies:
+ react: ^16.11.0 || ^17.0.0 || ^18.0.0
+
sync-child-process@1.0.2:
resolution: {integrity: sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==}
engines: {node: '>=16.0.0'}
@@ -3574,10 +3720,6 @@ snapshots:
dependencies:
'@noble/hashes': 1.8.0
- '@noble/curves@1.9.2':
- dependencies:
- '@noble/hashes': 1.8.0
-
'@noble/curves@1.9.6':
dependencies:
'@noble/hashes': 1.8.0
@@ -3592,8 +3734,49 @@ snapshots:
'@paulmillr/qr@0.2.1': {}
+ '@polkadot-api/json-rpc-provider@0.0.4': {}
+
+ '@polkadot-api/metadata-builders@0.10.2':
+ dependencies:
+ '@polkadot-api/substrate-bindings': 0.11.1
+ '@polkadot-api/utils': 0.1.2
+
+ '@polkadot-api/metadata-builders@0.13.0':
+ dependencies:
+ '@polkadot-api/substrate-bindings': 0.15.0
+ '@polkadot-api/utils': 0.2.0
+
+ '@polkadot-api/observable-client@0.8.6(@polkadot-api/substrate-client@0.3.0)(rxjs@7.8.1)':
+ dependencies:
+ '@polkadot-api/metadata-builders': 0.10.2
+ '@polkadot-api/substrate-bindings': 0.11.1
+ '@polkadot-api/substrate-client': 0.3.0
+ '@polkadot-api/utils': 0.1.2
+ rxjs: 7.8.1
+
+ '@polkadot-api/substrate-bindings@0.11.1':
+ dependencies:
+ '@noble/hashes': 1.8.0
+ '@polkadot-api/utils': 0.1.2
+ '@scure/base': 1.2.6
+ scale-ts: 1.6.1
+
+ '@polkadot-api/substrate-bindings@0.15.0':
+ dependencies:
+ '@noble/hashes': 1.8.0
+ '@polkadot-api/utils': 0.2.0
+ '@scure/base': 1.2.6
+ scale-ts: 1.6.1
+
+ '@polkadot-api/substrate-client@0.3.0':
+ dependencies:
+ '@polkadot-api/json-rpc-provider': 0.0.4
+ '@polkadot-api/utils': 0.1.2
+
'@polkadot-api/utils@0.1.2': {}
+ '@polkadot-api/utils@0.2.0': {}
+
'@polkadot-labs/hdkd-helpers@0.0.20':
dependencies:
'@noble/curves': 1.9.6
@@ -3602,6 +3785,105 @@ snapshots:
'@scure/sr25519': 0.2.0
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.5)':
+ dependencies:
+ '@noble/curves': 1.9.6
+ '@noble/hashes': 1.8.0
+ '@polkadot/networks': 13.5.5
+ '@polkadot/util': 13.5.5
+ '@polkadot/wasm-crypto': 7.4.1(@polkadot/util@13.5.5)(@polkadot/x-randomvalues@13.5.5(@polkadot/util@13.5.5)(@polkadot/wasm-util@7.4.1(@polkadot/util@13.5.5)))
+ '@polkadot/wasm-util': 7.4.1(@polkadot/util@13.5.5)
+ '@polkadot/x-bigint': 13.5.5
+ '@polkadot/x-randomvalues': 13.5.5(@polkadot/util@13.5.5)(@polkadot/wasm-util@7.4.1(@polkadot/util@13.5.5))
+ '@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/wasm-bridge@7.4.1(@polkadot/util@13.5.5)(@polkadot/x-randomvalues@13.5.5(@polkadot/util@13.5.5)(@polkadot/wasm-util@7.4.1(@polkadot/util@13.5.5)))':
+ dependencies:
+ '@polkadot/util': 13.5.5
+ '@polkadot/wasm-util': 7.4.1(@polkadot/util@13.5.5)
+ '@polkadot/x-randomvalues': 13.5.5(@polkadot/util@13.5.5)(@polkadot/wasm-util@7.4.1(@polkadot/util@13.5.5))
+ tslib: 2.8.1
+
+ '@polkadot/wasm-crypto-asmjs@7.4.1(@polkadot/util@13.5.5)':
+ dependencies:
+ '@polkadot/util': 13.5.5
+ tslib: 2.8.1
+
+ '@polkadot/wasm-crypto-init@7.4.1(@polkadot/util@13.5.5)(@polkadot/x-randomvalues@13.5.5(@polkadot/util@13.5.5)(@polkadot/wasm-util@7.4.1(@polkadot/util@13.5.5)))':
+ dependencies:
+ '@polkadot/util': 13.5.5
+ '@polkadot/wasm-bridge': 7.4.1(@polkadot/util@13.5.5)(@polkadot/x-randomvalues@13.5.5(@polkadot/util@13.5.5)(@polkadot/wasm-util@7.4.1(@polkadot/util@13.5.5)))
+ '@polkadot/wasm-crypto-asmjs': 7.4.1(@polkadot/util@13.5.5)
+ '@polkadot/wasm-crypto-wasm': 7.4.1(@polkadot/util@13.5.5)
+ '@polkadot/wasm-util': 7.4.1(@polkadot/util@13.5.5)
+ '@polkadot/x-randomvalues': 13.5.5(@polkadot/util@13.5.5)(@polkadot/wasm-util@7.4.1(@polkadot/util@13.5.5))
+ tslib: 2.8.1
+
+ '@polkadot/wasm-crypto-wasm@7.4.1(@polkadot/util@13.5.5)':
+ dependencies:
+ '@polkadot/util': 13.5.5
+ '@polkadot/wasm-util': 7.4.1(@polkadot/util@13.5.5)
+ tslib: 2.8.1
+
+ '@polkadot/wasm-crypto@7.4.1(@polkadot/util@13.5.5)(@polkadot/x-randomvalues@13.5.5(@polkadot/util@13.5.5)(@polkadot/wasm-util@7.4.1(@polkadot/util@13.5.5)))':
+ dependencies:
+ '@polkadot/util': 13.5.5
+ '@polkadot/wasm-bridge': 7.4.1(@polkadot/util@13.5.5)(@polkadot/x-randomvalues@13.5.5(@polkadot/util@13.5.5)(@polkadot/wasm-util@7.4.1(@polkadot/util@13.5.5)))
+ '@polkadot/wasm-crypto-asmjs': 7.4.1(@polkadot/util@13.5.5)
+ '@polkadot/wasm-crypto-init': 7.4.1(@polkadot/util@13.5.5)(@polkadot/x-randomvalues@13.5.5(@polkadot/util@13.5.5)(@polkadot/wasm-util@7.4.1(@polkadot/util@13.5.5)))
+ '@polkadot/wasm-crypto-wasm': 7.4.1(@polkadot/util@13.5.5)
+ '@polkadot/wasm-util': 7.4.1(@polkadot/util@13.5.5)
+ '@polkadot/x-randomvalues': 13.5.5(@polkadot/util@13.5.5)(@polkadot/wasm-util@7.4.1(@polkadot/util@13.5.5))
+ tslib: 2.8.1
+
+ '@polkadot/wasm-util@7.4.1(@polkadot/util@13.5.5)':
+ dependencies:
+ '@polkadot/util': 13.5.5
+ tslib: 2.8.1
+
+ '@polkadot/x-bigint@13.5.5':
+ dependencies:
+ '@polkadot/x-global': 13.5.5
+ tslib: 2.8.1
+
+ '@polkadot/x-global@13.5.5':
+ dependencies:
+ tslib: 2.8.1
+
+ '@polkadot/x-randomvalues@13.5.5(@polkadot/util@13.5.5)(@polkadot/wasm-util@7.4.1(@polkadot/util@13.5.5))':
+ dependencies:
+ '@polkadot/util': 13.5.5
+ '@polkadot/wasm-util': 7.4.1(@polkadot/util@13.5.5)
+ '@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-textencoder@13.5.5':
+ dependencies:
+ '@polkadot/x-global': 13.5.5
+ tslib: 2.8.1
+
'@popperjs/core@2.11.8': {}
'@reown/appkit-common@1.7.8(bufferutil@4.0.9)(utf-8-validate@5.0.10)(zod@3.22.4)':
@@ -3979,6 +4261,14 @@ snapshots:
'@socket.io/component-emitter@3.1.2': {}
+ '@substrate/connect-discovery@0.2.2':
+ dependencies:
+ '@substrate/discovery': 0.2.2
+
+ '@substrate/discovery@0.2.2': {}
+
+ '@substrate/ss58-registry@1.51.0': {}
+
'@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.26.9)':
dependencies:
'@babel/core': 7.26.9
@@ -4085,6 +4375,10 @@ snapshots:
dependencies:
'@babel/types': 7.26.9
+ '@types/bn.js@5.2.0':
+ dependencies:
+ '@types/node': 22.7.5
+
'@types/cookie@0.6.0': {}
'@types/debug@4.1.12':
@@ -4100,7 +4394,6 @@ snapshots:
'@types/node@22.7.5':
dependencies:
undici-types: 6.19.8
- optional: true
'@types/parse-json@4.0.2': {}
@@ -4828,6 +5121,8 @@ snapshots:
dependencies:
readdirp: 4.1.2
+ client-only@0.0.1: {}
+
cliui@6.0.0:
dependencies:
string-width: 4.2.3
@@ -4968,7 +5263,7 @@ snapshots:
dependencies:
'@ecies/ciphers': 0.2.3(@noble/ciphers@1.3.0)
'@noble/ciphers': 1.3.0
- '@noble/curves': 1.9.2
+ '@noble/curves': 1.9.6
'@noble/hashes': 1.8.0
electron-to-chromium@1.5.113: {}
@@ -5512,7 +5807,7 @@ snapshots:
ox@0.6.7(zod@3.22.4):
dependencies:
'@adraffy/ens-normalize': 1.11.0
- '@noble/curves': 1.9.2
+ '@noble/curves': 1.9.6
'@noble/hashes': 1.8.0
'@scure/bip32': 1.6.2
'@scure/bip39': 1.5.4
@@ -5775,7 +6070,7 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.34.9
fsevents: 2.3.3
- rxjs@7.8.2:
+ rxjs@7.8.1:
dependencies:
tslib: 2.8.1
@@ -5857,7 +6152,7 @@ snapshots:
buffer-builder: 0.2.0
colorjs.io: 0.5.2
immutable: 5.0.3
- rxjs: 7.8.2
+ rxjs: 7.8.1
supports-color: 8.1.1
sync-child-process: 1.0.2
varint: 6.0.0
@@ -5994,6 +6289,12 @@ snapshots:
svg-parser@2.0.4: {}
+ swr@2.2.5(react@19.0.0):
+ dependencies:
+ client-only: 0.0.1
+ react: 19.0.0
+ use-sync-external-store: 1.4.0(react@19.0.0)
+
sync-child-process@1.0.2:
dependencies:
sync-message-port: 1.1.3
@@ -6024,8 +6325,7 @@ snapshots:
uncrypto@0.1.3: {}
- undici-types@6.19.8:
- optional: true
+ undici-types@6.19.8: {}
unstorage@1.16.0(idb-keyval@6.2.2):
dependencies:
diff --git a/src/App.jsx b/src/App.jsx
index 7a17edf..c199696 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -16,6 +16,7 @@ import TopBar from "./components/TopBar/TopBar";
import { shouldTriggerSafetyCheck } from "./helpers";
import { isNetworkAvailable } from "./constants";
import useTheme from "./hooks/useTheme";
+import { useUnstableProvider } from "./hooks/ghost";
import { dark as darkTheme } from "./themes/dark.js";
import { girth as gTheme } from "./themes/girth.js";
import { light as lightTheme } from "./themes/light.js";
@@ -112,6 +113,24 @@ function App() {
const isSmallerScreen = useMediaQuery("(max-width: 1047px)");
const isSmallScreen = useMediaQuery("(max-width: 600px)");
+ const {
+ providerDetail,
+ providerDetails,
+ connectProviderDetail
+ } = useUnstableProvider()
+
+ useEffect(() => {
+ // TODO: make sure we are using correct extension
+ const maybeProvider = providerDetails?.find(obj => obj.info.rdns === "io.ghostchain.GhostWalletExtension")
+ if (maybeProvider && !providerDetail) {
+ try {
+ connectProviderDetail(maybeProvider)
+ } catch (e) {
+ console.log(e)
+ }
+ }
+ }, [providerDetail, providerDetails, connectProviderDetail])
+
useEffect(() => {
if (shouldTriggerSafetyCheck()) {
toast.success("Safety Check: Always verify you're on app.dao.ghostchain.io!", { duration: 5000 });
diff --git a/src/containers/Bridge/Bridge.jsx b/src/containers/Bridge/Bridge.jsx
index f550f69..285a9f0 100644
--- a/src/containers/Bridge/Bridge.jsx
+++ b/src/containers/Bridge/Bridge.jsx
@@ -14,12 +14,16 @@ import {
useMediaQuery,
useTheme
} from "@mui/material";
-import { ss58Decode } from "@polkadot-labs/hdkd-helpers";
+import { ss58Decode, ss58Address } from "@polkadot-labs/hdkd-helpers";
import { toHex } from "@polkadot-api/utils";
+import { decodeAddress } from "@polkadot/util-crypto";
import { useBlockNumber, useTransactionConfirmations } from "wagmi";
+import { keccak256 } from "viem";
+import { u64, u128 } from "scale-ts";
+import PendingIcon from '@mui/icons-material/Pending';
import PendingActionsIcon from '@mui/icons-material/PendingActions';
-import PublicIcon from '@mui/icons-material/Public';
+import ArrowBack from '@mui/icons-material/ArrowBack';
import ArrowRightIcon from '@mui/icons-material/ArrowRight';
import HourglassBottomIcon from '@mui/icons-material/HourglassBottom';
import ThumbUpIcon from '@mui/icons-material/ThumbUp';
@@ -42,6 +46,17 @@ import { formatCurrency } from "../../helpers";
import { useTokenSymbol, useBalance } from "../../hooks/tokens";
import { useGatekeeperAddress, ghost } from "../../hooks/staking";
+import {
+ useEvmNetwork,
+ useClapsInSession,
+ useApplauseThreshold,
+ useReceivedClaps,
+ useApplausesForTransaction,
+ useAuthorities,
+ useCurrentIndex,
+ useUnstableProvider,
+ useMetadata
+} from "../../hooks/ghost";
const STORAGE_PREFIX = "storedTransactions"
@@ -54,129 +69,79 @@ const Bridge = ({ chainId, address, config, connect }) => {
const [isPending, setIsPending] = useState(false);
const [bridgeAction, setBridgeAction] = useState(true);
const [activeTxIndex, setActiveTxIndex] = useState(-1);
- const [txStep, setTxStep] = useState(0);
const [receiver, setReceiver] = useState("");
const [convertedReceiver, setConvertedReceiver] = useState(undefined);
const [amount, setAmount] = useState("");
- // ReceivedClaps && ApplausesForTransaction
- // session_index
- // transaction_hash
- // keccak256(receiver, amount, chain_id)
+ const sliceString = (string, first, second) => {
+ return string.slice(0, first) + "..." + string.slice(second);
+ }
- // const initialStoredTransactions = sessionStorage.getItem(STORAGE_PREFIX);
- const initialStoredTransactions = JSON.stringify([
- {
- sessionIndex: 23,
- transactionHash: "0x11111111111111111",
- receiver: "sfAasdadasasads",
- amount: "2312323232223232",
- chainId: 11155111,
- timestamp: Date.now()
- },
- {
- sessionIndex: 23,
- transactionHash: "0x2222222222222222222",
- receiver: "sfAasdadasasads",
- amount: "1122232232",
- chainId: 1,
- timestamp: Date.now()
- },
- {
- sessionIndex: 24,
- transactionHash: "0x333333333333333333",
- receiver: "sfAasdadasasads",
- amount: "99999999999999992",
- chainId: 11155111,
- timestamp: Date.now()
- },
- {
- sessionIndex: 23,
- transactionHash: "0x4444444444444444444",
- receiver: "sfAasdadasasads",
- amount: "2312323232223232",
- chainId: 11155111,
- timestamp: Date.now()
- },
- {
- sessionIndex: 23,
- transactionHash: "0x555555555555555555555",
- receiver: "sfAasdadasasads",
- amount: "1122232232",
- chainId: 1,
- timestamp: Date.now()
- },
- {
- sessionIndex: 24,
- transactionHash: "0x66666666666666666666666666",
- receiver: "sfAasdadasasads",
- amount: "99999999999999992",
- chainId: 11155111,
- timestamp: Date.now()
- },
- {
- sessionIndex: 23,
- transactionHash: "0x77777777777777777777777777",
- receiver: "sfAasdadasasads",
- amount: "2312323232223232",
- chainId: 11155111,
- timestamp: Date.now()
- },
- {
- sessionIndex: 23,
- transactionHash: "0x888888888888888888888888888",
- receiver: "sfAasdadasasads",
- amount: "1122232232",
- chainId: 1,
- timestamp: Date.now()
- },
- {
- sessionIndex: 24,
- transactionHash: "0x999999999999999999999",
- receiver: "sfAasdadasasads",
- amount: "99999999999999992",
- chainId: 11155111,
- timestamp: Date.now()
- },
- {
- sessionIndex: 23,
- transactionHash: "0x10101010101010101010",
- receiver: "sfAasdadasasads",
- amount: "2312323232223232",
- chainId: 11155111,
- timestamp: Date.now()
- },
- {
- sessionIndex: 23,
- transactionHash: "0x12121212121212212",
- receiver: "sfAasdadasasads",
- amount: "1122232232",
- chainId: 1,
- timestamp: Date.now()
- },
- {
- sessionIndex: 24,
- transactionHash: "0x1313131313131313131",
- receiver: "sfAasdadasasads",
- amount: "99999999999999992",
- chainId: 11155111,
- timestamp: Date.now()
- }
- ]);
+ const initialStoredTransactions = sessionStorage.getItem(STORAGE_PREFIX);
+ // const initialStoredTransactions = JSON.stringify([
+ // {
+ // sessionIndex: 124,
+ // transactionHash: "0x637276eccfa0787de396877a1a259964334fb52cb5111ea84bb28f0006f06276",
+ // receiverAddress: "sfK147dy2NapxEKwrTLLxTkmhw15kkoJeEKrg77oLFRmUQZDb",
+ // amount: "10000000000000000000",
+ // chainId: 11155111,
+ // blockNumber: 9033063,
+ // timestamp: Date.now()
+ // }
+ // ]);
const [storedTransactions, setStoredTransactions] = useState(
initialStoredTransactions ? JSON.parse(initialStoredTransactions) : []
);
- const incomingCommission = new DecimalBigNumber(69n, 100);
- const validators = ["first", "second", "third"];
- const clappedValidators = 1;
+ const { providerDetail } = useUnstableProvider();
+ const metadata = useMetadata();
+
+ const watchTransaction = useMemo(() => {
+ if (activeTxIndex < 0 || activeTxIndex >= storedTransactions?.length) {
+ return undefined
+ }
+ return storedTransactions?.at(activeTxIndex)
+ }, [activeTxIndex, storedTransactions])
+
+ const hashedArguments = useMemo(() => {
+ if (!watchTransaction) return undefined
+
+ const amountEncoded = u128.enc(BigInt(watchTransaction.amount));
+ const networkIdEncoded = u64.enc(BigInt(chainId));
+ const addressEncoded = decodeAddress(watchTransaction.receiverAddress, false, 1996);
+
+ const clapArgsStr = new Uint8Array([
+ ...addressEncoded,
+ ...amountEncoded,
+ ...networkIdEncoded
+ ]);
+ return keccak256(clapArgsStr)
+ }, [watchTransaction])
+
+ const currentSession = useCurrentIndex();
+ const evmNetwork = useEvmNetwork({ evmChainId: chainId });
+ const authorities = useAuthorities({
+ currentSession: watchTransaction?.sessionIndex ?? currentSession
+ });
+ const clapsInSession = useClapsInSession({
+ currentSession: watchTransaction?.sessionIndex ?? currentSession
+ });
+ const appluseThreshold = useApplauseThreshold();
+ const receivedClaps = useReceivedClaps({
+ currentSession: watchTransaction?.sessionIndex ?? currentSession,
+ txHash: watchTransaction?.transactionHash,
+ argsHash: hashedArguments
+ });
+ const transactionApplaused = useApplausesForTransaction({
+ currentSession: watchTransaction?.sessionIndex ?? currentSession,
+ txHash: watchTransaction?.transactionHash,
+ argsHash: hashedArguments
+ });
+
+ const finalityDelay = Number(evmNetwork?.finality_delay ?? 0n);
+ const incomingFee = Number(evmNetwork?.incoming_fee ?? 0n) / 10000000;
const { data: blockNumber } = useBlockNumber({ watch: true });
- // const { data: txtx } = useTransactionConfirmations({
- // hash: "0xdb30adfa3bfc58539bc3a9a92f0dcace8f251af90f8a4f525b57d95d28103afc",
- // refetchInterval: 5000
- // });
- // console.log(txtx)
const { gatekeeperAddress } = useGatekeeperAddress(chainId);
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const {
@@ -196,6 +161,11 @@ const Bridge = ({ chainId, address, config, connect }) => {
}
}, [receiver])
+ const clapsInSessionLength = useMemo(() => {
+ const disabledIndexes = new Set(clapsInSession?.filter(item => item.at(1).disabled).map(item => item.at(0)));
+ return authorities?.filter((_, idx) => !disabledIndexes.has(idx)).length ?? 0;
+ }, [authorities, clapsInSession]);
+
const chainExplorerUrl = useMemo(() => {
const client = config?.getClient();
return client?.chain?.blockExplorers?.default?.url;
@@ -208,10 +178,36 @@ const Bridge = ({ chainId, address, config, connect }) => {
const currentRecord = useMemo(() => {
if (activeTxIndex === -1) return undefined
- return storedTransactions.at(activeTxIndex)
- }, [activeTxIndex, storedTransactions]);
+ const current = storedTransactions.at(activeTxIndex)
+ if (!current) return undefined
- const gatekeeperAddressEmpty = useMemo(() => {
+ const finalization = Math.max(0, (finalityDelay + watchTransaction.blockNumber) - Number(blockNumber));
+ const receivedClapsLength = receivedClaps?.length ?? 0;
+ const clapsNeeded = Math.floor(clapsInSessionLength * appluseThreshold / 100);
+ const step = finalization > 0
+ ? 0
+ : receivedClapsLength < clapsNeeded
+ ? 1
+ : !transactionApplaused
+ ? 2
+ : 3;
+
+ return {
+ ...watchTransaction,
+ finalization,
+ step,
+ }
+ }, [
+ transactionApplaused,
+ receivedClaps,
+ appluseThreshold,
+ clapsInSessionLength,
+ finalityDelay,
+ watchTransaction,
+ blockNumber
+ ]);
+
+ const gatekeeperAddressEmpty = useMemo(() => {
if (gatekeeperAddress === "0x0000000000000000000000000000000000000000") {
return true;
}
@@ -241,10 +237,6 @@ const Bridge = ({ chainId, address, config, connect }) => {
setActiveTxIndex(-1);
}, [storedTransactions, activeTxIndex, setStoredTransactions, setActiveTxIndex]);
- const handleMouseEnter = (index) => {
- setTxStep(index);
- }
-
const ghostOrConnect = async () => {
if (address === "") {
connect();
@@ -257,6 +249,21 @@ const Bridge = ({ chainId, address, config, connect }) => {
setReceiver("");
setAmount("");
setIsPending(false);
+
+ const transaction = {
+ sessionIndex: currentSession ?? 0,
+ transactionHash: txHash,
+ receiverAddress: receiver,
+ amount: preparedAmount.toString(),
+ chainId: chainId,
+ blockNumber: Number(blockNumber),
+ timestamp: Date.now()
+ }
+
+ const newStoredTransactions = [...storedTransactions, transaction];
+ setStoredTransactions(newStoredTransactions);
+ sessionStorage.setItem(STORAGE_PREFIX, JSON.stringify(newStoredTransactions));
+ setActiveTxIndex(newStoredTransactions.length - 1)
}
}
@@ -297,16 +304,16 @@ const Bridge = ({ chainId, address, config, connect }) => {
target="_blank"
rel="noopener noreferrer"
href={currentRecord
- ? `${chainExplorerUrl}/tx/${currentRecord.transactionHash}`
+ ? `${chainExplorerUrl}/tx/${currentRecord ? currentRecord.transactionHash : ""}`
: ""
}
>
- {currentRecord?.transactionHash.slice(0, 9)}...{currentRecord?.transactionHash.slice(-9)}
+ {currentRecord ? sliceString(currentRecord.transactionHash, 10, -8) : ""}
}
- open={activeTxIndex > -1}
+ open={activeTxIndex >= 0}
onClose={() => setActiveTxIndex(-1)}
minHeight={"100px"}
>
@@ -315,15 +322,14 @@ const Bridge = ({ chainId, address, config, connect }) => {
0 && theme.colors.primary[300]
}}
width="120px"
display="flex"
flexDirection="column"
justifyContent="start"
alignItems="center"
- onMouseEnter={() => handleMouseEnter(0)}
>
{
component={HourglassBottomIcon}
/>
Finalization
- {blockNumber?.toString()} blocks left
+
+ {Math.max(
+ 0,
+ (finalityDelay + (currentRecord ? currentRecord.blockNumber : 0) - Number(blockNumber))
+ ).toString()} blocks left
+
1 && theme.colors.primary[300]
}}
width="120px"
display="flex"
flexDirection="column"
justifyContent="start"
alignItems="center"
- onMouseEnter={() => handleMouseEnter(1)}
>
{
/>
- Slow claps
- {clappedValidators} / {validators.length}
+ Slow Claps
+ {receivedClaps?.length ?? 0} / {clapsInSessionLength}
= 2 && "scale(1.2)",
+ color: currentRecord?.step >= 2 && theme.colors.primary[300]
}}
width="120px"
display="flex"
flexDirection="column"
justifyContent="start"
alignItems="center"
- onMouseEnter={() => handleMouseEnter(2)}
>
Applaused
- Check Receiver
+ {
+ currentRecord?.step === 3 ? "Check Receiver" : "Waiting Room"
+ }
@@ -405,9 +419,20 @@ const Bridge = ({ chainId, address, config, connect }) => {
Session Index:
{currentRecord?.sessionIndex}
-
+
Receiver Address:
- {currentRecord?.receiver}
+ navigator.clipboard.writeText(currentRecord ? currentRecord.receiverAddress : "")}
+ >
+ {
+ currentRecord ? sliceString(currentRecord.receiverAddress, 14, -5) : ""
+ }
+
Sent Amount:
@@ -424,13 +449,33 @@ const Bridge = ({ chainId, address, config, connect }) => {
new Date(currentRecord ? currentRecord.timestamp : 0).toLocaleString('en-US')
}
+
+
+ Transaction Hash:
+ navigator.clipboard.writeText(currentRecord ? currentRecord.transactionHash : "")}
+ >
+ {
+ currentRecord ? sliceString(currentRecord.transactionHash, 10, -8) : "0x"
+ }
+
+
+
+ Arguments Hash:
+ navigator.clipboard.writeText(hashedArguments ? hashedArguments : "")}
+ >
+ {
+ hashedArguments ? sliceString(hashedArguments, 10, -8) : "0x"
+ }
+
+
removeStoredRecord()}
>
@@ -446,22 +491,24 @@ const Bridge = ({ chainId, address, config, connect }) => {
+
+ {!bridgeAction && ( setBridgeAction(!bridgeAction)}
+ />)}
{
- bridgeAction
- ? `Bridge $${ghstSymbol}`
- : "Transaction history"
+ bridgeAction ? `Bridge $${ghstSymbol}` : "Transaction history"
}
}
- topRight={
- setBridgeAction(!bridgeAction)}
- />
- }
+ topRight={bridgeAction && ( setBridgeAction(!bridgeAction)}
+ />)}
enableBackground
fullWidth
@@ -504,7 +551,7 @@ const Bridge = ({ chainId, address, config, connect }) => {
- There is no connected gatekeeper on {chainName} network. Propose gatekeeper on this network to make validators listen to it.
+ There is no connected gatekeeper on {chainName} network. Propose gatekeeper on this network to make authorities listen to it.
@@ -519,40 +566,55 @@ const Bridge = ({ chainId, address, config, connect }) => {
rel="noopener noreferrer"
href={`${chainExplorerUrl}/token/${gatekeeperAddress}`}
>
- {gatekeeperAddress.slice(0, 10) + "..." + gatekeeperAddress.slice(-8)}
+ {sliceString(gatekeeperAddress, 10, -8)}
>
)}
- {incomingCommission && validators?.length ? (
-
-
-
- GHOST Wallet is not detected on your browser. Download
-
- GHOST Wallet
- to see full detalization for bridge transaction.
-
-
-
- )
- : (
-
-
- {!isVerySmallScreen && Est. Commission:}
- unknown
-
-
- {!isVerySmallScreen && Number of validators:}
- unknown
-
-
- )}
-
+
+ {!providerDetail
+ ? (
+
+
+ GHOST Wallet is not detected on your browser. Download
+
+ GHOST Wallet
+ to see full detalization for bridge transaction.
+
+
+ )
+ : metadata
+ ? (
+
+
+ {!isVerySmallScreen && Est. Commission:}
+ {incomingFee.toFixed(4)}%
+
+
+ {!isVerySmallScreen && Finality Delay:}
+ {finalityDelay} blocks
+
+
+ {!isVerySmallScreen && Current Epoch:}
+ {currentSession ?? 0}
+
+
+ {!isVerySmallScreen && Number of validators:}
+ {clapsInSessionLength}
+
+
+ )
+ : (
+
+ Downloading chain metadata, wait please...
+
+ )
+ }
+
{
)}
{!bridgeAction && (
-
+ {!isSemiSmallScreen && (
Amount
Datetime
Status
-
+ )}
{filteredStoredTransactions
.map((obj, idx) => (
{
onClick={() => setActiveTxIndex(idx)}
>
-
+
{formatCurrency(
new DecimalBigNumber(BigInt(obj.amount), 18).toString(),
- 3,
+ isSemiSmallScreen ? 3 : 8,
ghstSymbol
)}
+
+ {sliceString(
+ obj.receiverAddress,
+ isSemiSmallScreen ? 5 : 10,
+ isSemiSmallScreen ? -3 : -8
+ )}
+
-
-
+
+
{new Date(obj.timestamp).toLocaleDateString('en-US')}
-
+
{new Date(obj.timestamp).toLocaleTimeString('en-US')}
@@ -626,24 +696,24 @@ const Bridge = ({ chainId, address, config, connect }) => {
justifyContent="center"
alignItems="center"
sx={{
- width: "25px",
- height: "25px",
- background: idx % 2 === 0
+ width: "20px",
+ height: "20px",
+ background: Number(blockNumber) - (obj.blockNumber + finalityDelay) < 0
? theme.colors.feedback.warning
: theme.colors.feedback.success,
borderRadius: "100%",
boxShadow: "0px 0px 1px black"
}}
>
- {idx % 2 === 0 ?
+ {Number(blockNumber) - (obj.blockNumber + finalityDelay) < 0 ?
:
diff --git a/src/hooks/ghost/MetadataProvider.jsx b/src/hooks/ghost/MetadataProvider.jsx
new file mode 100644
index 0000000..29b5838
--- /dev/null
+++ b/src/hooks/ghost/MetadataProvider.jsx
@@ -0,0 +1,44 @@
+import React, { ReactNode } from "react"
+import useSWR from "swr"
+import { createContext, useContext } from 'react';
+
+import { decAnyMetadata, unifyMetadata } from "@polkadot-api/substrate-bindings"
+import { useUnstableProvider } from "./UnstableProvider"
+
+const MetadataProvider = createContext(null)
+export const useMetadata = () => useContext(MetadataProvider)
+
+export const MetadataProviderProvider = ({ children }) => {
+ const { client, chainId } = useUnstableProvider()
+ const { data: metadata } = useSWR(
+ client && chainId ? ["metadata", client, chainId] : null,
+ async ([_, client]) => {
+ const storageKey = `metadata-${chainId}`
+ const storedMetadata = sessionStorage.getItem(storageKey)
+ if (storedMetadata) return unifyMetadata(decAnyMetadata(storedMetadata))
+
+ const metadata = await new Promise((resolve, reject) =>
+ client._request("state_getMetadata", [], {
+ onSuccess: resolve,
+ onError: reject,
+ }),
+ )
+ .then(r => r)
+ .catch(_ => undefined)
+
+ if (metadata) {
+ sessionStorage.setItem(storageKey, metadata)
+ return unifyMetadata(decAnyMetadata(metadata))
+ } else {
+ sessionStorage.removeItem(storageKey)
+ return undefined
+ }
+ }
+ )
+
+ return (
+
+ {children}
+
+ )
+}
diff --git a/src/hooks/ghost/UnstableProvider.jsx b/src/hooks/ghost/UnstableProvider.jsx
new file mode 100644
index 0000000..3ceb85c
--- /dev/null
+++ b/src/hooks/ghost/UnstableProvider.jsx
@@ -0,0 +1,55 @@
+import { createContext, useContext, useState, useMemo } from "react"
+import { Unstable } from "@substrate/connect-discovery"
+import { createClient } from "@polkadot-api/substrate-client"
+import { getObservableClient } from "@polkadot-api/observable-client"
+import useSWR from "swr"
+
+const DEFAULT_CHAIN_ID = "0xa217f4ee58a944470e9633ca5bd6d28a428ed64cd9b6f3e413565f359f89af90"
+const UnstableProvider = createContext(null)
+export const useUnstableProvider = () => useContext(UnstableProvider)
+
+export const UnstableProviderProvider = ({ children }) => {
+ const { data: providerDetails } = useSWR("getGhostProviders", () =>
+ Unstable.getSubstrateConnectExtensionProviders()
+ );
+
+ const [providerDetail, setProviderDetail] = useState();
+ const { data: provider } = useSWR(
+ () => providerDetail ? `ghostProviderDetail.${providerDetail.info.uuid}.provider` : null,
+ () => providerDetail ? providerDetail.provider : null
+ );
+
+ const [chainId, setChainId] = useState(DEFAULT_CHAIN_ID);
+
+ const client = useMemo(() => {
+ if (!provider || !chainId) return undefined;
+ const chain = provider.getChains()[chainId];
+ if (!chain) return undefined;
+ return createClient(chain.connect);
+ }, [provider, chainId]);
+
+ const observableClient = useMemo(() => {
+ return client ? getObservableClient(client) : undefined;
+ }, [client]);
+
+ const chainHead$ = useMemo(() => {
+ return observableClient ? observableClient.chainHead$() : undefined;
+ }, [observableClient]);
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/hooks/ghost/index.js b/src/hooks/ghost/index.js
new file mode 100644
index 0000000..da4f253
--- /dev/null
+++ b/src/hooks/ghost/index.js
@@ -0,0 +1,9 @@
+export * from "./UnstableProvider";
+export * from "./MetadataProvider";
+export * from "./useCurrentIndex";
+export * from "./useEvmNetwork";
+export * from "./useClapsInSession";
+export * from "./useApplauseThreshold";
+export * from "./useReceivedClaps";
+export * from "./useAuthorities";
+export * from "./useApplausesForTransaction";
diff --git a/src/hooks/ghost/useApplauseThreshold.js b/src/hooks/ghost/useApplauseThreshold.js
new file mode 100644
index 0000000..7b4f5a0
--- /dev/null
+++ b/src/hooks/ghost/useApplauseThreshold.js
@@ -0,0 +1,23 @@
+import useSWR from "swr"
+import { getDynamicBuilder, getLookupFn } from "@polkadot-api/metadata-builders"
+
+import { useUnstableProvider } from "./UnstableProvider"
+import { useMetadata } from "./MetadataProvider"
+
+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/ghost/useApplausesForTransaction.js b/src/hooks/ghost/useApplausesForTransaction.js
new file mode 100644
index 0000000..f956581
--- /dev/null
+++ b/src/hooks/ghost/useApplausesForTransaction.js
@@ -0,0 +1,47 @@
+import useSWRSubscription from "swr/subscription"
+import { getDynamicBuilder, getLookupFn } from "@polkadot-api/metadata-builders"
+import { distinct, filter, map, mergeMap } from "rxjs"
+import { fromHex } from "@polkadot-api/utils";
+
+import { useUnstableProvider } from "./UnstableProvider"
+import { useMetadata } from "./MetadataProvider"
+
+export const useApplausesForTransaction = ({ currentSession, txHash, argsHash }) => {
+ 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) => {
+ 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) => applausesForTransaction?.value.dec(value))
+ )
+ }),
+ )
+ .subscribe({
+ next(applausesForTransaction) {
+ next(null, applausesForTransaction)
+ },
+ error: next,
+ })
+ return () => subscription.unsubscribe()
+ }
+ )
+ return applausesForTransaction
+}
diff --git a/src/hooks/ghost/useAuthorities.js b/src/hooks/ghost/useAuthorities.js
new file mode 100644
index 0000000..a11ecc4
--- /dev/null
+++ b/src/hooks/ghost/useAuthorities.js
@@ -0,0 +1,41 @@
+import useSWRSubscription from "swr/subscription"
+import { getDynamicBuilder, getLookupFn } from "@polkadot-api/metadata-builders"
+import { distinct, filter, map, mergeMap } from "rxjs"
+
+import { useUnstableProvider } from "./UnstableProvider"
+import { useMetadata } from "./MetadataProvider"
+
+export const useAuthorities = ({ currentSession }) => {
+ 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) => {
+ 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) => slowClapAuthorities?.value.dec(value))
+ )
+ }),
+ )
+ .subscribe({
+ next(slowClapAuthorities) {
+ next(null, slowClapAuthorities)
+ },
+ error: next,
+ })
+ return () => subscription.unsubscribe()
+ }
+ )
+ return slowClapAuthorities
+}
diff --git a/src/hooks/ghost/useClapsInSession.js b/src/hooks/ghost/useClapsInSession.js
new file mode 100644
index 0000000..afed77c
--- /dev/null
+++ b/src/hooks/ghost/useClapsInSession.js
@@ -0,0 +1,41 @@
+import useSWRSubscription from "swr/subscription"
+import { getDynamicBuilder, getLookupFn } from "@polkadot-api/metadata-builders"
+import { distinct, filter, map, mergeMap } from "rxjs"
+
+import { useUnstableProvider } from "./UnstableProvider"
+import { useMetadata } from "./MetadataProvider"
+
+export const useClapsInSession = ({ currentSession }) => {
+ 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) => {
+ 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) => clapsInSession?.value.dec(value))
+ )
+ }),
+ )
+ .subscribe({
+ next(clapsInSession) {
+ next(null, clapsInSession)
+ },
+ error: next,
+ })
+ return () => subscription.unsubscribe()
+ }
+ )
+ return clapsInSession
+}
diff --git a/src/hooks/ghost/useCurrentIndex.js b/src/hooks/ghost/useCurrentIndex.js
new file mode 100644
index 0000000..c043d3e
--- /dev/null
+++ b/src/hooks/ghost/useCurrentIndex.js
@@ -0,0 +1,41 @@
+import useSWRSubscription from "swr/subscription"
+import { getDynamicBuilder, getLookupFn } from "@polkadot-api/metadata-builders"
+import { distinct, filter, map, mergeMap } from "rxjs"
+
+import { useUnstableProvider } from "./UnstableProvider"
+import { useMetadata } from "./MetadataProvider"
+
+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) => {
+ 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) => currentIndex?.value.dec(value))
+ )
+ }),
+ )
+ .subscribe({
+ next(currentIndex) {
+ next(null, currentIndex)
+ },
+ error: next,
+ })
+ return () => subscription.unsubscribe()
+ }
+ )
+ return currentIndex
+}
diff --git a/src/hooks/ghost/useEvmNetwork.js b/src/hooks/ghost/useEvmNetwork.js
new file mode 100644
index 0000000..46047a8
--- /dev/null
+++ b/src/hooks/ghost/useEvmNetwork.js
@@ -0,0 +1,41 @@
+import useSWRSubscription from "swr/subscription"
+import { getDynamicBuilder, getLookupFn } from "@polkadot-api/metadata-builders"
+import { distinct, filter, map, mergeMap } from "rxjs"
+
+import { useUnstableProvider } from "./UnstableProvider"
+import { useMetadata } from "./MetadataProvider"
+
+export const useEvmNetwork = ({ evmChainId }) => {
+ const { chainHead$, chainId } = useUnstableProvider()
+ const metadata = useMetadata()
+ const { data: evmNetwork } = useSWRSubscription(
+ chainHead$ && chainId && metadata
+ ? ["evmNetwork", chainHead$, evmChainId, chainId, metadata]
+ : null,
+ ([_, chainHead$, evmChainId, chainId, metadata], { next }) => {
+ const { finalized$, storage$ } = chainHead$
+ const subscription = finalized$.pipe(
+ filter(Boolean),
+ mergeMap((blockInfo) => {
+ const builder = getDynamicBuilder(getLookupFn(metadata))
+ const evmNetwork = builder.buildStorage("GhostNetworks", "Networks")
+ return storage$(blockInfo?.hash, "value", () =>
+ evmNetwork?.keys.enc(BigInt(evmChainId))
+ ).pipe(
+ filter(Boolean),
+ distinct(),
+ map((value) => evmNetwork?.value.dec(value))
+ )
+ }),
+ )
+ .subscribe({
+ next(evmNetwork) {
+ next(null, evmNetwork)
+ },
+ error: next,
+ })
+ return () => subscription.unsubscribe()
+ }
+ )
+ return evmNetwork
+}
diff --git a/src/hooks/ghost/useReceivedClaps.js b/src/hooks/ghost/useReceivedClaps.js
new file mode 100644
index 0000000..6f8ae00
--- /dev/null
+++ b/src/hooks/ghost/useReceivedClaps.js
@@ -0,0 +1,47 @@
+import useSWRSubscription from "swr/subscription"
+import { getDynamicBuilder, getLookupFn } from "@polkadot-api/metadata-builders"
+import { distinct, filter, map, mergeMap } from "rxjs"
+import { fromHex } from "@polkadot-api/utils";
+
+import { useUnstableProvider } from "./UnstableProvider"
+import { useMetadata } from "./MetadataProvider"
+
+export const useReceivedClaps = ({ currentSession, txHash, argsHash }) => {
+ 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) => {
+ 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) => receivedClaps?.value.dec(value))
+ )
+ }),
+ )
+ .subscribe({
+ next(receivedClaps) {
+ next(null, receivedClaps)
+ },
+ error: next,
+ })
+ return () => subscription.unsubscribe()
+ }
+ )
+ return receivedClaps
+}
diff --git a/src/main.jsx b/src/main.jsx
index dbbd427..a53193e 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -8,6 +8,7 @@ import { HashRouter } from "react-router-dom";
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { WagmiProvider } from "wagmi";
import { config } from "./config";
+import { UnstableProviderProvider, MetadataProviderProvider } from "./hooks/ghost"
import ReactGA from "react-ga4";
const queryClient = new QueryClient();
@@ -21,7 +22,11 @@ ReactDOM.createRoot(document.getElementById('root')).render(
-
+
+
+
+
+