diff --git a/pages/code/[slug].tsx b/pages/code/[slug].tsx index c82e66b..33daf0a 100644 --- a/pages/code/[slug].tsx +++ b/pages/code/[slug].tsx @@ -1,7 +1,7 @@ import FileSelectionDrawer from '@/components/Code/FileSelectionDrawer' import PageHead from '@/components/Common/PageHead' import { useNetwork } from '@/contexts/NetworkContext' -import { detectNetworkFromAddress } from '@/utils/detectNetwork' +import { detectNetworkFromAddress, isValidNearAccount } from '@/utils/detectNetwork' import { ascii_to_str } from '@/utils/near/ascii_converter' import { useRpcUrl } from '@/utils/near/rpc' import { bg, color } from '@/utils/theme' @@ -39,14 +39,11 @@ export default function Code() { // Step 1: Detect network from contract address and switch if necessary useEffect(() => { if (accountId) { - // Check if the TLD is valid (.near or .testnet) - const isValidTLD = - accountId.endsWith('.near') || accountId.endsWith('.testnet') - if (!isValidTLD) { - console.log(`Code page: Invalid TLD for contract: ${accountId}`) + if (!isValidNearAccount(accountId)) { + console.log(`Code page: Invalid account ID: ${accountId}`) setLoading(false) setErrorMessage( - `Invalid contract address: ${accountId}. Contract addresses must end with .near or .testnet` + `Invalid contract address: ${accountId}. Must be a named account (.near/.testnet) or an implicit/deterministic account.` ) return } @@ -438,14 +435,11 @@ export default function Code() { // Update the TLD validation to set the invalidTLD state useEffect(() => { if (accountId) { - // Check if the TLD is valid (.near or .testnet) - const isValidTLD = - accountId.endsWith('.near') || accountId.endsWith('.testnet') - if (!isValidTLD) { - console.log(`Code page: Invalid TLD for contract: ${accountId}`) + if (!isValidNearAccount(accountId)) { + console.log(`Code page: Invalid account ID: ${accountId}`) setLoading(false) setErrorMessage( - `Invalid contract address: ${accountId}. Contract addresses must end with .near or .testnet` + `Invalid contract address: ${accountId}. Must be a named account (.near/.testnet) or an implicit/deterministic account.` ) setInvalidTLD(true) return @@ -491,7 +485,8 @@ export default function Code() { textAlign="center" maxW="600px" > - Contract addresses must end with .near or .testnet + Must be a named account (.near/.testnet) or an + implicit/deterministic account. ) : loading ? ( diff --git a/pages/contract/[slug].tsx b/pages/contract/[slug].tsx index 7b82404..20bbea7 100644 --- a/pages/contract/[slug].tsx +++ b/pages/contract/[slug].tsx @@ -1,6 +1,6 @@ import { GithubDto } from '@/Interfaces/github/github.dto' import { useNetwork } from '@/contexts/NetworkContext' -import { detectNetworkFromAddress } from '@/utils/detectNetwork' +import { detectNetworkFromAddress, isValidNearAccount } from '@/utils/detectNetwork' import { extractGitHubDetails } from '@/utils/extractGithub' import { ascii_to_str } from '@/utils/near/ascii_converter' import { useRpcUrl } from '@/utils/near/rpc' @@ -248,11 +248,8 @@ export default function Contract() { // Step 1: Detect network from contract address and switch if necessary useEffect(() => { if (accountId) { - // Check if the TLD is valid (.near or .testnet) - const isValidTLD = - accountId.endsWith('.near') || accountId.endsWith('.testnet') - if (!isValidTLD) { - console.log(`Invalid TLD for contract: ${accountId}`) + if (!isValidNearAccount(accountId)) { + console.log(`Invalid account ID: ${accountId}`) setLoading(false) setInvalidTLD(true) return @@ -282,7 +279,8 @@ export default function Contract() { Invalid contract address: {accountId} - Contract addresses must end with .near or .testnet + Must be a named account (.near/.testnet) or an + implicit/deterministic account. ) : data ? ( diff --git a/utils/detectNetwork.ts b/utils/detectNetwork.ts index c64b7cf..19ecdff 100644 --- a/utils/detectNetwork.ts +++ b/utils/detectNetwork.ts @@ -1,34 +1,48 @@ import { NetworkType } from '@/contexts/NetworkContext' /** - * Detects the network type from a contract address based on its TLD - * @param contractAddress The contract address to analyze - * @returns The detected network type or null if can't be determined + * Checks whether an address is a valid NEAR implicit-style account: + * - NEAR implicit account: 64 lowercase hex characters + * - ETH implicit account: 0x + 40 lowercase hex characters + * - NEAR deterministic account: 0s + 40 lowercase hex characters + */ +export const isImplicitAccount = (address: string): boolean => { + if (!address) return false + return ( + /^[0-9a-f]{64}$/.test(address) || + /^0x[0-9a-f]{40}$/.test(address) || + /^0s[0-9a-f]{40}$/.test(address) + ) +} + +/** + * Detects the network type from a contract address based on its TLD. + * Returns null for implicit/deterministic accounts since they can exist on either network. */ export const detectNetworkFromAddress = ( contractAddress: string ): NetworkType | null => { if (!contractAddress) return null - // Check if the address ends with .near (mainnet) or .testnet (testnet) if (contractAddress.endsWith('.near')) { return 'mainnet' } else if (contractAddress.endsWith('.testnet')) { return 'testnet' } - // If no specific TLD is found, return null + // Implicit and deterministic accounts can exist on either network return null } /** - * Checks if a contract address has a valid NEAR TLD (.near or .testnet) - * @param contractAddress The contract address to check - * @returns Boolean indicating if the address has a valid TLD + * Checks if a contract address is a valid NEAR account identifier. + * Accepts named accounts (.near/.testnet) and implicit/deterministic accounts. */ -export const hasValidTLD = (contractAddress: string): boolean => { +export const isValidNearAccount = (contractAddress: string): boolean => { if (!contractAddress) return false return ( - contractAddress.endsWith('.near') || contractAddress.endsWith('.testnet') + contractAddress.endsWith('.near') || + contractAddress.endsWith('.testnet') || + isImplicitAccount(contractAddress) ) }