diff --git a/frontend/src/pages/AccountReceive.vue b/frontend/src/pages/AccountReceive.vue index 8f40206b9..2a332197d 100644 --- a/frontend/src/pages/AccountReceive.vue +++ b/frontend/src/pages/AccountReceive.vue @@ -84,8 +84,9 @@ import { computed, defineComponent, onMounted, ref, watch } from 'vue'; import { QForm } from 'quasar'; import { UserAnnouncement, KeyPair, AnnouncementDetail, utils } from '@umbracash/umbra-js'; -import { BigNumber, computeAddress, isHexString } from 'src/utils/ethers'; +import { BigNumber, JsonRpcSigner, computeAddress, isHexString } from 'src/utils/ethers'; import useSettingsStore from 'src/store/settings'; +import useWalletStore from 'src/store/wallet'; import useWallet from 'src/store/wallet'; import { filterUserAnnouncements } from 'src/worker/worker'; import AccountReceiveTable from 'components/AccountReceiveTable.vue'; @@ -101,6 +102,7 @@ function useScan() { // Start and end blocks for advanced mode settings const { advancedMode, startBlock, endBlock, setScanBlocks, setScanPrivateKey, scanPrivateKey, resetScanSettings } = useSettingsStore(); + const { signer, userAddress: userWalletAddress } = useWalletStore(); const startBlockLocal = ref(); const endBlockLocal = ref(); const scanPrivateKeyLocal = ref(); @@ -178,7 +180,11 @@ function useScan() { const overrides = { startBlock: startBlockLocal.value, endBlock: endBlockLocal.value }; let allAnnouncements: AnnouncementDetail[] = []; try { - allAnnouncements = await umbra.value.fetchAllAnnouncements(overrides); + allAnnouncements = await umbra.value.fetchSomeAnnouncements( + signer.value as JsonRpcSigner, + userWalletAddress.value as string, + overrides + ); } catch (e) { scanStatus.value = 'waiting'; // reset to the default state because we were unable to fetch announcements throw e; diff --git a/umbra-js/src/classes/Umbra.ts b/umbra-js/src/classes/Umbra.ts index baca53e63..3e673cd9a 100644 --- a/umbra-js/src/classes/Umbra.ts +++ b/umbra-js/src/classes/Umbra.ts @@ -26,7 +26,13 @@ import { } from '../ethers'; import { KeyPair } from './KeyPair'; import { RandomNumber } from './RandomNumber'; -import { blockedStealthAddresses, getEthSweepGasInfo, lookupRecipient, assertSupportedAddress } from '../utils/utils'; +import { + blockedStealthAddresses, + getEthSweepGasInfo, + lookupRecipient, + assertSupportedAddress, + getBlockNumberUserRegistered, +} from '../utils/utils'; import { Umbra as UmbraContract, Erc20 as ERC20 } from '@umbra/contracts-core/typechain'; import { ERC20_ABI, ETH_ADDRESS, UMBRA_ABI, UMBRA_BATCH_SEND_ABI } from '../utils/constants'; import type { Announcement, ChainConfig, EthersProvider, GraphFilterOverride, ScanOverrides, SendOverrides, SubgraphAnnouncement, UserAnnouncement, AnnouncementDetail, SendBatch, SendData} from '../types'; // prettier-ignore @@ -393,6 +399,38 @@ export class Umbra { return this.fetchAllAnnouncementFromLogs(startBlock, endBlock); } + /** + * @notice Fetches some Umbra event logs using The Graph, if available, falling back to RPC if not + * @param overrides Override the start and end block used for scanning; ignored if using The Graph + * @returns A list of Announcement events supplemented with additional metadata, such as the sender, block, + * timestamp, and txhash + */ + async fetchSomeAnnouncements( + Signer: JsonRpcSigner, + address: string, + overrides: ScanOverrides = {} + ): Promise { + const registeredBlockNumebr = await getBlockNumberUserRegistered(address, 0, Signer); + // Get start and end blocks to scan events for + const startBlock = overrides.startBlock || registeredBlockNumebr || this.chainConfig.startBlock; + const endBlock = overrides.endBlock || 'latest'; + + // Try querying events using the Graph, fallback to querying logs. + // The Graph fetching uses the browser's `fetch` method to query the subgraph, so we check + // that window is defined first to avoid trying to use fetch in node environments + if (typeof window !== 'undefined' && this.chainConfig.subgraphUrl) { + try { + const subgraphAnnouncements = await this.fetchAllAnnouncementsFromSubgraph(startBlock, endBlock); + // Map the subgraph amount field from string to BigNumber + return subgraphAnnouncements.map((x) => ({ ...x, amount: BigNumber.from(x.amount) })); + } catch (err) { + return this.fetchAllAnnouncementFromLogs(startBlock, endBlock); + } + } + + return this.fetchAllAnnouncementFromLogs(startBlock, endBlock); + } + /** * @notice Fetches all Umbra event logs using The Graph * @dev Currently ignores the start and end block parameters and returns all events; this may change in a diff --git a/umbra-js/src/utils/utils.ts b/umbra-js/src/utils/utils.ts index a2011310f..04fbd1b24 100644 --- a/umbra-js/src/utils/utils.ts +++ b/umbra-js/src/utils/utils.ts @@ -10,6 +10,7 @@ import { getAddress, HashZero, isHexString, + JsonRpcSigner, keccak256, Overrides, resolveProperties, @@ -241,6 +242,16 @@ export async function lookupRecipient( return { spendingPublicKey: publicKey, viewingPublicKey: publicKey }; } +export async function getBlockNumberUserRegistered(address: string, startblock = 0, Signer: JsonRpcSigner) { + address = getAddress(address); // address input validation + const { chainId } = await Signer.provider.getNetwork(); + const txHistoryProvider = new TxHistoryProvider(chainId); + const history = await txHistoryProvider.getHistory(address, startblock); + const StealthKeyRegistryAddress = '0x31fe56609C65Cd0C510E7125f051D440424D38f3'; + const registryBlock = history.find((tx) => tx.to === StealthKeyRegistryAddress); + return registryBlock?.blockNumber; +} + /** * @notice Throws if provided public key is not on the secp256k1 curve * @param point Uncompressed public key as hex string