From 710a1cf9be36148ff2003e3489412e76474b95f5 Mon Sep 17 00:00:00 2001 From: valentine Date: Wed, 12 Jun 2024 01:36:03 +0300 Subject: [PATCH 01/19] use numeraire from local storage --- apps/extension/src/storage/local.ts | 1 + apps/extension/src/storage/onboard.ts | 20 ++++++++++++++++++++ apps/extension/src/storage/types.ts | 1 + apps/extension/src/utils/test-constants.ts | 1 + apps/extension/src/wallet-services.ts | 6 +++++- packages/query/src/block-processor.ts | 10 +++++----- packages/query/src/price-indexer.ts | 6 ++---- packages/services-context/src/index.ts | 6 ++++-- 8 files changed, 39 insertions(+), 12 deletions(-) diff --git a/apps/extension/src/storage/local.ts b/apps/extension/src/storage/local.ts index 59a2e93a..d8f7a870 100644 --- a/apps/extension/src/storage/local.ts +++ b/apps/extension/src/storage/local.ts @@ -10,6 +10,7 @@ export const localDefaults: Required = { params: undefined, passwordKeyPrint: undefined, frontendUrl: undefined, + numeraireAssetId: undefined }; // Meant to be used for long-term persisted data. It is cleared when the extension is removed. diff --git a/apps/extension/src/storage/onboard.ts b/apps/extension/src/storage/onboard.ts index 7ecad502..fefc2ba8 100644 --- a/apps/extension/src/storage/onboard.ts +++ b/apps/extension/src/storage/onboard.ts @@ -48,6 +48,26 @@ export const onboardWallet = async (): Promise => { }); }; + +export const onboardNumeraire = async (): Promise => { + const numeraireAssetId = await localExtStorage.get('numeraireAssetId'); + if (numeraireAssetId) return numeraireAssetId; + + return new Promise(resolve => { + const storageListener = (changes: Record) => { + const storageItem = changes['numeraireAssetId']?.newValue as + | StorageItem + | undefined; + const numeraireAssetId = storageItem?.value; + if (numeraireAssetId) { + resolve(numeraireAssetId); + localExtStorage.removeListener(storageListener); + } + }; + localExtStorage.addListener(storageListener); + }); +}; + /** This fixes an issue where some users do not have 'grpcEndpoint' set after they have finished onboarding */ diff --git a/apps/extension/src/storage/types.ts b/apps/extension/src/storage/types.ts index fec1a9fe..de14c1f2 100644 --- a/apps/extension/src/storage/types.ts +++ b/apps/extension/src/storage/types.ts @@ -23,4 +23,5 @@ export interface LocalStorageState { fullSyncHeight: number | undefined; knownSites: OriginRecord[]; params: Jsonified | undefined; + numeraireAssetId: string | undefined; } diff --git a/apps/extension/src/utils/test-constants.ts b/apps/extension/src/utils/test-constants.ts index 94bbb33b..793f3199 100644 --- a/apps/extension/src/utils/test-constants.ts +++ b/apps/extension/src/utils/test-constants.ts @@ -11,4 +11,5 @@ export const localTestDefaults: LocalStorageState = { grpcEndpoint: undefined, passwordKeyPrint: undefined, frontendUrl: EXAMPLE_MINIFRONT_URL, + numeraireAssetId: undefined }; diff --git a/apps/extension/src/wallet-services.ts b/apps/extension/src/wallet-services.ts index 58804302..ce713019 100644 --- a/apps/extension/src/wallet-services.ts +++ b/apps/extension/src/wallet-services.ts @@ -7,14 +7,17 @@ import { WalletId, } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb'; import { localExtStorage } from './storage/local'; -import { onboardGrpcEndpoint, onboardWallet } from './storage/onboard'; +import {onboardGrpcEndpoint, onboardNumeraire, onboardWallet} from './storage/onboard'; import { Services } from '@penumbra-zone/services-context'; import { ServicesMessage } from './message/services'; import { WalletServices } from '@penumbra-zone/types/services'; +import {AssetId} from "@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb"; export const startWalletServices = async () => { const wallet = await onboardWallet(); const grpcEndpoint = await onboardGrpcEndpoint(); + const numeraireAssetId = await onboardNumeraire(); + const services = new Services({ grpcEndpoint, @@ -22,6 +25,7 @@ export const startWalletServices = async () => { idbVersion: IDB_VERSION, walletId: WalletId.fromJsonString(wallet.id), fullViewingKey: FullViewingKey.fromJsonString(wallet.fullViewingKey), + numeraireAssetId: AssetId.fromJsonString(numeraireAssetId) }); const { blockProcessor, indexedDb } = await services.getWalletServices(); diff --git a/packages/query/src/block-processor.ts b/packages/query/src/block-processor.ts index 84f3629b..c1da50e5 100644 --- a/packages/query/src/block-processor.ts +++ b/packages/query/src/block-processor.ts @@ -57,7 +57,7 @@ interface QueryClientProps { querier: RootQuerier; indexedDb: IndexedDbInterface; viewServer: ViewServerInterface; - numeraires: Metadata[]; + numeraire: Metadata; stakingTokenMetadata: Metadata; } @@ -76,7 +76,7 @@ export class BlockProcessor implements BlockProcessorInterface { private readonly indexedDb: IndexedDbInterface; private readonly viewServer: ViewServerInterface; private readonly abortController: AbortController = new AbortController(); - private readonly numeraires: Metadata[]; + private readonly numeraire: Metadata; private readonly stakingTokenMetadata: Metadata; private syncPromise: Promise | undefined; @@ -84,13 +84,13 @@ export class BlockProcessor implements BlockProcessorInterface { indexedDb, viewServer, querier, - numeraires, + numeraire, stakingTokenMetadata, }: QueryClientProps) { this.indexedDb = indexedDb; this.viewServer = viewServer; this.querier = querier; - this.numeraires = numeraires; + this.numeraire = numeraire; this.stakingTokenMetadata = stakingTokenMetadata; } @@ -284,7 +284,7 @@ export class BlockProcessor implements BlockProcessorInterface { if (blockInPriceRelevanceThreshold && compactBlock.swapOutputs.length) { await updatePricesFromSwaps( this.indexedDb, - this.numeraires, + this.numeraire, compactBlock.swapOutputs, compactBlock.height, ); diff --git a/packages/query/src/price-indexer.ts b/packages/query/src/price-indexer.ts index ad15dc58..af216e78 100644 --- a/packages/query/src/price-indexer.ts +++ b/packages/query/src/price-indexer.ts @@ -41,14 +41,12 @@ export const calculatePrice = (delta: Amount, unfilled: Amount, lambda: Amount): export const updatePricesFromSwaps = async ( indexedDb: IndexedDbInterface, - numeraires: Metadata[], + numeraire: Metadata, swapOutputs: BatchSwapOutputData[], height: bigint, ) => { - for (const numeraireMetadata of numeraires) { - const numeraireAssetId = getAssetId(numeraireMetadata); + const numeraireAssetId = getAssetId(numeraire); await deriveAndSavePriceFromBSOD(indexedDb, numeraireAssetId, swapOutputs, height); - } }; /** diff --git a/packages/services-context/src/index.ts b/packages/services-context/src/index.ts index 95a860ed..59197af2 100644 --- a/packages/services-context/src/index.ts +++ b/packages/services-context/src/index.ts @@ -9,6 +9,7 @@ import { } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb'; import { ChainRegistryClient } from '@penumbra-labs/registry'; import { AppParameters } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/app/v1/app_pb'; +import {AssetId} from "@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb"; export interface ServicesConfig { readonly chainId: string; @@ -16,6 +17,7 @@ export interface ServicesConfig { readonly grpcEndpoint: string; readonly walletId: WalletId; readonly fullViewingKey: FullViewingKey; + readonly numeraireAssetId: AssetId; } export class Services implements ServicesInterface { @@ -82,7 +84,7 @@ export class Services implements ServicesInterface { } private async initializeWalletServices(): Promise { - const { chainId, grpcEndpoint, walletId, fullViewingKey, idbVersion } = this.config; + const { chainId, grpcEndpoint, walletId, fullViewingKey, idbVersion,numeraireAssetId } = this.config; const querier = new RootQuerier({ grpcEndpoint }); const registryClient = new ChainRegistryClient(); const indexedDb = await IndexedDb.initialize({ @@ -109,7 +111,7 @@ export class Services implements ServicesInterface { querier, indexedDb, stakingTokenMetadata: registry.getMetadata(registry.stakingAssetId), - numeraires: registry.numeraires.map(numeraires => registry.getMetadata(numeraires)), + numeraire: registry.getMetadata(numeraireAssetId), }); return { viewServer, blockProcessor, indexedDb, querier }; From 25bac211b635e905e7e214bd2998a6838fe9588b Mon Sep 17 00:00:00 2001 From: valentine Date: Wed, 12 Jun 2024 01:52:47 +0300 Subject: [PATCH 02/19] save multiple numeraires in local storage --- .../page/onboarding/default-frontend.tsx | 2 +- .../src/routes/page/onboarding/routes.tsx | 5 +++ .../routes/page/onboarding/set-numeraire.tsx | 33 +++++++++++++++++++ apps/extension/src/routes/page/paths.ts | 1 + apps/extension/src/storage/local.ts | 2 +- apps/extension/src/storage/onboard.ts | 19 +++++------ apps/extension/src/storage/types.ts | 2 +- apps/extension/src/utils/test-constants.ts | 2 +- apps/extension/src/wallet-services.ts | 9 +++-- packages/query/src/block-processor.ts | 10 +++--- packages/query/src/price-indexer.ts | 6 ++-- packages/services-context/src/index.ts | 8 ++--- 12 files changed, 69 insertions(+), 30 deletions(-) create mode 100644 apps/extension/src/routes/page/onboarding/set-numeraire.tsx diff --git a/apps/extension/src/routes/page/onboarding/default-frontend.tsx b/apps/extension/src/routes/page/onboarding/default-frontend.tsx index 959c32d4..349a3825 100644 --- a/apps/extension/src/routes/page/onboarding/default-frontend.tsx +++ b/apps/extension/src/routes/page/onboarding/default-frontend.tsx @@ -10,7 +10,7 @@ export const SetDefaultFrontendPage = () => { const onSubmit: FormEventHandler = (event): void => { event.preventDefault(); - navigate(PagePath.ONBOARDING_SUCCESS); + navigate(PagePath.SET_NUMERAIRES); }; return ( diff --git a/apps/extension/src/routes/page/onboarding/routes.tsx b/apps/extension/src/routes/page/onboarding/routes.tsx index 84d2fd0f..05d1726b 100644 --- a/apps/extension/src/routes/page/onboarding/routes.tsx +++ b/apps/extension/src/routes/page/onboarding/routes.tsx @@ -8,6 +8,7 @@ import { SetPassword } from './set-password'; import { pageIndexLoader } from '..'; import { SetGrpcEndpoint } from './set-grpc-endpoint'; import { SetDefaultFrontendPage } from './default-frontend'; +import { SetNumerairesPage } from './set-numeraire'; export const onboardingRoutes = [ { @@ -38,6 +39,10 @@ export const onboardingRoutes = [ path: PagePath.SET_DEFAULT_FRONTEND, element: , }, + { + path: PagePath.SET_NUMERAIRES, + element: , + }, { path: PagePath.ONBOARDING_SUCCESS, element: , diff --git a/apps/extension/src/routes/page/onboarding/set-numeraire.tsx b/apps/extension/src/routes/page/onboarding/set-numeraire.tsx new file mode 100644 index 00000000..60af5a74 --- /dev/null +++ b/apps/extension/src/routes/page/onboarding/set-numeraire.tsx @@ -0,0 +1,33 @@ +import { Card, CardDescription, CardHeader, CardTitle } from '@penumbra-zone/ui/components/ui/card'; +import { FadeTransition } from '@penumbra-zone/ui/components/ui/fade-transition'; +import { usePageNav } from '../../../utils/navigate'; +import { PagePath } from '../paths'; +import { DefaultFrontendForm } from '../../../shared/components/default-frontend-form'; +import { FormEventHandler } from 'react'; + +export const SetNumerairesPage = () => { + const navigate = usePageNav(); + + const onSubmit: FormEventHandler = (event): void => { + event.preventDefault(); + navigate(PagePath.ONBOARDING_SUCCESS); + }; + + return ( + + + + In which token denomination would you prefer to price assets? + + + + Prax has a shortcut for your portfolio page. You can always change this later + + +
+ + +
+
+ ); +}; diff --git a/apps/extension/src/routes/page/paths.ts b/apps/extension/src/routes/page/paths.ts index e965074c..a501389c 100644 --- a/apps/extension/src/routes/page/paths.ts +++ b/apps/extension/src/routes/page/paths.ts @@ -8,6 +8,7 @@ export enum PagePath { SET_PASSWORD = '/welcome/set-password', SET_GRPC_ENDPOINT = '/welcome/set-grpc-endpoint', SET_DEFAULT_FRONTEND = '/welcome/set-default-frontend', + SET_NUMERAIRES = '/welcome/set-numeraires', RESTORE_PASSWORD = '/restore-password', RESTORE_PASSWORD_INDEX = '/restore-password/', RESTORE_PASSWORD_SET_PASSWORD = '/restore-password/set-password', diff --git a/apps/extension/src/storage/local.ts b/apps/extension/src/storage/local.ts index d8f7a870..30fd3a4b 100644 --- a/apps/extension/src/storage/local.ts +++ b/apps/extension/src/storage/local.ts @@ -10,7 +10,7 @@ export const localDefaults: Required = { params: undefined, passwordKeyPrint: undefined, frontendUrl: undefined, - numeraireAssetId: undefined + numeraires: [], }; // Meant to be used for long-term persisted data. It is cleared when the extension is removed. diff --git a/apps/extension/src/storage/onboard.ts b/apps/extension/src/storage/onboard.ts index fefc2ba8..03c9b162 100644 --- a/apps/extension/src/storage/onboard.ts +++ b/apps/extension/src/storage/onboard.ts @@ -48,19 +48,18 @@ export const onboardWallet = async (): Promise => { }); }; - -export const onboardNumeraire = async (): Promise => { - const numeraireAssetId = await localExtStorage.get('numeraireAssetId'); - if (numeraireAssetId) return numeraireAssetId; +export const onboardNumeraires = async (): Promise => { + const numeraires = await localExtStorage.get('numeraires'); + if (numeraires) return numeraires; return new Promise(resolve => { const storageListener = (changes: Record) => { - const storageItem = changes['numeraireAssetId']?.newValue as - | StorageItem - | undefined; - const numeraireAssetId = storageItem?.value; - if (numeraireAssetId) { - resolve(numeraireAssetId); + const storageItem = changes['numeraires']?.newValue as + | StorageItem + | undefined; + const numeraires = storageItem?.value; + if (numeraires) { + resolve(numeraires); localExtStorage.removeListener(storageListener); } }; diff --git a/apps/extension/src/storage/types.ts b/apps/extension/src/storage/types.ts index de14c1f2..603bec8a 100644 --- a/apps/extension/src/storage/types.ts +++ b/apps/extension/src/storage/types.ts @@ -23,5 +23,5 @@ export interface LocalStorageState { fullSyncHeight: number | undefined; knownSites: OriginRecord[]; params: Jsonified | undefined; - numeraireAssetId: string | undefined; + numeraires: string[]; } diff --git a/apps/extension/src/utils/test-constants.ts b/apps/extension/src/utils/test-constants.ts index 793f3199..9f1e848a 100644 --- a/apps/extension/src/utils/test-constants.ts +++ b/apps/extension/src/utils/test-constants.ts @@ -11,5 +11,5 @@ export const localTestDefaults: LocalStorageState = { grpcEndpoint: undefined, passwordKeyPrint: undefined, frontendUrl: EXAMPLE_MINIFRONT_URL, - numeraireAssetId: undefined + numeraires: [], }; diff --git a/apps/extension/src/wallet-services.ts b/apps/extension/src/wallet-services.ts index ce713019..4b54ed00 100644 --- a/apps/extension/src/wallet-services.ts +++ b/apps/extension/src/wallet-services.ts @@ -7,17 +7,16 @@ import { WalletId, } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb'; import { localExtStorage } from './storage/local'; -import {onboardGrpcEndpoint, onboardNumeraire, onboardWallet} from './storage/onboard'; +import { onboardGrpcEndpoint, onboardNumeraires, onboardWallet } from './storage/onboard'; import { Services } from '@penumbra-zone/services-context'; import { ServicesMessage } from './message/services'; import { WalletServices } from '@penumbra-zone/types/services'; -import {AssetId} from "@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb"; +import { AssetId } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; export const startWalletServices = async () => { const wallet = await onboardWallet(); const grpcEndpoint = await onboardGrpcEndpoint(); - const numeraireAssetId = await onboardNumeraire(); - + const numeraires = await onboardNumeraires(); const services = new Services({ grpcEndpoint, @@ -25,7 +24,7 @@ export const startWalletServices = async () => { idbVersion: IDB_VERSION, walletId: WalletId.fromJsonString(wallet.id), fullViewingKey: FullViewingKey.fromJsonString(wallet.fullViewingKey), - numeraireAssetId: AssetId.fromJsonString(numeraireAssetId) + numeraires: numeraires.map(n => AssetId.fromJsonString(n)), }); const { blockProcessor, indexedDb } = await services.getWalletServices(); diff --git a/packages/query/src/block-processor.ts b/packages/query/src/block-processor.ts index c1da50e5..84f3629b 100644 --- a/packages/query/src/block-processor.ts +++ b/packages/query/src/block-processor.ts @@ -57,7 +57,7 @@ interface QueryClientProps { querier: RootQuerier; indexedDb: IndexedDbInterface; viewServer: ViewServerInterface; - numeraire: Metadata; + numeraires: Metadata[]; stakingTokenMetadata: Metadata; } @@ -76,7 +76,7 @@ export class BlockProcessor implements BlockProcessorInterface { private readonly indexedDb: IndexedDbInterface; private readonly viewServer: ViewServerInterface; private readonly abortController: AbortController = new AbortController(); - private readonly numeraire: Metadata; + private readonly numeraires: Metadata[]; private readonly stakingTokenMetadata: Metadata; private syncPromise: Promise | undefined; @@ -84,13 +84,13 @@ export class BlockProcessor implements BlockProcessorInterface { indexedDb, viewServer, querier, - numeraire, + numeraires, stakingTokenMetadata, }: QueryClientProps) { this.indexedDb = indexedDb; this.viewServer = viewServer; this.querier = querier; - this.numeraire = numeraire; + this.numeraires = numeraires; this.stakingTokenMetadata = stakingTokenMetadata; } @@ -284,7 +284,7 @@ export class BlockProcessor implements BlockProcessorInterface { if (blockInPriceRelevanceThreshold && compactBlock.swapOutputs.length) { await updatePricesFromSwaps( this.indexedDb, - this.numeraire, + this.numeraires, compactBlock.swapOutputs, compactBlock.height, ); diff --git a/packages/query/src/price-indexer.ts b/packages/query/src/price-indexer.ts index af216e78..ad15dc58 100644 --- a/packages/query/src/price-indexer.ts +++ b/packages/query/src/price-indexer.ts @@ -41,12 +41,14 @@ export const calculatePrice = (delta: Amount, unfilled: Amount, lambda: Amount): export const updatePricesFromSwaps = async ( indexedDb: IndexedDbInterface, - numeraire: Metadata, + numeraires: Metadata[], swapOutputs: BatchSwapOutputData[], height: bigint, ) => { - const numeraireAssetId = getAssetId(numeraire); + for (const numeraireMetadata of numeraires) { + const numeraireAssetId = getAssetId(numeraireMetadata); await deriveAndSavePriceFromBSOD(indexedDb, numeraireAssetId, swapOutputs, height); + } }; /** diff --git a/packages/services-context/src/index.ts b/packages/services-context/src/index.ts index 59197af2..0593c8f7 100644 --- a/packages/services-context/src/index.ts +++ b/packages/services-context/src/index.ts @@ -9,7 +9,7 @@ import { } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb'; import { ChainRegistryClient } from '@penumbra-labs/registry'; import { AppParameters } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/app/v1/app_pb'; -import {AssetId} from "@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb"; +import { AssetId } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; export interface ServicesConfig { readonly chainId: string; @@ -17,7 +17,7 @@ export interface ServicesConfig { readonly grpcEndpoint: string; readonly walletId: WalletId; readonly fullViewingKey: FullViewingKey; - readonly numeraireAssetId: AssetId; + readonly numeraires: AssetId[]; } export class Services implements ServicesInterface { @@ -84,7 +84,7 @@ export class Services implements ServicesInterface { } private async initializeWalletServices(): Promise { - const { chainId, grpcEndpoint, walletId, fullViewingKey, idbVersion,numeraireAssetId } = this.config; + const { chainId, grpcEndpoint, walletId, fullViewingKey, idbVersion, numeraires } = this.config; const querier = new RootQuerier({ grpcEndpoint }); const registryClient = new ChainRegistryClient(); const indexedDb = await IndexedDb.initialize({ @@ -111,7 +111,7 @@ export class Services implements ServicesInterface { querier, indexedDb, stakingTokenMetadata: registry.getMetadata(registry.stakingAssetId), - numeraire: registry.getMetadata(numeraireAssetId), + numeraires: numeraires.map(registry.getMetadata), }); return { viewServer, blockProcessor, indexedDb, querier }; From 339e6368393c4c212516236ac9dccd887e6baa7a Mon Sep 17 00:00:00 2001 From: valentine Date: Wed, 12 Jun 2024 02:07:03 +0300 Subject: [PATCH 03/19] added NumerairesSlice --- .../shared/components/numeraires/index.tsx | 81 +++++++++++++++++++ apps/extension/src/state/index.ts | 3 + apps/extension/src/state/numeraires.ts | 25 ++++++ 3 files changed, 109 insertions(+) create mode 100644 apps/extension/src/shared/components/numeraires/index.tsx create mode 100644 apps/extension/src/state/numeraires.ts diff --git a/apps/extension/src/shared/components/numeraires/index.tsx b/apps/extension/src/shared/components/numeraires/index.tsx new file mode 100644 index 00000000..63828282 --- /dev/null +++ b/apps/extension/src/shared/components/numeraires/index.tsx @@ -0,0 +1,81 @@ +import { SelectList } from '@penumbra-zone/ui/components/ui/select-list'; +import { ChainRegistryClient } from '@penumbra-labs/registry'; +import { AllSlices } from '../../../state'; +import { useStoreShallow } from '../../../utils/use-store-shallow'; +import { useMemo, useRef } from 'react'; +import { Button } from '@penumbra-zone/ui/components/ui/button'; +import { useIsFocus } from './use-is-focus'; +import { extractDomain } from './extract-domain'; +import {Metadata} from "@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb"; + + +const getNumeraireFromRegistry = (selectedRpc?: string): Metadata[] => { + const registryClient = new ChainRegistryClient(); + const { frontends } = registryClient.globals(); + + const registeredFrontends = frontends.map(frontend => ({ + title: extractDomain(frontend), + url: frontend, + })); + + if (selectedRpc) { + registeredFrontends.push({ title: 'Embedded RPC frontend', url: `${selectedRpc}/app/` }); + } + + return registeredFrontends; +}; + + +const useDefaultFrontendSelector = (state: AllSlices) => { + return { + selectedFrontend: state.defaultFrontend.url, + selectUrl: state.defaultFrontend.setUrl, + selectedRpc: state.network.grpcEndpoint, + }; +}; + +export const DefaultFrontendForm = ({ isOnboarding }: { isOnboarding?: boolean }) => { + const { selectedFrontend, selectUrl, selectedRpc } = useStoreShallow(useDefaultFrontendSelector); + const frontends = useMemo(() => getFrontendsFromRegistry(selectedRpc), [selectedRpc]); + + const inputRef = useRef(null); + const isFocused = useIsFocus(inputRef); + + return ( + + {frontends.map(option => ( + + ))} + + + + + {(isOnboarding ?? isFocused) && ( + + )} + + ); +}; diff --git a/apps/extension/src/state/index.ts b/apps/extension/src/state/index.ts index 945760df..2f0ab409 100644 --- a/apps/extension/src/state/index.ts +++ b/apps/extension/src/state/index.ts @@ -13,12 +13,14 @@ import { createTxApprovalSlice, TxApprovalSlice } from './tx-approval'; import { createOriginApprovalSlice, OriginApprovalSlice } from './origin-approval'; import { ConnectedSitesSlice, createConnectedSitesSlice } from './connected-sites'; import { createDefaultFrontendSlice, DefaultFrontendSlice } from './default-frontend'; +import {createNumerairesSlice, NumerairesSlice} from "./numeraires"; export interface AllSlices { wallets: WalletsSlice; password: PasswordSlice; seedPhrase: SeedPhraseSlice; network: NetworkSlice; + numeraires: NumerairesSlice; txApproval: TxApprovalSlice; originApproval: OriginApprovalSlice; connectedSites: ConnectedSitesSlice; @@ -41,6 +43,7 @@ export const initializeStore = ( password: createPasswordSlice(session, local)(setState, getState, store), seedPhrase: createSeedPhraseSlice(setState, getState, store), network: createNetworkSlice(local)(setState, getState, store), + numeraires: createNumerairesSlice(local)(setState,getState, store), connectedSites: createConnectedSitesSlice(local)(setState, getState, store), txApproval: createTxApprovalSlice()(setState, getState, store), originApproval: createOriginApprovalSlice()(setState, getState, store), diff --git a/apps/extension/src/state/numeraires.ts b/apps/extension/src/state/numeraires.ts new file mode 100644 index 00000000..57d5d661 --- /dev/null +++ b/apps/extension/src/state/numeraires.ts @@ -0,0 +1,25 @@ +import { LocalStorageState } from '../storage/types'; +import { ExtensionStorage } from '../storage/base'; +import { AllSlices, SliceCreator } from '.'; + +export interface NumerairesSlice { + numeraires: string []; + setNumeraires: (numeraires: string []) => Promise; +} + +export const createNumerairesSlice = + (local: ExtensionStorage): SliceCreator => + set => { + return { + numeraires: [], + setNumeraires: async (numeraires: string []) => { + set(state => { + state.numeraires.numeraires = numeraires; + }); + + await local.set('numeraires', numeraires); + }, + }; + }; + +export const numerairesSelector = (state: AllSlices) => state.numeraires; From 10be0381355ab085e02746b7a3d4903d8cd25471 Mon Sep 17 00:00:00 2001 From: valentine Date: Wed, 12 Jun 2024 16:12:06 +0300 Subject: [PATCH 04/19] add ChangeNumeraires service message --- apps/extension/package.json | 1 + apps/extension/src/message/services.ts | 1 + .../routes/page/onboarding/set-numeraire.tsx | 4 +- .../shared/components/numeraires/index.tsx | 122 ++++++++---------- apps/extension/src/state/index.ts | 4 +- apps/extension/src/state/numeraires.ts | 31 ++--- apps/extension/src/wallet-services.ts | 6 + packages/query/src/block-processor.ts | 8 +- packages/query/src/price-indexer.ts | 11 +- packages/services-context/src/index.ts | 2 +- packages/types/src/block-processor.ts | 3 + pnpm-lock.yaml | 3 + 12 files changed, 101 insertions(+), 95 deletions(-) diff --git a/apps/extension/package.json b/apps/extension/package.json index d7d53e29..bf1aba92 100644 --- a/apps/extension/package.json +++ b/apps/extension/package.json @@ -22,6 +22,7 @@ "@penumbra-zone/bech32m": "workspace:*", "@penumbra-zone/client": "workspace:*", "@penumbra-zone/crypto-web": "workspace:*", + "@penumbra-zone/getters": "workspace:*", "@penumbra-zone/perspective": "workspace:*", "@penumbra-zone/protobuf": "workspace:*", "@penumbra-zone/query": "workspace:*", diff --git a/apps/extension/src/message/services.ts b/apps/extension/src/message/services.ts index 1eede672..23de08e4 100644 --- a/apps/extension/src/message/services.ts +++ b/apps/extension/src/message/services.ts @@ -1,3 +1,4 @@ export enum ServicesMessage { ClearCache = 'ClearCache', + ChangeNumeraires = 'ChangeNumeraires', } diff --git a/apps/extension/src/routes/page/onboarding/set-numeraire.tsx b/apps/extension/src/routes/page/onboarding/set-numeraire.tsx index 60af5a74..1998e71c 100644 --- a/apps/extension/src/routes/page/onboarding/set-numeraire.tsx +++ b/apps/extension/src/routes/page/onboarding/set-numeraire.tsx @@ -2,8 +2,8 @@ import { Card, CardDescription, CardHeader, CardTitle } from '@penumbra-zone/ui/ import { FadeTransition } from '@penumbra-zone/ui/components/ui/fade-transition'; import { usePageNav } from '../../../utils/navigate'; import { PagePath } from '../paths'; -import { DefaultFrontendForm } from '../../../shared/components/default-frontend-form'; import { FormEventHandler } from 'react'; +import { NumeraireForm } from '../../../shared/components/numeraires'; export const SetNumerairesPage = () => { const navigate = usePageNav(); @@ -25,7 +25,7 @@ export const SetNumerairesPage = () => {
- + diff --git a/apps/extension/src/shared/components/numeraires/index.tsx b/apps/extension/src/shared/components/numeraires/index.tsx index 63828282..fb19ba51 100644 --- a/apps/extension/src/shared/components/numeraires/index.tsx +++ b/apps/extension/src/shared/components/numeraires/index.tsx @@ -4,78 +4,70 @@ import { AllSlices } from '../../../state'; import { useStoreShallow } from '../../../utils/use-store-shallow'; import { useMemo, useRef } from 'react'; import { Button } from '@penumbra-zone/ui/components/ui/button'; -import { useIsFocus } from './use-is-focus'; -import { extractDomain } from './extract-domain'; -import {Metadata} from "@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb"; +import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; +import { useChainIdQuery } from '../../../hooks/chain-id'; +import { bech32mAssetId } from '@penumbra-zone/bech32m/passet'; +import { getAssetId } from '@penumbra-zone/getters/metadata'; +import { useIsFocus } from '../default-frontend-form/use-is-focus'; - -const getNumeraireFromRegistry = (selectedRpc?: string): Metadata[] => { - const registryClient = new ChainRegistryClient(); - const { frontends } = registryClient.globals(); - - const registeredFrontends = frontends.map(frontend => ({ - title: extractDomain(frontend), - url: frontend, - })); - - if (selectedRpc) { - registeredFrontends.push({ title: 'Embedded RPC frontend', url: `${selectedRpc}/app/` }); - } - - return registeredFrontends; +const getNumeraireFromRegistry = (chainId: string): Metadata[] => { + const registryClient = new ChainRegistryClient(); + const registry = registryClient.get(chainId); + return registry.numeraires.map(n => registry.getMetadata(n)); }; - -const useDefaultFrontendSelector = (state: AllSlices) => { - return { - selectedFrontend: state.defaultFrontend.url, - selectUrl: state.defaultFrontend.setUrl, - selectedRpc: state.network.grpcEndpoint, - }; +const useNumerairesSelector = (state: AllSlices) => { + return { + selectedNumeraires: state.numeraires.selectedNumeraires, + setNumeraires: state.numeraires.addNumeraire, + }; }; -export const DefaultFrontendForm = ({ isOnboarding }: { isOnboarding?: boolean }) => { - const { selectedFrontend, selectUrl, selectedRpc } = useStoreShallow(useDefaultFrontendSelector); - const frontends = useMemo(() => getFrontendsFromRegistry(selectedRpc), [selectedRpc]); - - const inputRef = useRef(null); - const isFocused = useIsFocus(inputRef); +export const NumeraireForm = ({ isOnboarding }: { isOnboarding?: boolean }) => { + const { chainId } = useChainIdQuery(); + const { selectedNumeraires, setNumeraires } = useStoreShallow(useNumerairesSelector); + const frontends = useMemo( + () => getNumeraireFromRegistry(chainId || 'penumbra-testnet-deimos-8'), + [chainId], + ); - return ( - - {frontends.map(option => ( - - ))} + const inputRef = useRef(null); + const isFocused = useIsFocus(inputRef); + return ( + + {frontends.map(option => ( + + ))} - + - {(isOnboarding ?? isFocused) && ( - - )} - - ); + {(isOnboarding ?? isFocused) && ( + + )} + + ); }; diff --git a/apps/extension/src/state/index.ts b/apps/extension/src/state/index.ts index 2f0ab409..201d7033 100644 --- a/apps/extension/src/state/index.ts +++ b/apps/extension/src/state/index.ts @@ -13,7 +13,7 @@ import { createTxApprovalSlice, TxApprovalSlice } from './tx-approval'; import { createOriginApprovalSlice, OriginApprovalSlice } from './origin-approval'; import { ConnectedSitesSlice, createConnectedSitesSlice } from './connected-sites'; import { createDefaultFrontendSlice, DefaultFrontendSlice } from './default-frontend'; -import {createNumerairesSlice, NumerairesSlice} from "./numeraires"; +import { createNumerairesSlice, NumerairesSlice } from './numeraires'; export interface AllSlices { wallets: WalletsSlice; @@ -43,7 +43,7 @@ export const initializeStore = ( password: createPasswordSlice(session, local)(setState, getState, store), seedPhrase: createSeedPhraseSlice(setState, getState, store), network: createNetworkSlice(local)(setState, getState, store), - numeraires: createNumerairesSlice(local)(setState,getState, store), + numeraires: createNumerairesSlice(local)(setState, getState, store), connectedSites: createConnectedSitesSlice(local)(setState, getState, store), txApproval: createTxApprovalSlice()(setState, getState, store), originApproval: createOriginApprovalSlice()(setState, getState, store), diff --git a/apps/extension/src/state/numeraires.ts b/apps/extension/src/state/numeraires.ts index 57d5d661..9c6123ee 100644 --- a/apps/extension/src/state/numeraires.ts +++ b/apps/extension/src/state/numeraires.ts @@ -1,25 +1,26 @@ import { LocalStorageState } from '../storage/types'; import { ExtensionStorage } from '../storage/base'; import { AllSlices, SliceCreator } from '.'; +import type { Stringified } from '@penumbra-zone/types/jsonified'; +import { AssetId } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; export interface NumerairesSlice { - numeraires: string []; - setNumeraires: (numeraires: string []) => Promise; + selectedNumeraires: Stringified[]; + addNumeraire: (numeraire: Stringified) => Promise; } export const createNumerairesSlice = - (local: ExtensionStorage): SliceCreator => - set => { - return { - numeraires: [], - setNumeraires: async (numeraires: string []) => { - set(state => { - state.numeraires.numeraires = numeraires; - }); - - await local.set('numeraires', numeraires); - }, - }; - }; + (local: ExtensionStorage): SliceCreator => + set => { + return { + selectedNumeraires: [], + addNumeraire: async (numeraire: Stringified) => { + set(state => { + state.numeraires.selectedNumeraires.push(numeraire); + void local.set('numeraires', state.numeraires.selectedNumeraires); + }); + }, + }; + }; export const numerairesSelector = (state: AllSlices) => state.numeraires; diff --git a/apps/extension/src/wallet-services.ts b/apps/extension/src/wallet-services.ts index 4b54ed00..84f32cfb 100644 --- a/apps/extension/src/wallet-services.ts +++ b/apps/extension/src/wallet-services.ts @@ -92,5 +92,11 @@ const attachServiceControlListener = ({ .then(() => respond()) .finally(() => chrome.runtime.reload()); return true; + case ServicesMessage.ChangeNumeraires: + void (async () => { + let newNumeraires = await localExtStorage.get('numeraires'); + blockProcessor.setNumeraires(newNumeraires.map(n => AssetId.fromJsonString(n))); + }); + return true; } }); diff --git a/packages/query/src/block-processor.ts b/packages/query/src/block-processor.ts index 84f3629b..5af75348 100644 --- a/packages/query/src/block-processor.ts +++ b/packages/query/src/block-processor.ts @@ -57,7 +57,7 @@ interface QueryClientProps { querier: RootQuerier; indexedDb: IndexedDbInterface; viewServer: ViewServerInterface; - numeraires: Metadata[]; + numeraires: AssetId[]; stakingTokenMetadata: Metadata; } @@ -76,7 +76,7 @@ export class BlockProcessor implements BlockProcessorInterface { private readonly indexedDb: IndexedDbInterface; private readonly viewServer: ViewServerInterface; private readonly abortController: AbortController = new AbortController(); - private readonly numeraires: Metadata[]; + private numeraires: AssetId[]; private readonly stakingTokenMetadata: Metadata; private syncPromise: Promise | undefined; @@ -116,6 +116,10 @@ export class BlockProcessor implements BlockProcessorInterface { public stop = (r: string) => this.abortController.abort(`Sync stop ${r}`); + setNumeraires(numeraires: AssetId[]): void { + this.numeraires = numeraires; + } + private async syncAndStore() { // start at next block, or genesis if height is undefined let currentHeight = (await this.indexedDb.getFullSyncHeight()) ?? -1n; diff --git a/packages/query/src/price-indexer.ts b/packages/query/src/price-indexer.ts index ad15dc58..0ce4f4d6 100644 --- a/packages/query/src/price-indexer.ts +++ b/packages/query/src/price-indexer.ts @@ -1,10 +1,7 @@ import { BatchSwapOutputData } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/dex/v1/dex_pb'; import { IndexedDbInterface } from '@penumbra-zone/types/indexed-db'; import { divideAmounts, isZero, subtractAmounts } from '@penumbra-zone/types/amount'; -import { - AssetId, - Metadata, -} from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; +import { AssetId } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; import { Amount } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/num/v1/num_pb'; import { getDelta1Amount, @@ -16,7 +13,6 @@ import { getUnfilled1Amount, getUnfilled2Amount, } from '@penumbra-zone/getters/batch-swap-output-data'; -import { getAssetId } from '@penumbra-zone/getters/metadata'; /** * @@ -41,12 +37,11 @@ export const calculatePrice = (delta: Amount, unfilled: Amount, lambda: Amount): export const updatePricesFromSwaps = async ( indexedDb: IndexedDbInterface, - numeraires: Metadata[], + numeraires: AssetId[], swapOutputs: BatchSwapOutputData[], height: bigint, ) => { - for (const numeraireMetadata of numeraires) { - const numeraireAssetId = getAssetId(numeraireMetadata); + for (const numeraireAssetId of numeraires) { await deriveAndSavePriceFromBSOD(indexedDb, numeraireAssetId, swapOutputs, height); } }; diff --git a/packages/services-context/src/index.ts b/packages/services-context/src/index.ts index 0593c8f7..48b2e62a 100644 --- a/packages/services-context/src/index.ts +++ b/packages/services-context/src/index.ts @@ -111,7 +111,7 @@ export class Services implements ServicesInterface { querier, indexedDb, stakingTokenMetadata: registry.getMetadata(registry.stakingAssetId), - numeraires: numeraires.map(registry.getMetadata), + numeraires: numeraires, }); return { viewServer, blockProcessor, indexedDb, querier }; diff --git a/packages/types/src/block-processor.ts b/packages/types/src/block-processor.ts index 399769dd..8288a9fa 100644 --- a/packages/types/src/block-processor.ts +++ b/packages/types/src/block-processor.ts @@ -1,4 +1,7 @@ +import { AssetId } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; + export interface BlockProcessorInterface { sync(): Promise; stop(r?: string): void; + setNumeraires(numeraires: AssetId[]): void; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3ebd1f8a..76df5480 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -150,6 +150,9 @@ importers: '@penumbra-zone/crypto-web': specifier: workspace:* version: link:../../packages/crypto + '@penumbra-zone/getters': + specifier: workspace:* + version: link:../../packages/getters '@penumbra-zone/perspective': specifier: workspace:* version: link:../../packages/perspective From 9701986c0a0ff9a887b3395ee80d846190345732 Mon Sep 17 00:00:00 2001 From: valentine Date: Wed, 12 Jun 2024 17:21:32 +0300 Subject: [PATCH 05/19] numeraires persist --- .../shared/components/numeraires/index.tsx | 73 +++++++++---------- apps/extension/src/state/numeraires.ts | 22 ++++-- apps/extension/src/state/persist.ts | 13 ++++ apps/extension/src/storage/types.ts | 5 +- 4 files changed, 66 insertions(+), 47 deletions(-) diff --git a/apps/extension/src/shared/components/numeraires/index.tsx b/apps/extension/src/shared/components/numeraires/index.tsx index fb19ba51..8d04dc2f 100644 --- a/apps/extension/src/shared/components/numeraires/index.tsx +++ b/apps/extension/src/shared/components/numeraires/index.tsx @@ -2,13 +2,12 @@ import { SelectList } from '@penumbra-zone/ui/components/ui/select-list'; import { ChainRegistryClient } from '@penumbra-labs/registry'; import { AllSlices } from '../../../state'; import { useStoreShallow } from '../../../utils/use-store-shallow'; -import { useMemo, useRef } from 'react'; +import { useMemo } from 'react'; import { Button } from '@penumbra-zone/ui/components/ui/button'; import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; import { useChainIdQuery } from '../../../hooks/chain-id'; import { bech32mAssetId } from '@penumbra-zone/bech32m/passet'; import { getAssetId } from '@penumbra-zone/getters/metadata'; -import { useIsFocus } from '../default-frontend-form/use-is-focus'; const getNumeraireFromRegistry = (chainId: string): Metadata[] => { const registryClient = new ChainRegistryClient(); @@ -19,55 +18,53 @@ const getNumeraireFromRegistry = (chainId: string): Metadata[] => { const useNumerairesSelector = (state: AllSlices) => { return { selectedNumeraires: state.numeraires.selectedNumeraires, - setNumeraires: state.numeraires.addNumeraire, + selectNumeraire: state.numeraires.selectNumeraire, + saveNumeraires: state.numeraires.saveNumeraires, }; }; export const NumeraireForm = ({ isOnboarding }: { isOnboarding?: boolean }) => { const { chainId } = useChainIdQuery(); - const { selectedNumeraires, setNumeraires } = useStoreShallow(useNumerairesSelector); + const { selectedNumeraires, selectNumeraire, saveNumeraires } = + useStoreShallow(useNumerairesSelector); const frontends = useMemo( () => getNumeraireFromRegistry(chainId || 'penumbra-testnet-deimos-8'), [chainId], ); - const inputRef = useRef(null); - const isFocused = useIsFocus(inputRef); - return ( - {frontends.map(option => ( - - ))} - - + {frontends.map(metadata => { + const icon = metadata?.images[0]?.png || metadata?.images[0]?.svg; + return ( + selectNumeraire(getAssetId(metadata).toJsonString())} + image={ + !!icon && ( + rpc endpoint brand image + ) + } + /> + ); + })} - {(isOnboarding ?? isFocused) && ( - - )} + ); }; diff --git a/apps/extension/src/state/numeraires.ts b/apps/extension/src/state/numeraires.ts index 9c6123ee..ec536f3f 100644 --- a/apps/extension/src/state/numeraires.ts +++ b/apps/extension/src/state/numeraires.ts @@ -1,25 +1,33 @@ import { LocalStorageState } from '../storage/types'; -import { ExtensionStorage } from '../storage/base'; import { AllSlices, SliceCreator } from '.'; -import type { Stringified } from '@penumbra-zone/types/jsonified'; +import { ExtensionStorage } from '../storage/base'; +import { Stringified } from '@penumbra-zone/types/jsonified'; import { AssetId } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; export interface NumerairesSlice { selectedNumeraires: Stringified[]; - addNumeraire: (numeraire: Stringified) => Promise; + selectNumeraire: (numeraire: Stringified) => void; + saveNumeraires: () => void; } export const createNumerairesSlice = (local: ExtensionStorage): SliceCreator => - set => { + (set, get) => { return { selectedNumeraires: [], - addNumeraire: async (numeraire: Stringified) => { + selectNumeraire: (numeraire: Stringified) => { set(state => { - state.numeraires.selectedNumeraires.push(numeraire); - void local.set('numeraires', state.numeraires.selectedNumeraires); + const index = state.numeraires.selectedNumeraires.indexOf(numeraire); + if (index > -1) { + state.numeraires.selectedNumeraires.splice(index, 1); + } else { + state.numeraires.selectedNumeraires.push(numeraire); + } }); }, + saveNumeraires: () => { + void local.set('numeraires', get().numeraires.selectedNumeraires); + }, }; }; diff --git a/apps/extension/src/state/persist.ts b/apps/extension/src/state/persist.ts index 9cfb1793..2daf7a3b 100644 --- a/apps/extension/src/state/persist.ts +++ b/apps/extension/src/state/persist.ts @@ -31,6 +31,7 @@ export const customPersistImpl: Persist = f => (set, get, store) => { const grpcEndpoint = await localExtStorage.get('grpcEndpoint'); const knownSites = await localExtStorage.get('knownSites'); const frontendUrl = await localExtStorage.get('frontendUrl'); + const numeraires = await localExtStorage.get('numeraires'); set( produce((state: AllSlices) => { @@ -39,6 +40,7 @@ export const customPersistImpl: Persist = f => (set, get, store) => { state.network.grpcEndpoint = grpcEndpoint; state.connectedSites.knownSites = knownSites; state.defaultFrontend.url = frontendUrl; + state.numeraires.selectedNumeraires = numeraires; }), ); @@ -107,6 +109,17 @@ function syncLocal(changes: Record, set: S }), ); } + + if (changes['numeraires']) { + const stored = changes['numeraires'].newValue as + | StorageItem + | undefined; + set( + produce((state: AllSlices) => { + state.numeraires.selectedNumeraires = stored?.value ?? state.numeraires.selectedNumeraires; + }), + ); + } } function syncSession(changes: Record, set: Setter) { diff --git a/apps/extension/src/storage/types.ts b/apps/extension/src/storage/types.ts index 603bec8a..2f1d88f7 100644 --- a/apps/extension/src/storage/types.ts +++ b/apps/extension/src/storage/types.ts @@ -1,8 +1,9 @@ import { AppParameters } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/app/v1/app_pb'; import { KeyPrintJson } from '@penumbra-zone/crypto-web/encryption'; -import { Jsonified } from '@penumbra-zone/types/jsonified'; +import { Jsonified, Stringified } from '@penumbra-zone/types/jsonified'; import { UserChoice } from '@penumbra-zone/types/user-choice'; import { WalletJson } from '@penumbra-zone/types/wallet'; +import { AssetId } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; export enum LocalStorageVersion { V1 = 'V1', @@ -23,5 +24,5 @@ export interface LocalStorageState { fullSyncHeight: number | undefined; knownSites: OriginRecord[]; params: Jsonified | undefined; - numeraires: string[]; + numeraires: Stringified[]; } From c427ddf6fb3a0c1a1da920e4993f4b12cc7da256 Mon Sep 17 00:00:00 2001 From: valentine Date: Wed, 12 Jun 2024 18:11:09 +0300 Subject: [PATCH 06/19] add icons --- .../src/icons/numeraires-gradient.tsx | 24 +++++++++++++++++++ .../routes/page/onboarding/set-numeraire.tsx | 3 ++- apps/extension/src/routes/popup/paths.ts | 1 + .../src/routes/popup/settings/routes.tsx | 5 ++++ .../popup/settings/settings-numeraires.tsx | 11 +++++++++ .../src/routes/popup/settings/settings.tsx | 6 +++++ 6 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 apps/extension/src/icons/numeraires-gradient.tsx create mode 100644 apps/extension/src/routes/popup/settings/settings-numeraires.tsx diff --git a/apps/extension/src/icons/numeraires-gradient.tsx b/apps/extension/src/icons/numeraires-gradient.tsx new file mode 100644 index 00000000..05766d33 --- /dev/null +++ b/apps/extension/src/icons/numeraires-gradient.tsx @@ -0,0 +1,24 @@ +export const NumerairesGradientIcon = () => ( + + + + + + + + + + +); diff --git a/apps/extension/src/routes/page/onboarding/set-numeraire.tsx b/apps/extension/src/routes/page/onboarding/set-numeraire.tsx index 1998e71c..0d876a94 100644 --- a/apps/extension/src/routes/page/onboarding/set-numeraire.tsx +++ b/apps/extension/src/routes/page/onboarding/set-numeraire.tsx @@ -21,7 +21,8 @@ export const SetNumerairesPage = () => { - Prax has a shortcut for your portfolio page. You can always change this later + Prax does not use third-party price providers for privacy reasons, instead Prax indexes + asset prices locally by selected denomination
diff --git a/apps/extension/src/routes/popup/paths.ts b/apps/extension/src/routes/popup/paths.ts index 22825517..c3555084 100644 --- a/apps/extension/src/routes/popup/paths.ts +++ b/apps/extension/src/routes/popup/paths.ts @@ -14,4 +14,5 @@ export enum PopupPath { SETTINGS_RECOVERY_PASSPHRASE = '/settings/recovery-passphrase', SETTINGS_FULL_VIEWING_KEY = '/settings/full-viewing-key', SETTINGS_SPEND_KEY = '/settings/spend-key', + SETTINGS_NUMERAIRES = '/settings/numeraires', } diff --git a/apps/extension/src/routes/popup/settings/routes.tsx b/apps/extension/src/routes/popup/settings/routes.tsx index b0de683c..357c0f9f 100644 --- a/apps/extension/src/routes/popup/settings/routes.tsx +++ b/apps/extension/src/routes/popup/settings/routes.tsx @@ -10,6 +10,7 @@ import { SettingsRPC } from './settings-rpc'; import { SettingsSecurity } from './settings-security'; import { SettingsSpendKey } from './settings-spend-key'; import { SettingsDefaultFrontend } from './settings-default-frontend'; +import { SettingsNumeraires } from './settings-numeraires'; export const settingsRoutes = [ { @@ -56,4 +57,8 @@ export const settingsRoutes = [ path: PopupPath.SETTINGS_SPEND_KEY, element: , }, + { + path: PopupPath.SETTINGS_NUMERAIRES, + element: , + }, ]; diff --git a/apps/extension/src/routes/popup/settings/settings-numeraires.tsx b/apps/extension/src/routes/popup/settings/settings-numeraires.tsx new file mode 100644 index 00000000..31867600 --- /dev/null +++ b/apps/extension/src/routes/popup/settings/settings-numeraires.tsx @@ -0,0 +1,11 @@ +import { SettingsScreen } from './settings-screen'; +import { NumeraireForm } from '../../../shared/components/numeraires'; +import { NumerairesGradientIcon } from '../../../icons/numeraires-gradient'; + +export const SettingsNumeraires = () => { + return ( + + + + ); +}; diff --git a/apps/extension/src/routes/popup/settings/settings.tsx b/apps/extension/src/routes/popup/settings/settings.tsx index ffe20a74..67d56820 100644 --- a/apps/extension/src/routes/popup/settings/settings.tsx +++ b/apps/extension/src/routes/popup/settings/settings.tsx @@ -1,4 +1,5 @@ import { + BarChartIcon, DashboardIcon, ExitIcon, HomeIcon, @@ -34,6 +35,11 @@ const links = [ icon: , href: PopupPath.SETTINGS_CONNECTED_SITES, }, + { + title: 'Price indexer', + icon: , + href: PopupPath.SETTINGS_NUMERAIRES, + }, { title: 'Advanced', icon: , From 88be2406618656c729ac80f90eace540541291c8 Mon Sep 17 00:00:00 2001 From: valentine Date: Wed, 12 Jun 2024 18:20:10 +0300 Subject: [PATCH 07/19] remove onboardNumeraires --- .../popup/settings/settings-numeraires.tsx | 2 +- .../shared/components/numeraires/index.tsx | 2 +- apps/extension/src/state/numeraires.ts | 2 ++ apps/extension/src/storage/onboard.ts | 19 ------------------- apps/extension/src/wallet-services.ts | 6 +++--- 5 files changed, 7 insertions(+), 24 deletions(-) diff --git a/apps/extension/src/routes/popup/settings/settings-numeraires.tsx b/apps/extension/src/routes/popup/settings/settings-numeraires.tsx index 31867600..98d3c830 100644 --- a/apps/extension/src/routes/popup/settings/settings-numeraires.tsx +++ b/apps/extension/src/routes/popup/settings/settings-numeraires.tsx @@ -4,7 +4,7 @@ import { NumerairesGradientIcon } from '../../../icons/numeraires-gradient'; export const SettingsNumeraires = () => { return ( - + ); diff --git a/apps/extension/src/shared/components/numeraires/index.tsx b/apps/extension/src/shared/components/numeraires/index.tsx index 8d04dc2f..d15d6f9d 100644 --- a/apps/extension/src/shared/components/numeraires/index.tsx +++ b/apps/extension/src/shared/components/numeraires/index.tsx @@ -35,7 +35,7 @@ export const NumeraireForm = ({ isOnboarding }: { isOnboarding?: boolean }) => { return ( {frontends.map(metadata => { - const icon = metadata?.images[0]?.png || metadata?.images[0]?.svg; + const icon = metadata.images[0]?.png || metadata.images[0]?.svg; return ( []; @@ -27,6 +28,7 @@ export const createNumerairesSlice = }, saveNumeraires: () => { void local.set('numeraires', get().numeraires.selectedNumeraires); + void chrome.runtime.sendMessage(ServicesMessage.ChangeNumeraires); }, }; }; diff --git a/apps/extension/src/storage/onboard.ts b/apps/extension/src/storage/onboard.ts index 03c9b162..7ecad502 100644 --- a/apps/extension/src/storage/onboard.ts +++ b/apps/extension/src/storage/onboard.ts @@ -48,25 +48,6 @@ export const onboardWallet = async (): Promise => { }); }; -export const onboardNumeraires = async (): Promise => { - const numeraires = await localExtStorage.get('numeraires'); - if (numeraires) return numeraires; - - return new Promise(resolve => { - const storageListener = (changes: Record) => { - const storageItem = changes['numeraires']?.newValue as - | StorageItem - | undefined; - const numeraires = storageItem?.value; - if (numeraires) { - resolve(numeraires); - localExtStorage.removeListener(storageListener); - } - }; - localExtStorage.addListener(storageListener); - }); -}; - /** This fixes an issue where some users do not have 'grpcEndpoint' set after they have finished onboarding */ diff --git a/apps/extension/src/wallet-services.ts b/apps/extension/src/wallet-services.ts index 84f32cfb..1ec92571 100644 --- a/apps/extension/src/wallet-services.ts +++ b/apps/extension/src/wallet-services.ts @@ -7,7 +7,7 @@ import { WalletId, } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb'; import { localExtStorage } from './storage/local'; -import { onboardGrpcEndpoint, onboardNumeraires, onboardWallet } from './storage/onboard'; +import { onboardGrpcEndpoint, onboardWallet } from './storage/onboard'; import { Services } from '@penumbra-zone/services-context'; import { ServicesMessage } from './message/services'; import { WalletServices } from '@penumbra-zone/types/services'; @@ -16,7 +16,7 @@ import { AssetId } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/a export const startWalletServices = async () => { const wallet = await onboardWallet(); const grpcEndpoint = await onboardGrpcEndpoint(); - const numeraires = await onboardNumeraires(); + let numeraires = await localExtStorage.get('numeraires'); const services = new Services({ grpcEndpoint, @@ -94,7 +94,7 @@ const attachServiceControlListener = ({ return true; case ServicesMessage.ChangeNumeraires: void (async () => { - let newNumeraires = await localExtStorage.get('numeraires'); + const newNumeraires = await localExtStorage.get('numeraires'); blockProcessor.setNumeraires(newNumeraires.map(n => AssetId.fromJsonString(n))); }); return true; From 992394d97f1e392f9de22a8ff45887b277d7fbb6 Mon Sep 17 00:00:00 2001 From: valentine Date: Wed, 12 Jun 2024 21:17:32 +0300 Subject: [PATCH 08/19] delete prices when numeraire is changed --- .../routes/page/onboarding/set-numeraire.tsx | 8 +- .../popup/settings/settings-numeraires.tsx | 10 ++- .../src/routes/popup/settings/settings.tsx | 2 +- .../shared/components/numeraires/index.tsx | 86 +++++++++++-------- apps/extension/src/state/numeraires.ts | 8 +- apps/extension/src/wallet-services.ts | 1 + packages/storage/src/indexed-db/index.ts | 18 +++- packages/types/src/indexed-db.ts | 1 + 8 files changed, 87 insertions(+), 47 deletions(-) diff --git a/apps/extension/src/routes/page/onboarding/set-numeraire.tsx b/apps/extension/src/routes/page/onboarding/set-numeraire.tsx index 0d876a94..5fe53c58 100644 --- a/apps/extension/src/routes/page/onboarding/set-numeraire.tsx +++ b/apps/extension/src/routes/page/onboarding/set-numeraire.tsx @@ -2,14 +2,12 @@ import { Card, CardDescription, CardHeader, CardTitle } from '@penumbra-zone/ui/ import { FadeTransition } from '@penumbra-zone/ui/components/ui/fade-transition'; import { usePageNav } from '../../../utils/navigate'; import { PagePath } from '../paths'; -import { FormEventHandler } from 'react'; import { NumeraireForm } from '../../../shared/components/numeraires'; export const SetNumerairesPage = () => { const navigate = usePageNav(); - const onSubmit: FormEventHandler = (event): void => { - event.preventDefault(); + const onSuccess = (): void => { navigate(PagePath.ONBOARDING_SUCCESS); }; @@ -25,9 +23,7 @@ export const SetNumerairesPage = () => { asset prices locally by selected denomination - - - + ); diff --git a/apps/extension/src/routes/popup/settings/settings-numeraires.tsx b/apps/extension/src/routes/popup/settings/settings-numeraires.tsx index 98d3c830..8d96a376 100644 --- a/apps/extension/src/routes/popup/settings/settings-numeraires.tsx +++ b/apps/extension/src/routes/popup/settings/settings-numeraires.tsx @@ -1,11 +1,19 @@ import { SettingsScreen } from './settings-screen'; import { NumeraireForm } from '../../../shared/components/numeraires'; import { NumerairesGradientIcon } from '../../../icons/numeraires-gradient'; +import {usePopupNav} from "../../../utils/navigate"; +import {PopupPath} from "../paths"; export const SettingsNumeraires = () => { + + const navigate = usePopupNav(); + + const onSuccess = async () => { + navigate(PopupPath.SETTINGS) + }; return ( - + ); }; diff --git a/apps/extension/src/routes/popup/settings/settings.tsx b/apps/extension/src/routes/popup/settings/settings.tsx index 67d56820..2b1f9ef8 100644 --- a/apps/extension/src/routes/popup/settings/settings.tsx +++ b/apps/extension/src/routes/popup/settings/settings.tsx @@ -36,7 +36,7 @@ const links = [ href: PopupPath.SETTINGS_CONNECTED_SITES, }, { - title: 'Price indexer', + title: 'Price denominations', icon: , href: PopupPath.SETTINGS_NUMERAIRES, }, diff --git a/apps/extension/src/shared/components/numeraires/index.tsx b/apps/extension/src/shared/components/numeraires/index.tsx index d15d6f9d..a6cca64e 100644 --- a/apps/extension/src/shared/components/numeraires/index.tsx +++ b/apps/extension/src/shared/components/numeraires/index.tsx @@ -2,7 +2,7 @@ import { SelectList } from '@penumbra-zone/ui/components/ui/select-list'; import { ChainRegistryClient } from '@penumbra-labs/registry'; import { AllSlices } from '../../../state'; import { useStoreShallow } from '../../../utils/use-store-shallow'; -import { useMemo } from 'react'; +import { FormEvent, useMemo } from 'react'; import { Button } from '@penumbra-zone/ui/components/ui/button'; import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; import { useChainIdQuery } from '../../../hooks/chain-id'; @@ -23,48 +23,66 @@ const useNumerairesSelector = (state: AllSlices) => { }; }; -export const NumeraireForm = ({ isOnboarding }: { isOnboarding?: boolean }) => { + + +export const NumeraireForm = ({ isOnboarding, onSuccess }: { isOnboarding?: boolean, onSuccess: () => void | Promise;}) => { const { chainId } = useChainIdQuery(); const { selectedNumeraires, selectNumeraire, saveNumeraires } = useStoreShallow(useNumerairesSelector); - const frontends = useMemo( + const numeraires = useMemo( () => getNumeraireFromRegistry(chainId || 'penumbra-testnet-deimos-8'), [chainId], ); + const onSubmit = async ( + onSuccess: () => void | Promise, + ) => { + saveNumeraires(); + await onSuccess(); + }; + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + void onSubmit(onSuccess); + }; return ( - - {frontends.map(metadata => { - const icon = metadata.images[0]?.png || metadata.images[0]?.svg; - return ( - selectNumeraire(getAssetId(metadata).toJsonString())} - image={ - !!icon && ( - rpc endpoint brand image +
+
+ + {numeraires.map(metadata => { + const icon = metadata.images[0]?.png || metadata.images[0]?.svg; + return ( + selectNumeraire(getAssetId(metadata).toJsonString())} + image={ + !!icon && ( + rpc endpoint brand image + ) + } /> - ) - } - /> - ); - })} + ); + })} - - + + +
+
+ ); }; diff --git a/apps/extension/src/state/numeraires.ts b/apps/extension/src/state/numeraires.ts index 97b80899..cd0e0592 100644 --- a/apps/extension/src/state/numeraires.ts +++ b/apps/extension/src/state/numeraires.ts @@ -3,7 +3,7 @@ import { AllSlices, SliceCreator } from '.'; import { ExtensionStorage } from '../storage/base'; import { Stringified } from '@penumbra-zone/types/jsonified'; import { AssetId } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; -import {ServicesMessage} from "../message/services"; +import { ServicesMessage } from '../message/services'; export interface NumerairesSlice { selectedNumeraires: Stringified[]; @@ -26,9 +26,9 @@ export const createNumerairesSlice = } }); }, - saveNumeraires: () => { - void local.set('numeraires', get().numeraires.selectedNumeraires); - void chrome.runtime.sendMessage(ServicesMessage.ChangeNumeraires); + saveNumeraires: async () => { + await local.set('numeraires', get().numeraires.selectedNumeraires); + await chrome.runtime.sendMessage(ServicesMessage.ChangeNumeraires); }, }; }; diff --git a/apps/extension/src/wallet-services.ts b/apps/extension/src/wallet-services.ts index 1ec92571..3a366c80 100644 --- a/apps/extension/src/wallet-services.ts +++ b/apps/extension/src/wallet-services.ts @@ -96,6 +96,7 @@ const attachServiceControlListener = ({ void (async () => { const newNumeraires = await localExtStorage.get('numeraires'); blockProcessor.setNumeraires(newNumeraires.map(n => AssetId.fromJsonString(n))); + await indexedDb.clearSwapBasedPrices(); }); return true; } diff --git a/packages/storage/src/indexed-db/index.ts b/packages/storage/src/indexed-db/index.ts index 061dfb32..aedff18d 100644 --- a/packages/storage/src/indexed-db/index.ts +++ b/packages/storage/src/indexed-db/index.ts @@ -79,6 +79,7 @@ export class IndexedDb implements IndexedDbInterface { private readonly u: IbdUpdater, private readonly c: IdbConstants, private readonly chainId: string, + private readonly stakingTokenAssetId: AssetId, ) {} static async initialize({ @@ -134,7 +135,13 @@ export class IndexedDb implements IndexedDbInterface { tables: IDB_TABLES, } satisfies IdbConstants; - const instance = new this(db, new IbdUpdater(db), constants, chainId); + const instance = new this( + db, + new IbdUpdater(db), + constants, + chainId, + registryClient.get(chainId).stakingAssetId, + ); await instance.saveRegistryAssets(registryClient, chainId); // Pre-load asset metadata from registry const existing0thEpoch = await instance.getEpochByHeight(0n); @@ -661,6 +668,15 @@ export class IndexedDb implements IndexedDbInterface { .filter(price => price.asOfHeight >= minHeight); } + async clearSwapBasedPrices(): Promise { + for await (const cursor of this.db.transaction('PRICES').store) { + const price = EstimatedPrice.fromJson(cursor.value); + // Do not delete prices for delegation assets + if (price.numeraire?.equals(this.stakingTokenAssetId)) continue; + await this.db.delete('PRICES', cursor.key); + } + } + private determinePriceRelevanceThresholdForAsset(assetMetadata: Metadata): number { if (assetPatterns.delegationToken.capture(assetMetadata.display)) { return PRICE_RELEVANCE_THRESHOLDS.delegationToken; diff --git a/packages/types/src/indexed-db.ts b/packages/types/src/indexed-db.ts index ec0b2e21..e4bef3bb 100644 --- a/packages/types/src/indexed-db.ts +++ b/packages/types/src/indexed-db.ts @@ -110,6 +110,7 @@ export interface IndexedDbInterface { height: bigint, ): Promise; getPricesForAsset(assetMetadata: Metadata, latestBlockHeight: bigint): Promise; + clearSwapBasedPrices(): Promise; // Add more auction union types as they are created upsertAuction( From bcd6c0be5315fe5191210f894014f1b5736b7521 Mon Sep 17 00:00:00 2001 From: valentine Date: Thu, 13 Jun 2024 00:15:47 +0300 Subject: [PATCH 09/19] refactor & fix numeraires update bug --- .../page/onboarding/set-grpc-endpoint.tsx | 1 + .../routes/page/onboarding/set-numeraire.tsx | 14 ++--- .../popup/settings/settings-numeraires.tsx | 15 +++-- .../index.tsx => numeraires-form.tsx} | 55 +++++++++++-------- apps/extension/src/state/numeraires.ts | 4 +- apps/extension/src/wallet-services.ts | 4 +- 6 files changed, 49 insertions(+), 44 deletions(-) rename apps/extension/src/shared/components/{numeraires/index.tsx => numeraires-form.tsx} (69%) diff --git a/apps/extension/src/routes/page/onboarding/set-grpc-endpoint.tsx b/apps/extension/src/routes/page/onboarding/set-grpc-endpoint.tsx index c79df697..46c1f854 100644 --- a/apps/extension/src/routes/page/onboarding/set-grpc-endpoint.tsx +++ b/apps/extension/src/routes/page/onboarding/set-grpc-endpoint.tsx @@ -16,6 +16,7 @@ export const SetGrpcEndpoint = () => { Select your preferred RPC endpoint + The requests you make may reveal your intentions about transactions you wish to make, so select an RPC node that you trust. If you're unsure which one to choose, leave this diff --git a/apps/extension/src/routes/page/onboarding/set-numeraire.tsx b/apps/extension/src/routes/page/onboarding/set-numeraire.tsx index 5fe53c58..9da0c590 100644 --- a/apps/extension/src/routes/page/onboarding/set-numeraire.tsx +++ b/apps/extension/src/routes/page/onboarding/set-numeraire.tsx @@ -2,7 +2,7 @@ import { Card, CardDescription, CardHeader, CardTitle } from '@penumbra-zone/ui/ import { FadeTransition } from '@penumbra-zone/ui/components/ui/fade-transition'; import { usePageNav } from '../../../utils/navigate'; import { PagePath } from '../paths'; -import { NumeraireForm } from '../../../shared/components/numeraires'; +import { NumeraireForm } from '../../../shared/components/numeraires-form'; export const SetNumerairesPage = () => { const navigate = usePageNav(); @@ -16,14 +16,14 @@ export const SetNumerairesPage = () => { In which token denomination would you prefer to price assets? + + Prax does not use third-party price providers for privacy reasons, instead Prax indexes + asset prices locally by selected denomination + - - - Prax does not use third-party price providers for privacy reasons, instead Prax indexes - asset prices locally by selected denomination - - +
+
); diff --git a/apps/extension/src/routes/popup/settings/settings-numeraires.tsx b/apps/extension/src/routes/popup/settings/settings-numeraires.tsx index 8d96a376..9ad1902d 100644 --- a/apps/extension/src/routes/popup/settings/settings-numeraires.tsx +++ b/apps/extension/src/routes/popup/settings/settings-numeraires.tsx @@ -1,16 +1,15 @@ import { SettingsScreen } from './settings-screen'; -import { NumeraireForm } from '../../../shared/components/numeraires'; import { NumerairesGradientIcon } from '../../../icons/numeraires-gradient'; -import {usePopupNav} from "../../../utils/navigate"; -import {PopupPath} from "../paths"; +import { usePopupNav } from '../../../utils/navigate'; +import { PopupPath } from '../paths'; +import { NumeraireForm } from '../../../shared/components/numeraires-form'; export const SettingsNumeraires = () => { + const navigate = usePopupNav(); - const navigate = usePopupNav(); - - const onSuccess = async () => { - navigate(PopupPath.SETTINGS) - }; + const onSuccess = () => { + navigate(PopupPath.INDEX); + }; return ( diff --git a/apps/extension/src/shared/components/numeraires/index.tsx b/apps/extension/src/shared/components/numeraires-form.tsx similarity index 69% rename from apps/extension/src/shared/components/numeraires/index.tsx rename to apps/extension/src/shared/components/numeraires-form.tsx index a6cca64e..ce7dc6a5 100644 --- a/apps/extension/src/shared/components/numeraires/index.tsx +++ b/apps/extension/src/shared/components/numeraires-form.tsx @@ -1,13 +1,13 @@ -import { SelectList } from '@penumbra-zone/ui/components/ui/select-list'; import { ChainRegistryClient } from '@penumbra-labs/registry'; -import { AllSlices } from '../../../state'; -import { useStoreShallow } from '../../../utils/use-store-shallow'; -import { FormEvent, useMemo } from 'react'; -import { Button } from '@penumbra-zone/ui/components/ui/button'; -import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; -import { useChainIdQuery } from '../../../hooks/chain-id'; +import { AllSlices, useStore } from '../../state'; +import { useChainIdQuery } from '../../hooks/chain-id'; +import { useMemo, useState } from 'react'; +import { ServicesMessage } from '../../message/services'; +import { SelectList } from '@penumbra-zone/ui/components/ui/select-list'; import { bech32mAssetId } from '@penumbra-zone/bech32m/passet'; import { getAssetId } from '@penumbra-zone/getters/metadata'; +import { Button } from '@penumbra-zone/ui/components/ui/button'; +import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; const getNumeraireFromRegistry = (chainId: string): Metadata[] => { const registryClient = new ChainRegistryClient(); @@ -23,34 +23,39 @@ const useNumerairesSelector = (state: AllSlices) => { }; }; - - -export const NumeraireForm = ({ isOnboarding, onSuccess }: { isOnboarding?: boolean, onSuccess: () => void | Promise;}) => { +export const NumeraireForm = ({ + isOnboarding, + onSuccess, +}: { + isOnboarding?: boolean; + onSuccess: () => void | Promise; +}) => { const { chainId } = useChainIdQuery(); - const { selectedNumeraires, selectNumeraire, saveNumeraires } = - useStoreShallow(useNumerairesSelector); + const { selectedNumeraires, selectNumeraire, saveNumeraires } = useStore(useNumerairesSelector); const numeraires = useMemo( - () => getNumeraireFromRegistry(chainId || 'penumbra-testnet-deimos-8'), + () => getNumeraireFromRegistry(chainId ?? 'penumbra-testnet-deimos-8'), [chainId], ); - const onSubmit = async ( - onSuccess: () => void | Promise, - ) => { - saveNumeraires(); - await onSuccess(); - }; + const [loading, setLoading] = useState(false); - const handleSubmit = (e: FormEvent) => { - e.preventDefault(); - void onSubmit(onSuccess); + const handleSubmit = () => { + setLoading(true); + void (async function () { + await saveNumeraires(); + await chrome.runtime.sendMessage(ServicesMessage.ChangeNumeraires); + await onSuccess(); + })(); }; + return ( <>
{numeraires.map(metadata => { + // Image default is "" and thus cannot do nullish-coalescing + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const icon = metadata.images[0]?.png || metadata.images[0]?.svg; return ( - {isOnboarding ? 'Next' : 'Save'} + {isOnboarding ? 'Next' : loading ? 'Saving...' : 'Save'}
diff --git a/apps/extension/src/state/numeraires.ts b/apps/extension/src/state/numeraires.ts index cd0e0592..b3aae19d 100644 --- a/apps/extension/src/state/numeraires.ts +++ b/apps/extension/src/state/numeraires.ts @@ -3,12 +3,11 @@ import { AllSlices, SliceCreator } from '.'; import { ExtensionStorage } from '../storage/base'; import { Stringified } from '@penumbra-zone/types/jsonified'; import { AssetId } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; -import { ServicesMessage } from '../message/services'; export interface NumerairesSlice { selectedNumeraires: Stringified[]; selectNumeraire: (numeraire: Stringified) => void; - saveNumeraires: () => void; + saveNumeraires: () => Promise; } export const createNumerairesSlice = @@ -28,7 +27,6 @@ export const createNumerairesSlice = }, saveNumeraires: async () => { await local.set('numeraires', get().numeraires.selectedNumeraires); - await chrome.runtime.sendMessage(ServicesMessage.ChangeNumeraires); }, }; }; diff --git a/apps/extension/src/wallet-services.ts b/apps/extension/src/wallet-services.ts index 3a366c80..29151298 100644 --- a/apps/extension/src/wallet-services.ts +++ b/apps/extension/src/wallet-services.ts @@ -16,7 +16,7 @@ import { AssetId } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/a export const startWalletServices = async () => { const wallet = await onboardWallet(); const grpcEndpoint = await onboardGrpcEndpoint(); - let numeraires = await localExtStorage.get('numeraires'); + const numeraires = await localExtStorage.get('numeraires'); const services = new Services({ grpcEndpoint, @@ -97,7 +97,7 @@ const attachServiceControlListener = ({ const newNumeraires = await localExtStorage.get('numeraires'); blockProcessor.setNumeraires(newNumeraires.map(n => AssetId.fromJsonString(n))); await indexedDb.clearSwapBasedPrices(); - }); + })().then(() => respond()); return true; } }); From b60e27356c93ea6b68f15adf5481754c7f9a6342 Mon Sep 17 00:00:00 2001 From: valentine Date: Thu, 13 Jun 2024 13:36:12 +0300 Subject: [PATCH 10/19] add idb tests --- packages/storage/src/indexed-db/index.ts | 14 ++++++++++---- packages/storage/src/indexed-db/indexed-db.test.ts | 10 +++++++++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/storage/src/indexed-db/index.ts b/packages/storage/src/indexed-db/index.ts index aedff18d..82d2cc17 100644 --- a/packages/storage/src/indexed-db/index.ts +++ b/packages/storage/src/indexed-db/index.ts @@ -669,12 +669,18 @@ export class IndexedDb implements IndexedDbInterface { } async clearSwapBasedPrices(): Promise { - for await (const cursor of this.db.transaction('PRICES').store) { + const tx = this.db.transaction('PRICES', 'readwrite'); + const store = tx.objectStore('PRICES'); + + let cursor = await store.openCursor(); + while (cursor) { const price = EstimatedPrice.fromJson(cursor.value); - // Do not delete prices for delegation assets - if (price.numeraire?.equals(this.stakingTokenAssetId)) continue; - await this.db.delete('PRICES', cursor.key); + if (!price.numeraire?.equals(this.stakingTokenAssetId)) { + cursor.delete(); + } + cursor = await cursor.continue(); } + await tx.done; } private determinePriceRelevanceThresholdForAsset(assetMetadata: Metadata): number { diff --git a/packages/storage/src/indexed-db/indexed-db.test.ts b/packages/storage/src/indexed-db/indexed-db.test.ts index 79b19a20..f2200919 100644 --- a/packages/storage/src/indexed-db/indexed-db.test.ts +++ b/packages/storage/src/indexed-db/indexed-db.test.ts @@ -594,9 +594,12 @@ describe('IndexedDb', () => { const numeraireAssetId = new AssetId({ inner: new Uint8Array([5, 6, 7, 8]) }); + const stakingAssetId = AssetId.fromJson({ + inner: 'KeqcLzNx9qSH5+lcJHBB9KNW+YPrBk5dKzvPMiypahA=', + }); beforeEach(async () => { db = await IndexedDb.initialize({ ...generateInitialProps() }); - await db.updatePrice(delegationMetadataA.penumbraAssetId!, numeraireAssetId, 1.23, 50n); + await db.updatePrice(delegationMetadataA.penumbraAssetId!, stakingAssetId, 1.23, 50n); await db.updatePrice(metadataA.penumbraAssetId!, numeraireAssetId, 22.15, 40n); }); @@ -628,6 +631,11 @@ describe('IndexedDb', () => { }), ]); }); + + it('should delete only prices with a numeraires different from the staking token', async () => { + await db.clearSwapBasedPrices(); + await expect(db.getPricesForAsset(metadataA, 50n)).resolves.toEqual([]); + }); }); describe('upsertAuction()', () => { From 109c34d3495e73a5aebe9f2cce7a02c53ae906e7 Mon Sep 17 00:00:00 2001 From: valentine Date: Thu, 13 Jun 2024 14:48:26 +0300 Subject: [PATCH 11/19] fix chain-id --- .../grpc-endpoint-form/use-grpc-endpoint-form.ts | 7 +++++-- .../src/shared/components/numeraires-form.tsx | 12 ++++++------ apps/extension/src/state/network.ts | 8 ++++++++ apps/extension/src/state/persist.ts | 14 ++++++++++++++ apps/extension/src/storage/types.ts | 4 ++-- apps/extension/src/wallet-services.ts | 2 +- packages/storage/src/indexed-db/index.ts | 2 +- packages/storage/src/indexed-db/indexed-db.test.ts | 4 ++-- 8 files changed, 39 insertions(+), 14 deletions(-) diff --git a/apps/extension/src/shared/components/grpc-endpoint-form/use-grpc-endpoint-form.ts b/apps/extension/src/shared/components/grpc-endpoint-form/use-grpc-endpoint-form.ts index a234cf32..2b06bf6a 100644 --- a/apps/extension/src/shared/components/grpc-endpoint-form/use-grpc-endpoint-form.ts +++ b/apps/extension/src/shared/components/grpc-endpoint-form/use-grpc-endpoint-form.ts @@ -12,7 +12,9 @@ import { isValidUrl } from '../../utils/is-valid-url'; const useSaveGrpcEndpointSelector = (state: AllSlices) => ({ grpcEndpoint: state.network.grpcEndpoint, + chainId: state.network.chainId, setGrpcEndpoint: state.network.setGRPCEndpoint, + setChainId: state.network.setChainId, }); const getRpcsFromRegistry = () => { @@ -25,10 +27,11 @@ export const useGrpcEndpointForm = () => { const grpcEndpoints = useMemo(() => getRpcsFromRegistry(), []); // Get the rpc set in storage (if present) - const { grpcEndpoint, setGrpcEndpoint } = useStoreShallow(useSaveGrpcEndpointSelector); + const { grpcEndpoint, chainId, setGrpcEndpoint, setChainId } = useStoreShallow( + useSaveGrpcEndpointSelector, + ); const [originalChainId, setOriginalChainId] = useState(); - const [chainId, setChainId] = useState(); const [grpcEndpointInput, setGrpcEndpointInput] = useState(''); const [rpcError, setRpcError] = useState(); const [isSubmitButtonEnabled, setIsSubmitButtonEnabled] = useState(false); diff --git a/apps/extension/src/shared/components/numeraires-form.tsx b/apps/extension/src/shared/components/numeraires-form.tsx index ce7dc6a5..4cd842f9 100644 --- a/apps/extension/src/shared/components/numeraires-form.tsx +++ b/apps/extension/src/shared/components/numeraires-form.tsx @@ -9,8 +9,9 @@ import { getAssetId } from '@penumbra-zone/getters/metadata'; import { Button } from '@penumbra-zone/ui/components/ui/button'; import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; -const getNumeraireFromRegistry = (chainId: string): Metadata[] => { +const getNumeraireFromRegistry = (chainId?: string): Metadata[] => { const registryClient = new ChainRegistryClient(); + if (!chainId) return []; const registry = registryClient.get(chainId); return registry.numeraires.map(n => registry.getMetadata(n)); }; @@ -20,6 +21,7 @@ const useNumerairesSelector = (state: AllSlices) => { selectedNumeraires: state.numeraires.selectedNumeraires, selectNumeraire: state.numeraires.selectNumeraire, saveNumeraires: state.numeraires.saveNumeraires, + networkChainId: state.network.chainId, }; }; @@ -31,11 +33,9 @@ export const NumeraireForm = ({ onSuccess: () => void | Promise; }) => { const { chainId } = useChainIdQuery(); - const { selectedNumeraires, selectNumeraire, saveNumeraires } = useStore(useNumerairesSelector); - const numeraires = useMemo( - () => getNumeraireFromRegistry(chainId ?? 'penumbra-testnet-deimos-8'), - [chainId], - ); + const { selectedNumeraires, selectNumeraire, saveNumeraires, networkChainId } = + useStore(useNumerairesSelector); + const numeraires = useMemo(() => getNumeraireFromRegistry(chainId ?? networkChainId), [chainId]); const [loading, setLoading] = useState(false); diff --git a/apps/extension/src/state/network.ts b/apps/extension/src/state/network.ts index f67b819e..db6ebee9 100644 --- a/apps/extension/src/state/network.ts +++ b/apps/extension/src/state/network.ts @@ -5,7 +5,9 @@ import { AllSlices, SliceCreator } from '.'; export interface NetworkSlice { grpcEndpoint: string | undefined; fullSyncHeight?: number; + chainId?: string; setGRPCEndpoint: (endpoint: string) => Promise; + setChainId: (chainId: string) => void; } export const createNetworkSlice = @@ -14,6 +16,7 @@ export const createNetworkSlice = return { grpcEndpoint: undefined, fullSyncHeight: undefined, + chainId: undefined, setGRPCEndpoint: async (endpoint: string) => { set(state => { state.network.grpcEndpoint = endpoint; @@ -21,6 +24,11 @@ export const createNetworkSlice = await local.set('grpcEndpoint', endpoint); }, + setChainId: (chainId: string) => { + set(state => { + state.network.chainId = chainId; + }); + }, }; }; diff --git a/apps/extension/src/state/persist.ts b/apps/extension/src/state/persist.ts index 2daf7a3b..7046d0b2 100644 --- a/apps/extension/src/state/persist.ts +++ b/apps/extension/src/state/persist.ts @@ -7,6 +7,7 @@ import { LocalStorageState } from '../storage/types'; import { sessionExtStorage, SessionStorageState } from '../storage/session'; import { StorageItem } from '../storage/base'; import { walletsFromJson } from '@penumbra-zone/types/wallet'; +import { AppParameters } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/app/v1/app_pb'; export type Middleware = < T, @@ -120,6 +121,19 @@ function syncLocal(changes: Record, set: S }), ); } + + if (changes['params']) { + const stored = changes['params'].newValue as + | StorageItem + | undefined; + set( + produce((state: AllSlices) => { + state.network.chainId = stored?.value + ? AppParameters.fromJsonString(stored.value).chainId + : state.network.chainId; + }), + ); + } } function syncSession(changes: Record, set: Setter) { diff --git a/apps/extension/src/storage/types.ts b/apps/extension/src/storage/types.ts index 2f1d88f7..1df3b80a 100644 --- a/apps/extension/src/storage/types.ts +++ b/apps/extension/src/storage/types.ts @@ -1,6 +1,6 @@ import { AppParameters } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/app/v1/app_pb'; import { KeyPrintJson } from '@penumbra-zone/crypto-web/encryption'; -import { Jsonified, Stringified } from '@penumbra-zone/types/jsonified'; +import { Stringified } from '@penumbra-zone/types/jsonified'; import { UserChoice } from '@penumbra-zone/types/user-choice'; import { WalletJson } from '@penumbra-zone/types/wallet'; import { AssetId } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; @@ -23,6 +23,6 @@ export interface LocalStorageState { passwordKeyPrint: KeyPrintJson | undefined; fullSyncHeight: number | undefined; knownSites: OriginRecord[]; - params: Jsonified | undefined; + params: Stringified | undefined; numeraires: Stringified[]; } diff --git a/apps/extension/src/wallet-services.ts b/apps/extension/src/wallet-services.ts index 29151298..11ae64b9 100644 --- a/apps/extension/src/wallet-services.ts +++ b/apps/extension/src/wallet-services.ts @@ -41,7 +41,7 @@ export const startWalletServices = async () => { const getChainId = async (baseUrl: string) => { const localChainId = await localExtStorage .get('params') - .then(json => json && AppParameters.fromJson(json).chainId); + .then(json => json && AppParameters.fromJsonString(json).chainId); if (localChainId) return localChainId; diff --git a/packages/storage/src/indexed-db/index.ts b/packages/storage/src/indexed-db/index.ts index 82d2cc17..ea21ac78 100644 --- a/packages/storage/src/indexed-db/index.ts +++ b/packages/storage/src/indexed-db/index.ts @@ -676,7 +676,7 @@ export class IndexedDb implements IndexedDbInterface { while (cursor) { const price = EstimatedPrice.fromJson(cursor.value); if (!price.numeraire?.equals(this.stakingTokenAssetId)) { - cursor.delete(); + await cursor.delete(); } cursor = await cursor.continue(); } diff --git a/packages/storage/src/indexed-db/indexed-db.test.ts b/packages/storage/src/indexed-db/indexed-db.test.ts index f2200919..47f57c35 100644 --- a/packages/storage/src/indexed-db/indexed-db.test.ts +++ b/packages/storage/src/indexed-db/indexed-db.test.ts @@ -609,7 +609,7 @@ describe('IndexedDb', () => { await expect(db.getPricesForAsset(delegationMetadataA, 50n)).resolves.toEqual([ new EstimatedPrice({ pricedAsset: delegationMetadataA.penumbraAssetId!, - numeraire: numeraireAssetId, + numeraire: stakingAssetId, numerairePerUnit: 1.23, asOfHeight: 50n, }), @@ -625,7 +625,7 @@ describe('IndexedDb', () => { await expect(db.getPricesForAsset(delegationMetadataA, 241n)).resolves.toEqual([ new EstimatedPrice({ pricedAsset: delegationMetadataA.penumbraAssetId!, - numeraire: numeraireAssetId, + numeraire: stakingAssetId, numerairePerUnit: 1.23, asOfHeight: 50n, }), From c92c9e0faf8c94e84335e474df5de631089d4be5 Mon Sep 17 00:00:00 2001 From: Valentine Date: Fri, 14 Jun 2024 21:30:27 +0300 Subject: [PATCH 12/19] Update apps/extension/src/shared/components/numeraires-form.tsx Co-authored-by: Jesse Pinho --- apps/extension/src/shared/components/numeraires-form.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/extension/src/shared/components/numeraires-form.tsx b/apps/extension/src/shared/components/numeraires-form.tsx index 4cd842f9..66754086 100644 --- a/apps/extension/src/shared/components/numeraires-form.tsx +++ b/apps/extension/src/shared/components/numeraires-form.tsx @@ -49,7 +49,6 @@ export const NumeraireForm = ({ }; return ( - <>
From b83c2aa38266b43851a6b3d25774999651b735fe Mon Sep 17 00:00:00 2001 From: Valentine Date: Fri, 14 Jun 2024 21:30:45 +0300 Subject: [PATCH 13/19] Update apps/extension/src/shared/components/numeraires-form.tsx Co-authored-by: Jesse Pinho --- apps/extension/src/shared/components/numeraires-form.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/extension/src/shared/components/numeraires-form.tsx b/apps/extension/src/shared/components/numeraires-form.tsx index 66754086..ac29e09b 100644 --- a/apps/extension/src/shared/components/numeraires-form.tsx +++ b/apps/extension/src/shared/components/numeraires-form.tsx @@ -10,8 +10,8 @@ import { Button } from '@penumbra-zone/ui/components/ui/button'; import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; const getNumeraireFromRegistry = (chainId?: string): Metadata[] => { - const registryClient = new ChainRegistryClient(); if (!chainId) return []; + const registryClient = new ChainRegistryClient(); const registry = registryClient.get(chainId); return registry.numeraires.map(n => registry.getMetadata(n)); }; From ad6467986e62578b65aeb4ea1f211171d5be0638 Mon Sep 17 00:00:00 2001 From: Valentine Date: Fri, 14 Jun 2024 21:31:12 +0300 Subject: [PATCH 14/19] Update apps/extension/src/routes/popup/settings/settings.tsx Co-authored-by: Jesse Pinho --- apps/extension/src/routes/popup/settings/settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/extension/src/routes/popup/settings/settings.tsx b/apps/extension/src/routes/popup/settings/settings.tsx index 2b1f9ef8..a2c56f47 100644 --- a/apps/extension/src/routes/popup/settings/settings.tsx +++ b/apps/extension/src/routes/popup/settings/settings.tsx @@ -36,7 +36,7 @@ const links = [ href: PopupPath.SETTINGS_CONNECTED_SITES, }, { - title: 'Price denominations', + title: 'Price denomination', icon: , href: PopupPath.SETTINGS_NUMERAIRES, }, From c17991e2516d0c748f6ba2670cf88cc9049f5c17 Mon Sep 17 00:00:00 2001 From: Valentine Date: Fri, 14 Jun 2024 21:31:23 +0300 Subject: [PATCH 15/19] Update apps/extension/src/routes/page/onboarding/set-numeraire.tsx Co-authored-by: Jesse Pinho --- apps/extension/src/routes/page/onboarding/set-numeraire.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/extension/src/routes/page/onboarding/set-numeraire.tsx b/apps/extension/src/routes/page/onboarding/set-numeraire.tsx index 9da0c590..99a22e3c 100644 --- a/apps/extension/src/routes/page/onboarding/set-numeraire.tsx +++ b/apps/extension/src/routes/page/onboarding/set-numeraire.tsx @@ -18,7 +18,7 @@ export const SetNumerairesPage = () => { In which token denomination would you prefer to price assets? Prax does not use third-party price providers for privacy reasons, instead Prax indexes - asset prices locally by selected denomination + asset prices locally by selected denomination.
From 40306ac13cd3e942674f0949d986026e041cca61 Mon Sep 17 00:00:00 2001 From: Valentine Date: Fri, 14 Jun 2024 21:31:41 +0300 Subject: [PATCH 16/19] Update apps/extension/src/routes/page/onboarding/set-numeraire.tsx Co-authored-by: Jesse Pinho --- apps/extension/src/routes/page/onboarding/set-numeraire.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/extension/src/routes/page/onboarding/set-numeraire.tsx b/apps/extension/src/routes/page/onboarding/set-numeraire.tsx index 99a22e3c..eb347b14 100644 --- a/apps/extension/src/routes/page/onboarding/set-numeraire.tsx +++ b/apps/extension/src/routes/page/onboarding/set-numeraire.tsx @@ -17,7 +17,7 @@ export const SetNumerairesPage = () => { In which token denomination would you prefer to price assets? - Prax does not use third-party price providers for privacy reasons, instead Prax indexes + Prax does not use third-party price providers for privacy reasons. Instead, Prax indexes asset prices locally by selected denomination. From fabf6ae6ffccf9fcb0313c159b42959d174adb50 Mon Sep 17 00:00:00 2001 From: valentine Date: Fri, 14 Jun 2024 21:52:55 +0300 Subject: [PATCH 17/19] fix lint --- .../src/shared/components/numeraires-form.tsx | 79 +++++++++---------- 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/apps/extension/src/shared/components/numeraires-form.tsx b/apps/extension/src/shared/components/numeraires-form.tsx index ac29e09b..d0f2aa50 100644 --- a/apps/extension/src/shared/components/numeraires-form.tsx +++ b/apps/extension/src/shared/components/numeraires-form.tsx @@ -49,46 +49,45 @@ export const NumeraireForm = ({ }; return ( -
- - - {numeraires.map(metadata => { - // Image default is "" and thus cannot do nullish-coalescing - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const icon = metadata.images[0]?.png || metadata.images[0]?.svg; - return ( - selectNumeraire(getAssetId(metadata).toJsonString())} - image={ - !!icon && ( - rpc endpoint brand image - ) - } - /> - ); - })} +
+ + + {numeraires.map(metadata => { + // Image default is "" and thus cannot do nullish-coalescing + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const icon = metadata.images[0]?.png || metadata.images[0]?.svg; + return ( + selectNumeraire(getAssetId(metadata).toJsonString())} + image={ + !!icon && ( + rpc endpoint brand image + ) + } + /> + ); + })} - - - -
- + +
+ +
); }; From b1af2bf35e041a65eb5769f6d0e7c6cd48bd3277 Mon Sep 17 00:00:00 2001 From: valentine Date: Fri, 14 Jun 2024 22:09:37 +0300 Subject: [PATCH 18/19] add docs for clearSwapBasedPrices --- apps/extension/src/wallet-services.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/extension/src/wallet-services.ts b/apps/extension/src/wallet-services.ts index 11ae64b9..606ea7df 100644 --- a/apps/extension/src/wallet-services.ts +++ b/apps/extension/src/wallet-services.ts @@ -96,6 +96,11 @@ const attachServiceControlListener = ({ void (async () => { const newNumeraires = await localExtStorage.get('numeraires'); blockProcessor.setNumeraires(newNumeraires.map(n => AssetId.fromJsonString(n))); + /** + * Changing numeraires causes all BSOD-based prices to be removed. + * This means that some new blocks will need to be scanned to get prices for the new numeraires. + * It also means that immediately after changing numeraires user will not see any equivalent BSOD-based prices. + */ await indexedDb.clearSwapBasedPrices(); })().then(() => respond()); return true; From d2f7230faaa1d725ada301d61fbf8cf73efe99b1 Mon Sep 17 00:00:00 2001 From: valentine Date: Fri, 14 Jun 2024 22:14:48 +0300 Subject: [PATCH 19/19] add chainId comment --- apps/extension/src/shared/components/numeraires-form.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/extension/src/shared/components/numeraires-form.tsx b/apps/extension/src/shared/components/numeraires-form.tsx index d0f2aa50..71b1e906 100644 --- a/apps/extension/src/shared/components/numeraires-form.tsx +++ b/apps/extension/src/shared/components/numeraires-form.tsx @@ -35,6 +35,9 @@ export const NumeraireForm = ({ const { chainId } = useChainIdQuery(); const { selectedNumeraires, selectNumeraire, saveNumeraires, networkChainId } = useStore(useNumerairesSelector); + + // 'chainId' from 'useChainIdQuery' is not available during onboarding, + // this forces you to use two sources to guarantee 'chainId' for both settings and onboarding const numeraires = useMemo(() => getNumeraireFromRegistry(chainId ?? networkChainId), [chainId]); const [loading, setLoading] = useState(false);