From 5c6ae36f81190eda4c171b435f9e1725b2db0460 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Tue, 1 Oct 2024 14:15:10 +0200 Subject: [PATCH] Mobx spike (#77) * feat: sync popover * fix: sync bar * refactor: change `useConnect` to use signals * feat: change state manager to Zustand * refactor: remove signals lib usage * Mobx spike * Post review updates --------- Co-authored-by: Max Korsunov --- package.json | 8 +- pnpm-lock.yaml | 146 ++++++++++++-------- src/app/v2/layout.tsx | 9 +- src/components/copiedTx.tsx | 2 +- src/components/header/connection.tsx | 19 +-- src/components/header/desktop-nav.tsx | 4 +- src/components/header/index.tsx | 12 +- src/components/header/logo.tsx | 2 +- src/components/header/mobile-nav.tsx | 10 +- src/components/header/provider-popover.tsx | 13 +- src/components/header/status-popover.tsx | 66 +++++++++ src/components/header/sync-bar.tsx | 17 +++ src/pages/block/[block_height].tsx | 2 +- src/state/connection/index.ts | 82 +++++++++++ src/state/status/getSyncPercent.ts | 20 +++ src/state/status/index.ts | 80 +++++++++++ src/{constants => utils}/configConstants.ts | 0 src/utils/{penumbra => }/penumbra.ts | 0 src/utils/penumbra/useConnect.ts | 79 ----------- src/utils/token/tokenFetch.tsx | 2 +- styles/v2.css | 5 + 21 files changed, 406 insertions(+), 172 deletions(-) create mode 100644 src/components/header/status-popover.tsx create mode 100644 src/components/header/sync-bar.tsx create mode 100644 src/state/connection/index.ts create mode 100644 src/state/status/getSyncPercent.ts create mode 100644 src/state/status/index.ts rename src/{constants => utils}/configConstants.ts (100%) rename src/utils/{penumbra => }/penumbra.ts (100%) delete mode 100644 src/utils/penumbra/useConnect.ts diff --git a/package.json b/package.json index 45c5d5b9..55bc5d68 100644 --- a/package.json +++ b/package.json @@ -28,10 +28,10 @@ "@emotion/styled": "^11.13.0", "@grpc/proto-loader": "^0.7.13", "@penumbra-labs/registry": "^11.3.1", - "@penumbra-zone/client": "^18.1.0", - "@penumbra-zone/protobuf": "^6.0.0", + "@penumbra-zone/client": "^19.0.0", + "@penumbra-zone/protobuf": "^6.1.0", "@penumbra-zone/transport-dom": "^7.5.0", - "@penumbra-zone/ui": "^9.0.0", + "@penumbra-zone/ui": "^10.0.0", "@penumbra-zone/wasm": "^26.2.0", "@radix-ui/react-icons": "^1.3.0", "@rehooks/component-size": "^1.0.3", @@ -56,6 +56,8 @@ "grpc_tools_node_protoc_ts": "^5.3.3", "lodash": "^4.17.21", "lucide-react": "^0.445.0", + "mobx": "^6.13.3", + "mobx-react-lite": "^4.0.7", "next": "^14.2.7", "pg": "^8.12.0", "react": "^18.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a0eb288d..289186fd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,20 +39,20 @@ importers: specifier: ^11.3.1 version: 11.3.1 '@penumbra-zone/client': - specifier: ^18.1.0 - version: 18.1.0(@bufbuild/protobuf@1.10.0)(@connectrpc/connect@1.4.0(@bufbuild/protobuf@1.10.0))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0))(@penumbra-zone/transport-dom@7.5.0) + specifier: ^19.0.0 + version: 19.0.0(@bufbuild/protobuf@1.10.0)(@connectrpc/connect@1.4.0(@bufbuild/protobuf@1.10.0))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0))(@penumbra-zone/transport-dom@7.5.0) '@penumbra-zone/protobuf': - specifier: ^6.0.0 - version: 6.0.0(@bufbuild/protobuf@1.10.0) + specifier: ^6.1.0 + version: 6.1.0(@bufbuild/protobuf@1.10.0) '@penumbra-zone/transport-dom': specifier: ^7.5.0 version: 7.5.0 '@penumbra-zone/ui': - specifier: ^9.0.0 - version: 9.1.1(22ahnkytf72hixbg7m46epplfq) + specifier: ^10.0.0 + version: 10.0.0(zk5auaacq632y64evccopof44u) '@penumbra-zone/wasm': specifier: ^26.2.0 - version: 26.2.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0))(@penumbra-zone/types@23.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/getters@17.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0))) + version: 26.2.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0))(@penumbra-zone/types@24.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/getters@17.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0))) '@radix-ui/react-icons': specifier: ^1.3.0 version: 1.3.0(react@18.3.1) @@ -122,6 +122,12 @@ importers: lucide-react: specifier: ^0.445.0 version: 0.445.0(react@18.3.1) + mobx: + specifier: ^6.13.3 + version: 6.13.3 + mobx-react-lite: + specifier: ^4.0.7 + version: 4.0.7(mobx@6.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next: specifier: ^14.2.7 version: 14.2.9(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1810,17 +1816,17 @@ packages: '@penumbra-labs/registry@11.3.1': resolution: {integrity: sha512-0hBfPZW4Y3my6RzYSBGI3cwutW+C7KJXn5OLXOhhXPsH+VlexrxvKIWc8nJeUwRCTtBkRR0lUSwuIhnaw0tsyQ==} - '@penumbra-zone/bech32m@7.0.0': - resolution: {integrity: sha512-OqSH4G8pQ/mjy/7EfS9KlxF8Pdr3h/Ib8ZDOjDyQJB4/L+ZYkK3bWFGrFpAI8XDoc0phoMXkL77fZEpR18JoAg==} + '@penumbra-zone/bech32m@8.0.0': + resolution: {integrity: sha512-1MBJClwos7QGFlY/MH7HOfsY1F99FR/OqeYmkL+1NuTCEHrUENi/pS+WOx56rmVIokqSfRPj8zQCh6gd7aQVuw==} peerDependencies: - '@penumbra-zone/protobuf': 6.0.0 + '@penumbra-zone/protobuf': 6.1.0 - '@penumbra-zone/client@18.1.0': - resolution: {integrity: sha512-khXFquqIpDmXUCwQpP6RfYQnkNI4ekDqz+SP/XCo+7W1xHKJiApXnHDKBA/VsOZ1Q/qGak/R1xfq6z9aV58xSA==} + '@penumbra-zone/client@19.0.0': + resolution: {integrity: sha512-SQOEPJ6XYxzPhoxMG4RKz/dC2zHwW6ldOTpEsJxp1nBIa0gvBjkD7olo2OoX/y3aUBKC5xUxuf44JE9L7p1vLg==} peerDependencies: '@bufbuild/protobuf': ^1.10.0 '@connectrpc/connect': ^1.4.0 - '@penumbra-zone/protobuf': 6.0.0 + '@penumbra-zone/protobuf': 6.1.0 '@penumbra-zone/transport-dom': 7.5.0 '@penumbra-zone/getters@17.0.0': @@ -1834,36 +1840,36 @@ packages: resolution: {integrity: sha512-1K+/8bh53Kse4u/I1afUQuRrTnZhLLA6JWIV+mFiXX8An2J2CGIVDjp1mSJkUSzFjFDUzUX052kvYHCtZYK3QA==} hasBin: true - '@penumbra-zone/perspective@30.0.0': - resolution: {integrity: sha512-jgxWYCURzX5IosFCN70MSFWHbLo+3l0g83PFnDlvmswygDJdwObvOzV3rpSVnlVBXAQ9/ZKVAoA3Z6g7Bgr0mg==} + '@penumbra-zone/perspective@31.0.0': + resolution: {integrity: sha512-pBp+Y/VQygEUa63+akigFz+HJEL1jWx333NdAay7tmw6M98eu6+Q8Dc3HbyUK8fokuOzmBNt5V7/oab+FE4iRw==} peerDependencies: '@bufbuild/protobuf': ^1.10.0 - '@penumbra-zone/bech32m': 7.0.0 - '@penumbra-zone/getters': 17.0.0 - '@penumbra-zone/protobuf': 6.0.0 - '@penumbra-zone/wasm': 28.0.0 + '@penumbra-zone/bech32m': 8.0.0 + '@penumbra-zone/getters': 18.0.0 + '@penumbra-zone/protobuf': 6.1.0 + '@penumbra-zone/wasm': 29.0.0 - '@penumbra-zone/protobuf@6.0.0': - resolution: {integrity: sha512-x+g2plwEnYwzB692oAIzA9yzqcbsJt1f1lYmPNJpWOqNA9wC3OPDOfzQa7rgP4v6+KNd5IbRfRNyGPd/6zxYNg==} + '@penumbra-zone/protobuf@6.1.0': + resolution: {integrity: sha512-0aVpa0VvodqGERXRNfD0Q3VawfjH77E86e/aDIc/7FjhZB+9TGvhfVVFFBaaRWwOmueQxH2iT5IY/3p+eXQGXA==} peerDependencies: '@bufbuild/protobuf': ^1.10.0 '@penumbra-zone/transport-dom@7.5.0': resolution: {integrity: sha512-8xFIEDeXODl18AITfiIrJJoE8Y6y/+apO+BhrqCmXP+yqGo8LBzcW3iWDQkOc6Go2c1MGJT97B25r079Km1kuA==} - '@penumbra-zone/types@23.0.0': - resolution: {integrity: sha512-AaIK1A/T6mClpx12Uc5zOdAFzSHLOJS7HvB4A+n+Vo6rylcECz7JM4LV5XmkuWLe9dEW6fDir1XqiKdLQzGi0Q==} + '@penumbra-zone/types@24.0.0': + resolution: {integrity: sha512-iD7K0e34gVrkNvfAOoEUctFMtAfXfofynOSngNqDGqZmu3MMRmKfBFiSAfouloJpf+1lT7h92AJo1vmpUV2VIw==} peerDependencies: '@bufbuild/protobuf': ^1.10.0 - '@penumbra-zone/bech32m': 7.0.0 - '@penumbra-zone/getters': 17.0.0 - '@penumbra-zone/protobuf': 6.0.0 + '@penumbra-zone/bech32m': 8.0.0 + '@penumbra-zone/getters': 18.0.0 + '@penumbra-zone/protobuf': 6.1.0 - '@penumbra-zone/ui@9.1.1': - resolution: {integrity: sha512-ICy1MknA1Thp6zGeTc6MJOjLNolkHPRH+XUXhCCP+QMNKt9xAdvbw9rVq81MlXJaPGGf1p6nmzu03b99sv7esw==} + '@penumbra-zone/ui@10.0.0': + resolution: {integrity: sha512-osBziKnIxN7ORBJhG5YhQ+DfoDvGTIsnwhL2LcA22kJY4Z1DrD1p1Xf42u080dvyj2zsJXd5I7uEUzzC2CWWsA==} peerDependencies: '@bufbuild/protobuf': ^1.10.0 - '@penumbra-zone/protobuf': 6.0.0 + '@penumbra-zone/protobuf': 6.1.0 framer-motion: ^11.2.4 lucide-react: ^0.378.0 postcss: ^8.4.38 @@ -4663,6 +4669,22 @@ packages: engines: {node: '>=10'} hasBin: true + mobx-react-lite@4.0.7: + resolution: {integrity: sha512-RjwdseshK9Mg8On5tyJZHtGD+J78ZnCnRaxeQDSiciKVQDUbfZcXhmld0VMxAwvcTnPEHZySGGewm467Fcpreg==} + peerDependencies: + mobx: ^6.9.0 + react: ^16.8.0 || ^17 || ^18 + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + + mobx@6.13.3: + resolution: {integrity: sha512-YtAS+ZMbdpbHYUU4ESht3na8KiX11KuMT1yOiKtbKlQ0GZkHDYPKyEw/Tdp7h7aHyLrTWj2TBaSNJ6bCr638iQ==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -7844,36 +7866,36 @@ snapshots: '@penumbra-labs/registry@11.3.1': {} - '@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0))': + '@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0))': dependencies: - '@penumbra-zone/protobuf': 6.0.0(@bufbuild/protobuf@1.10.0) + '@penumbra-zone/protobuf': 6.1.0(@bufbuild/protobuf@1.10.0) bech32: 2.0.0 - '@penumbra-zone/client@18.1.0(@bufbuild/protobuf@1.10.0)(@connectrpc/connect@1.4.0(@bufbuild/protobuf@1.10.0))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0))(@penumbra-zone/transport-dom@7.5.0)': + '@penumbra-zone/client@19.0.0(@bufbuild/protobuf@1.10.0)(@connectrpc/connect@1.4.0(@bufbuild/protobuf@1.10.0))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0))(@penumbra-zone/transport-dom@7.5.0)': dependencies: '@bufbuild/protobuf': 1.10.0 '@connectrpc/connect': 1.4.0(@bufbuild/protobuf@1.10.0) - '@penumbra-zone/protobuf': 6.0.0(@bufbuild/protobuf@1.10.0) + '@penumbra-zone/protobuf': 6.1.0(@bufbuild/protobuf@1.10.0) '@penumbra-zone/transport-dom': 7.5.0 - '@penumbra-zone/getters@17.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0))': + '@penumbra-zone/getters@17.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0))': dependencies: '@bufbuild/protobuf': 1.10.0 - '@penumbra-zone/bech32m': 7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)) - '@penumbra-zone/protobuf': 6.0.0(@bufbuild/protobuf@1.10.0) + '@penumbra-zone/bech32m': 8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)) + '@penumbra-zone/protobuf': 6.1.0(@bufbuild/protobuf@1.10.0) '@penumbra-zone/keys@4.2.1': optional: true - '@penumbra-zone/perspective@30.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/getters@17.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0))(@penumbra-zone/wasm@26.2.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0))(@penumbra-zone/types@23.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/getters@17.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0))))': + '@penumbra-zone/perspective@31.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/getters@17.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0))(@penumbra-zone/wasm@26.2.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0))(@penumbra-zone/types@24.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/getters@17.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0))))': dependencies: '@bufbuild/protobuf': 1.10.0 - '@penumbra-zone/bech32m': 7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)) - '@penumbra-zone/getters': 17.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)) - '@penumbra-zone/protobuf': 6.0.0(@bufbuild/protobuf@1.10.0) - '@penumbra-zone/wasm': 26.2.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0))(@penumbra-zone/types@23.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/getters@17.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0))) + '@penumbra-zone/bech32m': 8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)) + '@penumbra-zone/getters': 17.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)) + '@penumbra-zone/protobuf': 6.1.0(@bufbuild/protobuf@1.10.0) + '@penumbra-zone/wasm': 26.2.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0))(@penumbra-zone/types@24.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/getters@17.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0))) - '@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)': + '@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)': dependencies: '@bufbuild/protobuf': 1.10.0 @@ -7882,25 +7904,25 @@ snapshots: '@bufbuild/protobuf': 1.10.0 '@connectrpc/connect': 1.4.0(@bufbuild/protobuf@1.10.0) - '@penumbra-zone/types@23.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/getters@17.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0))': + '@penumbra-zone/types@24.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/getters@17.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0))': dependencies: '@bufbuild/protobuf': 1.10.0 - '@penumbra-zone/bech32m': 7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)) - '@penumbra-zone/getters': 17.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)) - '@penumbra-zone/protobuf': 6.0.0(@bufbuild/protobuf@1.10.0) + '@penumbra-zone/bech32m': 8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)) + '@penumbra-zone/getters': 17.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)) + '@penumbra-zone/protobuf': 6.1.0(@bufbuild/protobuf@1.10.0) bignumber.js: 9.1.2 idb: 8.0.0 zod: 3.23.8 - '@penumbra-zone/ui@9.1.1(22ahnkytf72hixbg7m46epplfq)': + '@penumbra-zone/ui@10.0.0(zk5auaacq632y64evccopof44u)': dependencies: '@bufbuild/protobuf': 1.10.0 '@emotion/react': 11.13.3(@types/react@18.3.5)(react@18.3.1) '@emotion/styled': 11.13.0(@emotion/react@11.13.3(@types/react@18.3.5)(react@18.3.1))(@types/react@18.3.5)(react@18.3.1) - '@penumbra-zone/bech32m': 7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)) - '@penumbra-zone/perspective': 30.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/getters@17.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0))(@penumbra-zone/wasm@26.2.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0))(@penumbra-zone/types@23.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/getters@17.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))) - '@penumbra-zone/protobuf': 6.0.0(@bufbuild/protobuf@1.10.0) - '@penumbra-zone/types': 23.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/getters@17.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)) + '@penumbra-zone/bech32m': 8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)) + '@penumbra-zone/perspective': 31.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/getters@17.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0))(@penumbra-zone/wasm@26.2.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0))(@penumbra-zone/types@24.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/getters@17.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))) + '@penumbra-zone/protobuf': 6.1.0(@bufbuild/protobuf@1.10.0) + '@penumbra-zone/types': 24.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/getters@17.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)) '@radix-ui/react-avatar': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-checkbox': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-dialog': 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -7957,12 +7979,12 @@ snapshots: - immer - supports-color - '@penumbra-zone/wasm@26.2.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0))(@penumbra-zone/types@23.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/getters@17.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))': + '@penumbra-zone/wasm@26.2.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0))(@penumbra-zone/types@24.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/getters@17.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))': dependencies: '@bufbuild/protobuf': 1.10.0 - '@penumbra-zone/bech32m': 7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)) - '@penumbra-zone/protobuf': 6.0.0(@bufbuild/protobuf@1.10.0) - '@penumbra-zone/types': 23.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/getters@17.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@7.0.0(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.0.0(@bufbuild/protobuf@1.10.0)) + '@penumbra-zone/bech32m': 8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)) + '@penumbra-zone/protobuf': 6.1.0(@bufbuild/protobuf@1.10.0) + '@penumbra-zone/types': 24.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/getters@17.0.0(@bufbuild/protobuf@1.10.0)(@penumbra-zone/bech32m@8.0.0(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)))(@penumbra-zone/protobuf@6.1.0(@bufbuild/protobuf@1.10.0)) optionalDependencies: '@penumbra-zone/keys': 4.2.1 @@ -10413,7 +10435,7 @@ snapshots: safe-regex-test: 1.0.3 string.prototype.includes: 2.0.0 - eslint-plugin-prettier@5.2.1(eslint-config-prettier@9.1.0(eslint@9.10.0(jiti@1.21.6)))(eslint@9.10.0(jiti@1.21.6))(prettier@3.3.3): + eslint-plugin-prettier@5.2.1(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@9.10.0(jiti@1.21.6))(prettier@3.3.3): dependencies: eslint: 9.10.0(jiti@1.21.6) prettier: 3.3.3 @@ -11236,6 +11258,16 @@ snapshots: mkdirp@1.0.4: {} + mobx-react-lite@4.0.7(mobx@6.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + mobx: 6.13.3 + react: 18.3.1 + use-sync-external-store: 1.2.2(react@18.3.1) + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + + mobx@6.13.3: {} + ms@2.1.3: {} murmurhash3js@3.0.1: {} @@ -11555,7 +11587,7 @@ snapshots: eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.6.2))(eslint-plugin-import-x@0.5.3(eslint@8.57.0)(typescript@5.6.2))(eslint-plugin-import@2.30.0)(eslint@9.10.0(jiti@1.21.6)) eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0) eslint-plugin-import-x: 0.5.3(eslint@9.10.0(jiti@1.21.6))(typescript@5.6.2) - eslint-plugin-prettier: 5.2.1(eslint-config-prettier@9.1.0(eslint@9.10.0(jiti@1.21.6)))(eslint@9.10.0(jiti@1.21.6))(prettier@3.3.3) + eslint-plugin-prettier: 5.2.1(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@9.10.0(jiti@1.21.6))(prettier@3.3.3) eslint-plugin-react: 7.35.2(eslint@9.10.0(jiti@1.21.6)) eslint-plugin-react-hooks: 5.1.0-rc-c3cdbec0a7-20240708(eslint@9.10.0(jiti@1.21.6)) eslint-plugin-react-refresh: 0.4.11(eslint@9.10.0(jiti@1.21.6)) diff --git a/src/app/v2/layout.tsx b/src/app/v2/layout.tsx index 05c7859c..c489cca3 100644 --- a/src/app/v2/layout.tsx +++ b/src/app/v2/layout.tsx @@ -4,16 +4,23 @@ import { ReactNode } from 'react'; import { PenumbraUIProvider } from '@penumbra-zone/ui/PenumbraUIProvider'; import { Display } from '@penumbra-zone/ui/Display'; import { Header } from '@/components/header'; +import { SyncBar } from '@/components/header/sync-bar'; +import { enableStaticRendering } from 'mobx-react-lite'; + +// Used so that observer() won't subscribe to any observables used in an SSR environment +// and no garbage collection problems are introduced. +enableStaticRendering(typeof window === 'undefined'); const V2Layout = ({ children }: { children: ReactNode }) => { return ( +
{children} - ) + ); }; export default V2Layout; diff --git a/src/components/copiedTx.tsx b/src/components/copiedTx.tsx index de7116f5..8e72926a 100644 --- a/src/components/copiedTx.tsx +++ b/src/components/copiedTx.tsx @@ -3,7 +3,7 @@ import React, { FC, useState } from "react"; import { CopyIcon } from "@radix-ui/react-icons"; import { HStack } from "@chakra-ui/react"; -import { Constants } from "@/constants/configConstants"; +import { Constants } from "@/utils/configConstants.ts"; interface CopyTxToClipboardProps { txHash: string; diff --git a/src/components/header/connection.tsx b/src/components/header/connection.tsx index 43d55394..6e7ba838 100644 --- a/src/components/header/connection.tsx +++ b/src/components/header/connection.tsx @@ -1,15 +1,16 @@ import { Button } from '@penumbra-zone/ui/Button'; -import { useConnect } from '@/utils/penumbra/useConnect.ts'; -import { ProviderPopover } from './provider-popover.tsx'; +import { ProviderPopover } from './provider-popover'; +import { connectionStore } from '@/state/connection'; +import { observer } from 'mobx-react-lite'; -export const Connection = () => { - const { connected, connect } = useConnect(); - - if (!connected) { +export const Connection = observer(() => { + if (!connectionStore.connected) { return ( - + ); } - return -}; + return ; +}); diff --git a/src/components/header/desktop-nav.tsx b/src/components/header/desktop-nav.tsx index 387e1e97..2a087eae 100644 --- a/src/components/header/desktop-nav.tsx +++ b/src/components/header/desktop-nav.tsx @@ -1,8 +1,8 @@ import { useRouter } from 'next/navigation'; import { Tabs } from '@penumbra-zone/ui/Tabs'; import { Density } from '@penumbra-zone/ui/Density'; -import { HEADER_LINKS } from './links.ts'; -import { usePagePath } from '@/utils/routes/usePagePath.ts'; +import { HEADER_LINKS } from './links'; +import { usePagePath } from '@/utils/routes/usePagePath'; export const DesktopNav = () => { const pagePath = usePagePath(); diff --git a/src/components/header/index.tsx b/src/components/header/index.tsx index 00ffb933..3475d4a9 100644 --- a/src/components/header/index.tsx +++ b/src/components/header/index.tsx @@ -1,9 +1,9 @@ import { Density } from '@penumbra-zone/ui/Density'; -import { HeaderLogo } from './logo.tsx'; -// import { StatusPopover } from './status-popover.tsx'; -import { MobileNav } from './mobile-nav.tsx'; -import { DesktopNav } from './desktop-nav.tsx'; -import { Connection } from './connection.tsx'; +import { HeaderLogo } from './logo'; +import { StatusPopover } from './status-popover'; +import { MobileNav } from './mobile-nav'; +import { DesktopNav } from './desktop-nav'; +import { Connection } from './connection'; export const Header = () => { return ( @@ -14,7 +14,7 @@ export const Header = () => {
- {/* */} +
diff --git a/src/components/header/logo.tsx b/src/components/header/logo.tsx index e409e711..db334f6c 100644 --- a/src/components/header/logo.tsx +++ b/src/components/header/logo.tsx @@ -1,5 +1,5 @@ import Link from 'next/link' -import { PagePath } from '@/utils/routes/pages.ts'; +import { PagePath } from '@/utils/routes/pages'; import PenumbraLogo from './logo.svg'; export const HeaderLogo = () => { diff --git a/src/components/header/mobile-nav.tsx b/src/components/header/mobile-nav.tsx index f933de4d..89eb244c 100644 --- a/src/components/header/mobile-nav.tsx +++ b/src/components/header/mobile-nav.tsx @@ -5,10 +5,10 @@ import { Button } from '@penumbra-zone/ui/Button'; import { Dialog } from '@penumbra-zone/ui/Dialog'; import { Display } from '@penumbra-zone/ui/Display'; import { MenuItem } from '@penumbra-zone/ui/MenuItem'; -// import { StatusPopover } from './status-popover.tsx'; -import { HeaderLogo } from './logo.tsx'; -import { HEADER_LINKS } from './links.ts'; -import { Connection } from './connection.tsx'; +import { StatusPopover } from './status-popover'; +import { HeaderLogo } from './logo'; +import { HEADER_LINKS } from './links'; +import { Connection } from './connection'; export const MobileNav = () => { const router = useRouter(); @@ -31,7 +31,7 @@ export const MobileNav = () => {
- {/* */} +
); -}; +}); diff --git a/src/components/header/status-popover.tsx b/src/components/header/status-popover.tsx new file mode 100644 index 00000000..dfb4cd98 --- /dev/null +++ b/src/components/header/status-popover.tsx @@ -0,0 +1,66 @@ +import { useMemo } from 'react'; +import { Blocks } from 'lucide-react'; +import { Popover } from '@penumbra-zone/ui/Popover'; +import { Button } from '@penumbra-zone/ui/Button'; +import { Density } from '@penumbra-zone/ui/Density'; +import { Pill } from '@penumbra-zone/ui/Pill'; +import { Text } from '@penumbra-zone/ui/Text'; +import { statusStore } from '@/state/status'; +import { connectionStore } from '@/state/connection'; +import { observer } from 'mobx-react-lite'; + +export const StatusPopover = observer(() => { + const { loading, error, syncing, fullSyncHeight, latestKnownBlockHeight } = statusStore; + + // a ReactNode displaying the sync status in form of a pill + const pill = useMemo(() => { + if (error) { + return Block Sync Error; + } + + if (loading) { + return null; + } + + if (!syncing) { + return Blocks Synced; + } + + return Block Syncing; + }, [error, loading, syncing]); + + if (!connectionStore.connected) { + return null; + } + + return ( + + + + + + +
+
+ Status + {pill} + {error} +
+ {!loading && ( +
+ Block Height + + {latestKnownBlockHeight !== fullSyncHeight + ? `${fullSyncHeight} of ${latestKnownBlockHeight}` + : `${latestKnownBlockHeight}`} + +
+ )} +
+
+
+
+ ); +}); diff --git a/src/components/header/sync-bar.tsx b/src/components/header/sync-bar.tsx new file mode 100644 index 00000000..1512736e --- /dev/null +++ b/src/components/header/sync-bar.tsx @@ -0,0 +1,17 @@ +import { Progress } from '@penumbra-zone/ui/Progress'; +import { statusStore } from '@/state/status'; +import { observer } from 'mobx-react-lite'; + +export const SyncBar = observer(() => { + const { loading, error, syncPercent, updating } = statusStore; + + return ( +
+ {loading ? ( + + ) : ( + + )} +
+ ); +}); diff --git a/src/pages/block/[block_height].tsx b/src/pages/block/[block_height].tsx index ce911fc2..a3ee33b1 100644 --- a/src/pages/block/[block_height].tsx +++ b/src/pages/block/[block_height].tsx @@ -16,7 +16,7 @@ import { BlockDetailedSummaryData } from "@/utils/types/block"; import { BlockInfo, LiquidityPositionEvent } from "@/utils/indexer/types/lps"; import { SwapExecutionWithBlockHeight } from "@/utils/protos/types/DexQueryServiceClientInterface"; import { LoadingSpinner } from "@/components/util/loadingSpinner"; -import { Constants } from "@/constants/configConstants"; +import { Constants } from "@/utils/configConstants.ts"; import { formatTimestampShort } from "@/components/blockTimestamp"; import { AddIcon, MinusIcon } from "@chakra-ui/icons"; import { innerToBech32Address } from "@/utils/math/bech32"; diff --git a/src/state/connection/index.ts b/src/state/connection/index.ts new file mode 100644 index 00000000..2283771b --- /dev/null +++ b/src/state/connection/index.ts @@ -0,0 +1,82 @@ +import { PenumbraRequestFailure, PenumbraState, PenumbraManifest } from '@penumbra-zone/client'; +import { penumbra, PRAX_ORIGIN } from '@/utils/penumbra'; +import { makeAutoObservable } from 'mobx'; + +class ConnectionStateStore { + connected = false; + manifest: PenumbraManifest | undefined; + + constructor() { + makeAutoObservable(this); + + if (typeof window !== 'undefined') { + this.setup(); + } + } + + private setManifest(manifest: PenumbraManifest | undefined) { + this.manifest = manifest; + } + + private setConnected(connected: boolean) { + this.connected = connected; + } + + async reconnect() { + await penumbra.attach(PRAX_ORIGIN); + if (!penumbra.connected) { + return; + } + + try { + await penumbra.connect(); + this.setConnected(true); + } catch (error) { + /* no-op */ + } + } + + async connect() { + try { + await penumbra.connect(); + } catch (error) { + if (error instanceof Error && error.cause) { + if (error.cause === PenumbraRequestFailure.Denied) { + // TODO: replace these alerts with toasts + alert( + 'Connection denied: you may need to un-ignore this site in your extension settings.', + ); + } + if (error.cause === PenumbraRequestFailure.NeedsLogin) { + alert('Not logged in: please login into the extension and try again'); + } + } + } + } + + async disconnect() { + if (!penumbra.connected) { + return; + } + + try { + await penumbra.disconnect(); + } catch (error) { + console.error(error); + } + } + + setup() { + this.setManifest(penumbra.manifest); + + // If Prax is connected on page load, reconnect to ensure the connection is still active + void this.reconnect(); + + penumbra.onConnectionStateChange(event => { + this.setManifest(penumbra.manifest); + this.setConnected(event.state === PenumbraState.Connected); + }); + } +} + +export const connectionStore = new ConnectionStateStore(); diff --git a/src/state/status/getSyncPercent.ts b/src/state/status/getSyncPercent.ts new file mode 100644 index 00000000..24ae9bfa --- /dev/null +++ b/src/state/status/getSyncPercent.ts @@ -0,0 +1,20 @@ +export const getSyncPercent = ( + fullSyncHeight: bigint, + latestKnownBlockHeight: bigint, +): { syncPercent: number, syncPercentStringified: string } => { + let percentSyncedNumber = 0; + if (latestKnownBlockHeight) { + percentSyncedNumber = Number(fullSyncHeight) / Number(latestKnownBlockHeight); + if (percentSyncedNumber > 1) { + percentSyncedNumber = 1; + } + } + + // Round down to ensure whole numbers + const roundedPercentSyncedNumber = Math.floor(percentSyncedNumber * 100); + + return { + syncPercent: roundedPercentSyncedNumber / 100, + syncPercentStringified: `${roundedPercentSyncedNumber}%`, + } +}; diff --git a/src/state/status/index.ts b/src/state/status/index.ts new file mode 100644 index 00000000..7a63e068 --- /dev/null +++ b/src/state/status/index.ts @@ -0,0 +1,80 @@ +import { ViewService } from '@penumbra-zone/protobuf'; +import { penumbra } from '@/utils/penumbra'; +import { getSyncPercent } from '@/state/status/getSyncPercent'; +import { makeAutoObservable, runInAction, when } from 'mobx'; +import { + StatusResponse, + StatusStreamResponse, +} from '@penumbra-zone/protobuf/penumbra/view/v1/view_pb'; +import { connectionStore } from '@/state/connection'; + +class StatusState { + /** If true, ignore all other state values */ + loading = true; + /** The error is set in case of a request failure */ + error?: string; + /** Indicates that the account needs syncing with the blockchain */ + syncing = false; + /** Indicates that the account is almost in sync with the blockchain (amount of unsynced blocks is less than 10) */ + updating?: boolean = false; + /** The amount of synced blocks */ + fullSyncHeight = 0n; + /** The total amount of blocks in the blockchain */ + latestKnownBlockHeight?: bigint; + /** A number between 0 and 1 indicating the sync progress */ + syncPercent = 0; + /** A stringified sync percentage, e.g. '100%' or '17%' */ + syncPercentStringified = '0%'; + + constructor() { + makeAutoObservable(this); + + when( + () => connectionStore.connected, + () => void this.setup(), + ); + } + + async setup() { + try { + const status = await penumbra.service(ViewService).status({}); + this.setUnaryStatus(status); + + const stream = penumbra.service(ViewService).statusStream({}); + for await (const status of stream) { + this.setStreamedStatus(status); + } + } catch (error) { + runInAction(() => { + this.error = error instanceof Error ? `${error.name}: ${error.message}` : 'Streaming error'; + }); + setTimeout(() => void this.setup(), 1000); + } + } + + setUnaryStatus(status: StatusResponse) { + this.loading = false; + this.error = undefined; + this.syncing = status.catchingUp; + this.fullSyncHeight = status.fullSyncHeight; + this.latestKnownBlockHeight = status.catchingUp ? undefined : status.fullSyncHeight; + this.syncPercent = status.catchingUp ? 0 : 1; + this.syncPercentStringified = status.catchingUp ? '0%' : '100%'; + } + + setStreamedStatus(status: StatusStreamResponse) { + this.loading = false; + this.error = undefined; + this.syncing = status.fullSyncHeight !== status.latestKnownBlockHeight; + this.fullSyncHeight = status.fullSyncHeight; + this.latestKnownBlockHeight = status.latestKnownBlockHeight; + const { syncPercent, syncPercentStringified } = getSyncPercent( + status.fullSyncHeight, + status.latestKnownBlockHeight, + ); + this.syncPercent = syncPercent; + this.syncPercentStringified = syncPercentStringified; + } +} + +export const statusStore = new StatusState(); diff --git a/src/constants/configConstants.ts b/src/utils/configConstants.ts similarity index 100% rename from src/constants/configConstants.ts rename to src/utils/configConstants.ts diff --git a/src/utils/penumbra/penumbra.ts b/src/utils/penumbra.ts similarity index 100% rename from src/utils/penumbra/penumbra.ts rename to src/utils/penumbra.ts diff --git a/src/utils/penumbra/useConnect.ts b/src/utils/penumbra/useConnect.ts deleted file mode 100644 index c0103dc4..00000000 --- a/src/utils/penumbra/useConnect.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { useEffect, useState } from 'react'; -import { penumbra, PRAX_ORIGIN } from '@/utils/penumbra/penumbra.ts'; -import { PenumbraRequestFailure, PenumbraState, PenumbraManifest } from '@penumbra-zone/client'; - -export const useConnect = () => { - const [manifest, setManifest] = useState(); - const [connectionLoading, setConnectionLoading] = useState(false); - const [connected, setConnected] = useState(false); - - const reconnect = async () => { - await penumbra.attach(PRAX_ORIGIN); - if (!penumbra.connected) { - return; - } - - try { - await penumbra.connect(); - setConnected(true); - } catch (error) { - /* no-op */ - } - }; - - const connect = async () => { - try { - setConnectionLoading(true); - await penumbra.connect(); - } catch (error) { - if (error instanceof Error && error.cause) { - if (error.cause === PenumbraRequestFailure.Denied) { - // TODO: replace these alerts with toasts - alert('Connection denied: you may need to un-ignore this site in your extension settings.'); - } - if (error.cause === PenumbraRequestFailure.NeedsLogin) { - alert('Not logged in: please login into the extension and try again'); - } - } - } finally { - setConnectionLoading(false); - } - }; - - const disconnect = async () => { - if (!penumbra.connected) { - return; - } - - try { - await penumbra.disconnect(); - } catch (error) { - console.error(error); - } - }; - - // Monitors the connection - useEffect(() => { - setManifest(penumbra.manifest); - - // If Prax is connected on page load, reconnect to ensure the connection is still active - void reconnect(); - - penumbra.onConnectionStateChange((event) => { - setManifest(penumbra.manifest); - if (event.state === PenumbraState.Connected) { - setConnected(true); - } else { - setConnected(false); - } - }); - }, []); - - return { - manifest, - connectionLoading, - connected, - connect, - disconnect, - } -}; diff --git a/src/utils/token/tokenFetch.tsx b/src/utils/token/tokenFetch.tsx index 98df7f7b..85f40233 100644 --- a/src/utils/token/tokenFetch.tsx +++ b/src/utils/token/tokenFetch.tsx @@ -1,5 +1,5 @@ import { uint8ArrayToBase64, base64ToUint8Array } from "../math/base64"; -import { Constants } from "../../constants/configConstants"; +import { Constants } from "../configConstants.ts"; import { AssetId, AssetImage, diff --git a/styles/v2.css b/styles/v2.css index 1de6571d..98fd39bd 100644 --- a/styles/v2.css +++ b/styles/v2.css @@ -17,3 +17,8 @@ a { * { box-sizing: border-box; } + +body > section { + position: relative; + z-index: 0; +}