diff --git a/apps/extension/src/background/index.ts b/apps/extension/src/background/index.ts index a1418bbd65..531b45d86e 100644 --- a/apps/extension/src/background/index.ts +++ b/apps/extension/src/background/index.ts @@ -14,6 +14,7 @@ import { ExtensionRequester, ExtensionRouter, getNamadaRouterId, + initNamadaLatestSyncBlock, } from "extension"; import { KVPrefix, Ports } from "router"; import { LocalStorage, RevealedPKStorage, VaultStorage } from "storage"; @@ -56,6 +57,7 @@ const init = new Promise(async (resolve) => { const requester = new ExtensionRequester(messenger, routerId); const broadcaster = new ExtensionBroadcaster(localStorage, requester); const sdkService = await SdkService.init(localStorage); + await initNamadaLatestSyncBlock(localStorage); const { cryptoMemory } = sdkService.getSdk(); diff --git a/apps/extension/src/background/keyring/handler.ts b/apps/extension/src/background/keyring/handler.ts index 48c53b8dae..08898e3237 100644 --- a/apps/extension/src/background/keyring/handler.ts +++ b/apps/extension/src/background/keyring/handler.ts @@ -209,9 +209,9 @@ const handleQueryAccountsMsg: ( const { query } = msg; const output = - query && query.accountId - ? await service.queryAccountById(query.accountId) - : await service.queryAccounts(); + query && query.accountId ? + await service.queryAccountById(query.accountId) + : await service.queryAccounts(); return output; }; diff --git a/apps/extension/src/background/keyring/keyring.ts b/apps/extension/src/background/keyring/keyring.ts index 6033d1f8f0..9eb73c7786 100644 --- a/apps/extension/src/background/keyring/keyring.ts +++ b/apps/extension/src/background/keyring/keyring.ts @@ -816,6 +816,16 @@ export class KeyRing { return Result.ok(null); } + async queryLastBlock(): Promise { + try { + const query = this.sdkService.getSdk().rpc; + return await query.queryLastBlock(); + } catch (e) { + console.warn(e); + return NaN; + } + } + async queryBalances( owner: string, tokens: string[] diff --git a/apps/extension/src/background/keyring/service.ts b/apps/extension/src/background/keyring/service.ts index 5b3a60e50b..fe7a095678 100644 --- a/apps/extension/src/background/keyring/service.ts +++ b/apps/extension/src/background/keyring/service.ts @@ -538,12 +538,26 @@ export class KeyRingService { return await this.sdkService.getSdk().masp.hasMaspParams(); } + async queryLastBlock(): Promise { + return await this._keyRing.queryLastBlock(); + } + async shieldedSync(): Promise { const vks = (await this.vaultStorage.findAll(KeyStore)) .filter((a) => a.public.type === AccountType.ShieldedKeys) .map((a) => a.public.owner); - await this.sdkService.getSdk().rpc.shieldedSync(vks); + const startHeight = (await this.localStorage.getLatestSyncBlock()) + ?.lastestSyncBlock; + const lastHeight = await this.queryLastBlock(); + const start_height = startHeight ? BigInt(startHeight) : undefined; + await this.sdkService.getSdk().rpc.shieldedSync(vks, start_height); + + if (startHeight !== undefined && lastHeight) { + await this.localStorage.setLatestSyncBlock({ + lastestSyncBlock: lastHeight, + }); + } } async queryBalances( diff --git a/apps/extension/src/extension/utils.ts b/apps/extension/src/extension/utils.ts index a79d4a3f70..60968d2080 100644 --- a/apps/extension/src/extension/utils.ts +++ b/apps/extension/src/extension/utils.ts @@ -17,6 +17,15 @@ export const getNamadaRouterId = async ( return storedId; }; +export const initNamadaLatestSyncBlock = async ( + store: LocalStorage +): Promise => { + const latestBlock = await store.getLatestSyncBlock(); + if (!latestBlock) { + await store.setLatestSyncBlock({ lastestSyncBlock: 0 }); + } +}; + // Determine if content-scripts can be executed in this environment export class ContentScriptEnv { static readonly produceEnv = (sender: MessageSender): Env => { diff --git a/apps/extension/src/storage/LocalStorage.ts b/apps/extension/src/storage/LocalStorage.ts index 6227574a38..96abb7993a 100644 --- a/apps/extension/src/storage/LocalStorage.ts +++ b/apps/extension/src/storage/LocalStorage.ts @@ -61,26 +61,35 @@ type NamadaExtensionApprovedOriginsType = t.TypeOf< const NamadaExtensionRouterId = t.number; type NamadaExtensionRouterIdType = t.TypeOf; +const NamadaExtensionLatestSyncBlock = t.type({ lastestSyncBlock: t.number }); +type NamadaExtensionLatestBlockType = t.TypeOf< + typeof NamadaExtensionLatestSyncBlock +>; + type LocalStorageTypes = | ChainType | NamadaExtensionApprovedOriginsType - | NamadaExtensionRouterIdType; + | NamadaExtensionRouterIdType + | NamadaExtensionLatestBlockType; type LocalStorageSchemas = | typeof Chain | typeof NamadaExtensionApprovedOrigins - | typeof NamadaExtensionRouterId; + | typeof NamadaExtensionRouterId + | typeof NamadaExtensionLatestSyncBlock; export type LocalStorageKeys = | "chains" | "namadaExtensionApprovedOrigins" | "namadaExtensionRouterId" + | "namadaExtensionLatestSyncBlock" | "tabs"; const schemasMap = new Map([ [Chain, "chains"], [NamadaExtensionApprovedOrigins, "namadaExtensionApprovedOrigins"], [NamadaExtensionRouterId, "namadaExtensionRouterId"], + [NamadaExtensionLatestSyncBlock, "namadaExtensionLatestSyncBlock"], ]); export class LocalStorage extends ExtStorage { @@ -132,6 +141,30 @@ export class LocalStorage extends ExtStorage { ); } + async getLatestSyncBlock(): Promise< + NamadaExtensionLatestBlockType | undefined + > { + const data = await this.getRaw(this.getKey(NamadaExtensionLatestSyncBlock)); + + const Schema = t.union([NamadaExtensionLatestSyncBlock, t.undefined]); + const decodedData = Schema.decode(data); + + if (E.isLeft(decodedData)) { + throw new Error("Latest block is not valid"); + } + + return decodedData.right; + } + + async setLatestSyncBlock( + namadaExtensionLatestSyncBlock: NamadaExtensionLatestBlockType + ): Promise { + await this.setRaw( + this.getKey(NamadaExtensionLatestSyncBlock), + namadaExtensionLatestSyncBlock + ); + } + async getRouterId(): Promise { const data = await this.getRaw(this.getKey(NamadaExtensionRouterId)); diff --git a/apps/namada-interface/src/App/AccountOverview/DerivedAccounts/DerivedAccounts.tsx b/apps/namada-interface/src/App/AccountOverview/DerivedAccounts/DerivedAccounts.tsx index c9ef6a52bc..9c2c3ac5de 100644 --- a/apps/namada-interface/src/App/AccountOverview/DerivedAccounts/DerivedAccounts.tsx +++ b/apps/namada-interface/src/App/AccountOverview/DerivedAccounts/DerivedAccounts.tsx @@ -142,7 +142,7 @@ const DerivedAccounts = (): JSX.Element => { // Show native token first return tokenType === chain.currency.token ? 1 : -1; }) - .filter(([_, amount]) => amount.isGreaterThan(0)) + .filter(([_, amount]) => amount?.isGreaterThan(0)) .map(([token, amount]) => { return ( diff --git a/apps/namada-interface/src/App/Token/TokenSend/TokenSend.tsx b/apps/namada-interface/src/App/Token/TokenSend/TokenSend.tsx index 94301a50c7..d9fe5a8088 100644 --- a/apps/namada-interface/src/App/Token/TokenSend/TokenSend.tsx +++ b/apps/namada-interface/src/App/Token/TokenSend/TokenSend.tsx @@ -70,11 +70,11 @@ const TokenSend = (): JSX.Element => { const shieldedAccountsWithBalance = accounts.filter( ({ details }) => details.isShielded - ).filter(({ balance }) => Object.values(balance).some((amount) => amount.isGreaterThan(0))); + ).filter(({ balance }) => Object.values(balance).some((amount) => amount?.isGreaterThan(0))); const transparentAccountsWithBalance = accounts.filter( ({ details }) => !details.isShielded - ).filter(({ balance }) => Object.values(balance).some((amount) => amount.isGreaterThan(0))); + ).filter(({ balance }) => Object.values(balance).some((amount) => amount?.isGreaterThan(0))); const shieldedTokenData = accountsWithBalanceIntoSelectData( shieldedAccountsWithBalance diff --git a/packages/integrations/src/types/Integration.ts b/packages/integrations/src/types/Integration.ts index a2ef751119..2694d0e7f9 100644 --- a/packages/integrations/src/types/Integration.ts +++ b/packages/integrations/src/types/Integration.ts @@ -28,5 +28,5 @@ export interface Integration { owner: string, tokens?: string[] ) => Promise>; - sync: (owners: string[]) => Promise; + sync: () => Promise; } diff --git a/packages/sdk/src/rpc/rpc.ts b/packages/sdk/src/rpc/rpc.ts index aaa7430e57..535ebebdb9 100644 --- a/packages/sdk/src/rpc/rpc.ts +++ b/packages/sdk/src/rpc/rpc.ts @@ -216,9 +216,19 @@ export class Rpc { * Sync the shielded context * @async * @param vks - Array of viewing keys + * @param start_height - Bigint epoch height to start syncing from * @returns */ - async shieldedSync(vks: string[]): Promise { - await this.query.shielded_sync(vks); + async shieldedSync(vks: string[], start_height?: bigint): Promise { + await this.query.shielded_sync(vks, start_height); + } + + /** + * Get the latest block + * @async + * @returns latest block + */ + async queryLastBlock(): Promise { + return Number(await this.query.query_last_block()); } } diff --git a/packages/shared/lib/src/query.rs b/packages/shared/lib/src/query.rs index 554d414919..34c868bc47 100644 --- a/packages/shared/lib/src/query.rs +++ b/packages/shared/lib/src/query.rs @@ -18,8 +18,9 @@ use namada::sdk::masp_primitives::zip32::ExtendedFullViewingKey; use namada::sdk::rpc::{ format_denominated_amount, get_public_key_at, get_token_balance, get_total_staked_tokens, query_epoch, query_native_token, query_proposal_by_id, query_proposal_votes, - query_storage_value, + query_storage_value, query_block }; +use namada::storage::BlockHeight; use namada::token; use namada::uint::I256; use std::collections::{BTreeMap, HashMap, HashSet}; @@ -255,7 +256,18 @@ impl Query { Ok(result) } - pub async fn shielded_sync(&self, owners: Box<[JsValue]>) -> Result<(), JsError> { + pub async fn query_last_block(&self) -> u64 { + let last_block_height_opt = query_block(&self.client).await.unwrap(); + let last_block_height = + last_block_height_opt.map_or_else(BlockHeight::first, |block| block.height); + last_block_height.0 + } + + pub async fn shielded_sync( + &self, + owners: Box<[JsValue]>, + start_height: Option, + ) -> Result<(), JsError> { let owners: Vec = owners .into_iter() .filter_map(|owner| owner.as_string()) @@ -282,7 +294,7 @@ impl Query { .fetch( &self.client, &DefaultLogger::new(&WebIo), - None, + start_height.map(BlockHeight), None, 1, &[],