From d7e65b12941ea6a85639f5c7cd1708e6164c16f9 Mon Sep 17 00:00:00 2001 From: makylfang Date: Fri, 21 Apr 2023 14:22:59 +0400 Subject: [PATCH 001/153] Added unity check in redirect and mfa (#287) --- src/pages/MFARestoreScreen.vue | 2 +- src/pages/loginRedirect.vue | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pages/MFARestoreScreen.vue b/src/pages/MFARestoreScreen.vue index 195e08c6..f8467bcf 100644 --- a/src/pages/MFARestoreScreen.vue +++ b/src/pages/MFARestoreScreen.vue @@ -155,7 +155,7 @@ async function returnToParent(key: string) { } ) } else { - if (loginSrc === 'rn') { + if (loginSrc === 'rn' || loginSrc === 'flutter' || loginSrc === 'unity') { await connectionToParent.goToWallet() return } diff --git a/src/pages/loginRedirect.vue b/src/pages/loginRedirect.vue index bc4ec012..7b693456 100644 --- a/src/pages/loginRedirect.vue +++ b/src/pages/loginRedirect.vue @@ -81,7 +81,11 @@ async function init() { } ) } else { - if (loginSrc === 'rn' || loginSrc === 'flutter') { + if ( + loginSrc === 'rn' || + loginSrc === 'flutter' || + loginSrc === 'unity' + ) { await connectionToParent.goToWallet() return } From 895c86e21724240d5218f3a74072a6353336aed2 Mon Sep 17 00:00:00 2001 From: mmjee Date: Tue, 25 Apr 2023 18:13:31 +0530 Subject: [PATCH 002/153] Reduce Polling of eth_balance calls from wallet to reduce costs (#286) * Initial implementation * Move relevant balance tracking to the store * Get wallet balance first * Add timers in the wallet middleware * Move certain values to constants --- src/components/AddNetwork.vue | 2 +- src/pages/homeScreen.vue | 25 ++++--------------- src/store/rpc.ts | 47 +++++++++++++++++++++++++++++++++++ src/utils/accountHandler.ts | 4 +-- src/utils/getwalletType.ts | 2 +- src/utils/walletMiddleware.ts | 4 +++ 6 files changed, 60 insertions(+), 24 deletions(-) diff --git a/src/components/AddNetwork.vue b/src/components/AddNetwork.vue index b876dd13..ccf6a0b9 100644 --- a/src/components/AddNetwork.vue +++ b/src/components/AddNetwork.vue @@ -45,7 +45,7 @@ async function handleSubmit() { `RPC URL - ${rpcUrl} already exists, please use different one` ) } else { - const provider = new ethers.providers.JsonRpcProvider( + const provider = new ethers.providers.StaticJsonRpcProvider( rpcConfig.value.rpcUrl ) const chainId = await provider.getNetwork() diff --git a/src/pages/homeScreen.vue b/src/pages/homeScreen.vue index 118385e8..7151c650 100644 --- a/src/pages/homeScreen.vue +++ b/src/pages/homeScreen.vue @@ -6,7 +6,6 @@ import AppLoader from '@/components/AppLoader.vue' import AssetsView from '@/components/AssetsView.vue' import UserWallet from '@/components/UserWallet.vue' import { useRpcStore } from '@/store/rpc' -import { getRequestHandler } from '@/utils/requestHandlerSingleton' const rpcStore = useRpcStore() const walletBalance = ref('') @@ -17,7 +16,6 @@ const loader = ref({ show: false, message: '', }) -let balancePolling function showLoader(message) { loader.value.show = true @@ -34,24 +32,20 @@ onMounted(() => { if (rpcStore.walletBalanceChainId !== rpcStore.selectedChainId) { handleChainChange() } else { - getWalletBalance() + rpcStore.getWalletBalance() } - balancePolling = setInterval(getWalletBalance, 4000) + rpcStore.setUpBalancePolling() } catch (err) { console.log({ err }) } }) -onBeforeUnmount(() => { - if (balancePolling) { - clearInterval(balancePolling) - } -}) +onBeforeUnmount(rpcStore.cleanUpBalancePolling) async function handleChainChange() { showLoader('Fetching Wallet Balance...') try { - await getWalletBalance() + await rpcStore.getWalletBalance() } catch (err) { console.log({ err }) } finally { @@ -59,19 +53,10 @@ async function handleChainChange() { } } -async function getWalletBalance() { - const accountHandler = getRequestHandler().getAccountHandler() - if (accountHandler) { - const balance = (await accountHandler.getBalance()) || '0' - rpcStore.setWalletBalance(balance.toString()) - walletBalance.value = ethers.utils.formatEther(balance.toString()) - } -} - async function handleRefresh() { showLoader('Refreshing wallet balance...') try { - await getWalletBalance() + await rpcStore.getWalletBalance() } catch (err) { console.log({ err }) } finally { diff --git a/src/store/rpc.ts b/src/store/rpc.ts index 48d25089..b396609f 100644 --- a/src/store/rpc.ts +++ b/src/store/rpc.ts @@ -1,6 +1,10 @@ import { defineStore } from 'pinia' import { RpcConfigWallet, CHAIN_LIST } from '@/models/RpcConfigList' +import { getRequestHandler } from '@/utils/requestHandlerSingleton' + +const BALANCE_POLLING_INTERVAL = 10 * 1000 +const BALANCE_POLLING_DURATION = 10 * 60 * 1000 type RpcConfigs = { [chainId: number]: RpcConfigWallet @@ -9,8 +13,12 @@ type RpcConfigs = { type RpcConfigState = { selectedRPCConfig: RpcConfigWallet selectedChainId: string | number + walletBalance: string walletBalanceChainId: string + walletBalancePollingIntervalID: NodeJS.Timer | null + walletBalancePollingCleanupID: NodeJS.Timer | null + rpcConfigs: RpcConfigs | null editChainId: number | null } @@ -20,7 +28,12 @@ export const useRpcStore = defineStore('rpcStore', { ({ selectedRPCConfig: CHAIN_LIST[0], selectedChainId: CHAIN_LIST[0].chainId, + walletBalance: '', + walletBalanceChainId: '', + walletBalancePollingCleanupID: null, + walletBalancePollingIntervalID: null, + rpcConfigs: null, editChainId: null, } as RpcConfigState), @@ -115,5 +128,39 @@ export const useRpcStore = defineStore('rpcStore', { }) this.rpcConfigs = configs }, + + async getWalletBalance() { + const accountHandler = getRequestHandler().getAccountHandler() + if (accountHandler) { + const balance = (await accountHandler.getBalance()) || '0' + this.setWalletBalance(balance.toString()) + } + }, + + cleanUpBalancePolling() { + if (this.walletBalancePollingIntervalID != null) { + clearInterval(this.walletBalancePollingIntervalID) + this.walletBalancePollingIntervalID = null + } + if (this.walletBalancePollingCleanupID != null) { + clearTimeout(this.walletBalancePollingCleanupID) + this.walletBalancePollingCleanupID = null + } + }, + + async setUpBalancePolling() { + this.cleanUpBalancePolling() + await this.getWalletBalance() + // Poll every 10 seconds + this.walletBalancePollingIntervalID = setInterval( + this.getWalletBalance.bind(this), + BALANCE_POLLING_INTERVAL + ) + this.walletBalancePollingCleanupID = setTimeout(() => { + if (this.walletBalancePollingIntervalID != null) { + clearInterval(this.walletBalancePollingIntervalID) + } + }, BALANCE_POLLING_DURATION) + }, }, }) diff --git a/src/utils/accountHandler.ts b/src/utils/accountHandler.ts index 6fc0d02c..b352402f 100644 --- a/src/utils/accountHandler.ts +++ b/src/utils/accountHandler.ts @@ -26,14 +26,14 @@ class AccountHandler { rpcUrl: string = process.env.VUE_APP_WALLET_RPC_URL ) { this.wallet = new ethers.Wallet(privateKey) - this.provider = new ethers.providers.JsonRpcProvider(rpcUrl) + this.provider = new ethers.providers.StaticJsonRpcProvider(rpcUrl) } getBalance() { return this.provider.getBalance(this.wallet.address) } setProvider(url: string) { - this.provider = new ethers.providers.JsonRpcProvider(url) + this.provider = new ethers.providers.StaticJsonRpcProvider(url) } asMiddleware() { diff --git a/src/utils/getwalletType.ts b/src/utils/getwalletType.ts index 4d22621b..4a2efcfe 100644 --- a/src/utils/getwalletType.ts +++ b/src/utils/getwalletType.ts @@ -1,7 +1,7 @@ import { ethers } from 'ethers' const getContract = (rpcUrl, appAddress) => { - const provider = new ethers.providers.JsonRpcProvider(rpcUrl) + const provider = new ethers.providers.StaticJsonRpcProvider(rpcUrl) return new ethers.Contract( appAddress, ['function walletType() view returns (uint)'], diff --git a/src/utils/walletMiddleware.ts b/src/utils/walletMiddleware.ts index d30a364a..f15b3984 100644 --- a/src/utils/walletMiddleware.ts +++ b/src/utils/walletMiddleware.ts @@ -8,6 +8,8 @@ import { PendingJsonRpcResponse, } from 'json-rpc-engine' +import { useRpcStore } from '@/store/rpc' + interface TransactionParams { from: string } @@ -141,6 +143,7 @@ function createWalletMiddleware({ req ) res.result = await processTransaction(txParams) + await useRpcStore().setUpBalancePolling() } async function signTransaction( @@ -158,6 +161,7 @@ function createWalletMiddleware({ req ) res.result = await processSignTransaction(txParams, req) + await useRpcStore().setUpBalancePolling() } // From ecfc117105f26c18edbf453ec1bfd3015dae82fd Mon Sep 17 00:00:00 2001 From: mmjee Date: Thu, 27 Apr 2023 15:33:06 +0530 Subject: [PATCH 003/153] Wallet - NFT Auto-detection (#271) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add a โ€˜databaseโ€™ for NFTs with automatic detection from Ankr * Cleanup * Send all blockchains to ensure all NFTs are retrieved * Add ui updates to ankr nft autofetch * Update toast message * Filter NFTs out rigorously * Update to startsWith instead of includes to check ipfs url * Optimistically bypass the checkOwnership execution if the NFT is autodetected * Reduce complexity * Add NFT balance to the list * Add conditional check to store balance --------- Co-authored-by: shrinathprabhu --- src/components/NFTView.vue | 92 ++++++-------- src/models/NFT.ts | 2 + src/pages/AddOrEditNFTScreen.vue | 101 ++++----------- src/pages/ManageNFTScreen.vue | 49 ++++--- src/services/nft.service.ts | 211 +++++++++++++++++++++++++++++++ src/shims-vue.d.ts | 1 + src/utils/storageWrapper.ts | 1 + 7 files changed, 309 insertions(+), 148 deletions(-) create mode 100644 src/services/nft.service.ts diff --git a/src/components/NFTView.vue b/src/components/NFTView.vue index 6833eeb3..c2717bd1 100644 --- a/src/components/NFTView.vue +++ b/src/components/NFTView.vue @@ -3,6 +3,8 @@ import { onMounted, ref, type Ref, reactive, watch } from 'vue' import { useRouter } from 'vue-router' import type { NFT } from '@/models/NFT' +import { getNFTDetails, modifyIpfsUrl } from '@/services/getNFTDetails.service' +import { NFTDB } from '@/services/nft.service' import { useRpcStore } from '@/store/rpc' import { useUserStore } from '@/store/user' import { checkOwnership } from '@/utils/nftUtils' @@ -11,6 +13,7 @@ import { getStorage } from '@/utils/storageWrapper' const userStore = useUserStore() const rpcStore = useRpcStore() const storage = getStorage() +let nftDB: NFTDB type NFTViewProps = { refreshState?: boolean @@ -26,73 +29,48 @@ const loader = reactive({ message: 'Loading saved NFTs...', }) -function fetchStoredNfts(): NFT[] { - const storedNftsString = storage.local.getItem( - `${userStore.walletAddress}/${rpcStore.selectedRpcConfig?.chainId}/nfts` - ) - if (storedNftsString) { - return (JSON.parse(storedNftsString) as NFT[]).filter( - (contract) => contract.type === 'erc721' || contract.type === 'erc1155' - ) - } else { - return [] +function sanitizeUrl(url?: string) { + if (url && url.startsWith('ipfs://')) { + return modifyIpfsUrl(url) } + return url } async function getNFTAssets() { loader.show = true + nftDB = await NFTDB.create(storage.local, userStore.walletAddress) nfts.value = [] - const storedNfts = fetchStoredNfts() - storedNfts.forEach((nft) => { - nfts.value.push({ - type: nft.type, - name: nft.name, - balance: nft.balance, - imageUrl: nft.imageUrl, - animationUrl: nft.animationUrl, - description: nft.description, - collectionName: nft.collectionName, - tokenId: nft.tokenId, - address: nft.address, - attributes: nft.attributes, - }) - }) - const nftsToRemoveIndex: number[] = [] - const nftCheckOwnerPromises: Promise[] = [] - nfts.value.forEach((nft, index) => { - nftCheckOwnerPromises.push(checkNftOwnership(nft, index, nftsToRemoveIndex)) - }) - - await Promise.all(nftCheckOwnerPromises) - - nfts.value = nfts.value.filter( - (nft, index) => !nftsToRemoveIndex.includes(index) - ) - storage.local.setItem( - `${userStore.walletAddress}/${rpcStore.selectedRpcConfig?.chainId}/nfts`, - JSON.stringify(nfts.value) + const potentialNFTList = nftDB.getNFTs(Number(rpcStore.selectedChainId)) + const chainId = Number(rpcStore.selectedChainId) + + await Promise.all( + potentialNFTList.map(async (nft) => { + const [ownership, details] = await Promise.all([ + nft.autodetected + ? undefined + : checkOwnership(nft.type, { + tokenId: nft.tokenId, + contractAddress: nft.address, + }), + getNFTDetails(nft.tokenUrl, nft.tokenId), + ]) + if (ownership != undefined && !ownership.owner) { + nftDB.removeNFT(nft, chainId) + return + } + + details.name = details.name || `#${nft.tokenId}` + details.imageUrl = sanitizeUrl(details.image_url || details.image) + details.animationUrl = sanitizeUrl( + details.animation_url || details.animations + ) + nfts.value.push(details) + }) ) - loader.show = false } -async function checkNftOwnership( - nft: NFT, - index: number, - nftsToRemoveIndex: number[] -) { - const hasOwnership = await checkOwnership(nft.type, { - tokenId: nft.tokenId, - contractAddress: nft.address, - }) - if (hasOwnership.owner) { - nft.balance = hasOwnership.balance - } else { - nftsToRemoveIndex.push(index) - } -} - function handleManageNFT() { router.push({ name: 'ManageNft' }) } @@ -101,8 +79,10 @@ function getNftDetailValues(nft: NFT) { const attributes = nft.attributes?.length ? JSON.stringify(nft.attributes) : '' + const autodetected = JSON.stringify(nft.autodetected) return { ...nft, + autodetected, attributes, } } diff --git a/src/models/NFT.ts b/src/models/NFT.ts index 992a3be4..1c1dfe1c 100644 --- a/src/models/NFT.ts +++ b/src/models/NFT.ts @@ -13,6 +13,8 @@ type NFTContract = { trait: string value: string }[] + tokenUrl: string + autodetected?: boolean } type NFT = NFTContract & { balance?: number } diff --git a/src/pages/AddOrEditNFTScreen.vue b/src/pages/AddOrEditNFTScreen.vue index 2b42444d..bf64fa1c 100644 --- a/src/pages/AddOrEditNFTScreen.vue +++ b/src/pages/AddOrEditNFTScreen.vue @@ -1,5 +1,5 @@