diff --git a/lib/chains/events.ts b/lib/chains/events.ts index bc637b4d3..566448f7c 100644 --- a/lib/chains/events.ts +++ b/lib/chains/events.ts @@ -29,10 +29,11 @@ const getTokenEventsDefault = async (chainId: DocumentedChainId, address: Addres const publicClient = createViemPublicClientForChain(chainId); const logsProvider = getLogsProvider(chainId); - const [openSeaProxyAddress, fromBlock, toBlock, isLoggedIn] = await Promise.all([ + const [openSeaProxyAddress, fromBlock, toBlock, nonce, isLoggedIn] = await Promise.all([ getOpenSeaProxyAddress(address), 0, publicClient.getBlockNumber().then((blockNumber) => Number(blockNumber)), + publicClient.getTransactionCount({ address }), apiLogin(), ]); @@ -40,6 +41,13 @@ const getTokenEventsDefault = async (chainId: DocumentedChainId, address: Addres throw new Error('Failed to create an API session'); } + // If the address is an EOA and has no transactions, we can skip fetching events for efficiency. Note that all deployed contracts have a nonce of >= 1 + // See https://eips.ethereum.org/EIPS/eip-161 + if (nonce === 0) { + console.log('Skipping event fetching for EOA with no transactions', address); + return []; + } + // Create required event filters const getErc721EventSelector = (eventName: 'Transfer' | 'Approval' | 'ApprovalForAll') => { diff --git a/lib/utils/risk.tsx b/lib/utils/risk.tsx index c49831805..79d675a8c 100644 --- a/lib/utils/risk.tsx +++ b/lib/utils/risk.tsx @@ -10,6 +10,7 @@ export const RiskFactorScore: Record = { exploit: 100, phishing_risk: 50, unsafe: 50, + uninitialized: 50, }; export const filterUnknownRiskFactors = (riskFactors: RiskFactor[]): RiskFactor[] => { diff --git a/lib/whois/spender/risk/OnchainSpenderRiskDataSource.ts b/lib/whois/spender/risk/OnchainSpenderRiskDataSource.ts index 0de13c29e..5c99b83e1 100644 --- a/lib/whois/spender/risk/OnchainSpenderRiskDataSource.ts +++ b/lib/whois/spender/risk/OnchainSpenderRiskDataSource.ts @@ -19,11 +19,15 @@ export class OnchainSpenderRiskDataSource implements SpenderDataSource { try { const time = new Date().getTime(); - const bytecode = await publicClient.getCode({ address }); + const [bytecode, nonce] = await Promise.all([ + publicClient.getCode({ address }), + publicClient.getTransactionCount({ address }), + ]); const riskFactors = []; - if (this.isEOA(bytecode)) riskFactors.push({ type: 'eoa', source: 'onchain' }); + if (this.isEOA(bytecode, nonce)) riskFactors.push({ type: 'eoa', source: 'onchain' }); + if (this.isUninitialized(bytecode, nonce)) riskFactors.push({ type: 'uninitialized', source: 'onchain' }); // if (this.isSmallBytecode(bytecode)) riskFactors.push({ type: 'unsafe', source: 'revoke' }); if (this.isOpenSeaProxy(bytecode)) riskFactors.push({ type: 'deprecated', source: 'onchain' }); if (this.hasPhishingRisk(address, bytecode)) riskFactors.push({ type: 'phishing_risk', source: 'onchain' }); @@ -41,11 +45,15 @@ export class OnchainSpenderRiskDataSource implements SpenderDataSource { } } - isEOA(bytecode?: Hex): boolean { - return isNullish(bytecode) || bytecode === '0x'; + isEOA(bytecode: Hex | undefined, nonce: number): boolean { + return isNullish(bytecode) && nonce > 0; } - isSmallBytecode(bytecode: Hex): boolean { + isUninitialized(bytecode: Hex | undefined, nonce: number): boolean { + return isNullish(bytecode) && nonce === 0; + } + + isSmallBytecode(bytecode?: Hex): boolean { return !isNullish(bytecode) && bytecode.length > 0 && bytecode.length < 1000; } diff --git a/locales/en/address.json b/locales/en/address.json index 8745a174b..d03953245 100644 --- a/locales/en/address.json +++ b/locales/en/address.json @@ -88,6 +88,7 @@ }, "phishing_risk": "Increases risk surface in case of phishing", "source": "reported by {source}", + "uninitialized": "No contract deployed to this address", "unsafe": "Potentially unsafe contract code" }, "search": { diff --git a/locales/es/address.json b/locales/es/address.json index 2b4c7404a..1dff48825 100644 --- a/locales/es/address.json +++ b/locales/es/address.json @@ -88,6 +88,7 @@ }, "phishing_risk": "Aumenta la superficie de riesgo en caso de phishing", "source": "reportado por {source}", + "uninitialized": "No contract deployed to this address", "unsafe": "Código de contrato potencialmente inseguro" }, "search": { diff --git a/locales/ja/address.json b/locales/ja/address.json index 3611496ee..04a0eafb9 100644 --- a/locales/ja/address.json +++ b/locales/ja/address.json @@ -88,6 +88,7 @@ }, "phishing_risk": "フィッシングの場合のリスク領域が増加", "source": "{source}に報告された", + "uninitialized": "No contract deployed to this address", "unsafe": "安全でない可能性のある契約コード" }, "search": { diff --git a/locales/ru/address.json b/locales/ru/address.json index c9da61eb4..5b1fd97ff 100644 --- a/locales/ru/address.json +++ b/locales/ru/address.json @@ -88,6 +88,7 @@ }, "phishing_risk": "Увеличивает поверхность риска в случае фишинга", "source": "сообщил(а){source}", + "uninitialized": "No contract deployed to this address", "unsafe": "Потенциально небезопасный код контракта" }, "search": { diff --git a/locales/zh/address.json b/locales/zh/address.json index 856087195..2f698d08e 100644 --- a/locales/zh/address.json +++ b/locales/zh/address.json @@ -88,6 +88,7 @@ }, "phishing_risk": "增加钓鱼风险", "source": "举报人:{source}", + "uninitialized": "No contract deployed to this address", "unsafe": "可能不安全的合约代码" }, "search": { diff --git a/package.json b/package.json index 1bdd13ff9..e877a23e3 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "lint:staged": "yarn lint --error-on-warnings --no-errors-on-unmatched --staged .", "lint:fix": "yarn lint --write", "postinstall": "husky install", - "translations:update": "localazy upload && localazy download && yarn lint", + "translations:update": "localazy upload && localazy download && yarn lint:fix", "translations:update:md": "localazy upload -k localazy.content.keys.json -c localazy.content.json && localazy download -k localazy.content.keys.json -c localazy.content.json && tsx scripts/remove-untranslated-markdown-files.ts", "test": "yarn test:mocha && yarn test:cypress", "test:cypress": "cypress run",