import {z} from 'zod' export type TokenMetadata = {domain?: (string | null) | undefined}; export type TokenScanSupportedChains = 'arbitrum' | 'avalanche' | 'base' | 'bsc' | 'ethereum' | 'optimism' | 'polygon' | 'zora' | 'solana' | 'starknet' | 'starknet-sepolia' | 'stellar' | 'linea' | 'degen' | 'zksync' | 'scroll' | 'blast' | 'soneium-minato' | 'base-sepolia' | 'bitcoin' | 'abstract' | 'soneium' | 'ink' | 'berachain' | 'unichain' | 'ronin' | 'sui' | 'hedera' | 'hyperevm' | 'xlayer' | 'monad' | 'megaeth' | 'tempo' | 'sei' | 'kaia' | 'tron' | 'robinhood' | 'flare' | 'arc'; export type BulkTokenValidationRequest = {metadata?: TokenMetadata | undefined, chain: TokenScanSupportedChains, tokens: Array}; export type CreateApiV0TokenBulkScanArgs = {body: BulkTokenValidationRequest}; export type CreateApiV0TokenBulkScanOptions = {baseUrl?: string | undefined, APIKey?: string | undefined, fetch?: typeof fetch | undefined}; export const resultType = z.enum(["Benign", "Warning", "Malicious", "Spam"]); export const attackType = z.object({score: z.string(), threshold: z.string().optional(), features: z.object({}).optional()}); export const tokenScanSupportedChains = z.enum(["arbitrum", "avalanche", "base", "bsc", "ethereum", "optimism", "polygon", "zora", "solana", "starknet", "starknet-sepolia", "stellar", "linea", "degen", "zksync", "scroll", "blast", "soneium-minato", "base-sepolia", "bitcoin", "abstract", "soneium", "ink", "berachain", "unichain", "ronin", "sui", "hedera", "hyperevm", "xlayer", "monad", "megaeth", "tempo", "sei", "kaia", "tron", "robinhood", "flare", "arc"]); export const amount = z.object({amount: z.number().nullable().optional(), amount_wei: z.string().nullable().optional()}); export const externalLinks = z.object({homepage: z.string().nullable().optional(), twitter_page: z.string().nullable().optional(), telegram_channel_id: z.string().nullable().optional()}); export const impersonationSource = z.enum(["TOP_TOKEN", "USER_DEFINED"]); export const impersonationTarget = z.object({chain: z.string(), address: z.string(), name: z.string().nullable().optional(), symbol: z.string().nullable().optional(), source: impersonationSource.nullable().optional()}); export const solanaMetadata = z.object({type: z.string().nullable().optional(), name: z.string().nullable().optional(), symbol: z.string().nullable().optional(), decimals: z.number().int().nullable().optional(), image_url: z.string().nullable().optional(), description: z.string().nullable().optional(), deployer: z.string().nullable().optional(), deployer_balance: amount.nullable().optional(), contract_balance: amount.nullable().optional(), owner_balance: amount.nullable().optional(), owner: z.string().nullable().optional(), creation_timestamp: z.string().nullable().optional(), external_links: externalLinks.optional(), urls: z.array(z.string()).nullable().optional(), malicious_urls: z.array(z.string()).nullable().optional(), token_creation_initiator: z.string().nullable().optional(), impersonation_targets: z.array(impersonationTarget).nullable().optional(), mint_authority: z.string().nullable().optional(), update_authority: z.string().nullable().optional(), freeze_authority: z.string().nullable().optional(), permanent_delegate: z.string().nullable().optional()}); export const bitcoinMetadataToken = z.object({type: z.string().nullable().optional(), name: z.string().nullable().optional(), symbol: z.string().nullable().optional(), decimals: z.number().int().nullable().optional(), id: z.string().nullable().optional(), number: z.number().int().nullable().optional(), formatted_name: z.string().nullable().optional()}); export const evmMetadataToken = z.object({type: z.string().nullable().optional(), name: z.string().nullable().optional(), symbol: z.string().nullable().optional(), decimals: z.number().int().nullable().optional(), image_url: z.string().nullable().optional(), description: z.string().nullable().optional(), deployer: z.string().nullable().optional(), deployer_balance: amount.nullable().optional(), contract_balance: amount.nullable().optional(), owner_balance: amount.nullable().optional(), owner: z.string().nullable().optional(), creation_timestamp: z.string().nullable().optional(), external_links: externalLinks.optional(), urls: z.array(z.string()).nullable().optional(), malicious_urls: z.array(z.string()).nullable().optional(), token_creation_initiator: z.string().nullable().optional(), impersonation_targets: z.array(impersonationTarget).nullable().optional()}); export const fees = z.object({transfer: z.number().nullable().optional(), transfer_fee_max_amount: z.number().int().nullable().optional(), buy: z.number().nullable().optional(), sell: z.number().nullable().optional()}); export const featureID = z.enum(["VERIFIED_CONTRACT", "UNVERIFIED_CONTRACT", "HIGH_TRADE_VOLUME", "MARKET_PLACE_SALES_HISTORY", "HIGH_REPUTATION_TOKEN", "ONCHAIN_ACTIVITY_VALIDATOR", "STATIC_CODE_SIGNATURE", "KNOWN_MALICIOUS", "IS_EOA", "IS_CONTRACT", "ERC20_CONTRACT", "TRUSTED_CONTRACT", "BENIGN_CREATOR", "METADATA", "AIRDROP_PATTERN", "IMPERSONATOR", "INORGANIC_VOLUME", "DYNAMIC_ANALYSIS", "CONCENTRATED_SUPPLY_DISTRIBUTION", "HONEYPOT", "INSUFFICIENT_LOCKED_LIQUIDITY", "UNSTABLE_TOKEN_PRICE", "RUGPULL", "KNOWN_RUGPULL_CODE_PATTERN", "WASH_TRADING", "CONSUMER_OVERRIDE", "INAPPROPRIATE_CONTENT", "HIGH_TRANSFER_FEE", "HIGH_BUY_FEE", "HIGH_SELL_FEE", "UNSELLABLE_TOKEN", "IS_MINTABLE", "MODIFIABLE_TAXES", "CAN_BLACKLIST", "CAN_WHITELIST", "HAS_TRADING_COOLDOWN", "EXTERNAL_FUNCTIONS", "HIDDEN_OWNER", "TRANSFER_PAUSEABLE", "OWNERSHIP_RENOUNCED", "OWNER_CAN_CHANGE_BALANCE", "PROXY_CONTRACT", "SIMILAR_MALICIOUS_CONTRACT", "IMPERSONATOR_SENSITIVE_ASSET", "IMPERSONATOR_HIGH_CONFIDENCE", "IMPERSONATOR_MEDIUM_CONFIDENCE", "IMPERSONATOR_LOW_CONFIDENCE", "IMPERSONATION_PROTECTED", "FAKE_VOLUME", "HIDDEN_SUPPLY_BY_KEY_HOLDER", "FAKE_TRADE_MAKER_COUNT", "TRANSFER_FROM_REVERTS", "OFFENSIVE_TOKEN_METADATA", "LISTED_ON_CENTRALIZED_EXCHANGE", "SANCTIONED_CREATOR", "SPAM_TEXT", "BONDING_CURVE_TOKEN", "HEAVILY_SNIPED", "SOLANA_TOKEN_2022", "POST_DUMP", "DEX_PAID", "LOW_REPUTATION_CREATOR", "SNIPE_AT_MINT", "TRANSFER_HOOK_ENABLED", "CONFIDENTIAL_TRANSFERS_ENABLED", "NON_TRANSERABLE", "TOKEN_BACKDOOR", "CREATED_VIA_LAUNCHPAD", "COMPROMISED_TOKEN", "LONG_FUND_TRAIL", "METADATA_PROMPT_INJECTION"]); export const featureType = z.enum(["Benign", "Info", "Warning", "Malicious"]); export const feature = z.object({feature_id: featureID, type: featureType, description: z.string()}); export const tradingLimits = z.object({max_buy: amount.nullable().optional(), max_sell: amount.nullable().optional(), max_holding: amount.nullable().optional(), sell_limit_per_block: amount.nullable().optional()}); export const topHolderLabel = z.enum(["market", "locker", "wallet", "contract", "program"]); export const topHolder = z.object({label: topHolderLabel.nullable().optional(), name: z.string().nullable().optional(), address: z.string().nullable().optional(), holding_percentage: z.number().nullable().optional()}); export const marketType = z.enum(["BONDING_CURVE", "AMM", "UNKNOWN"]); export const tokenMarket = z.object({market_type: marketType, market_name: z.string(), pair_name: z.string(), address: z.string(), base_token: z.string(), quote_token: z.string(), reserve_in_usd: z.number()}); export const financialStats = z.object({supply: z.number().int().nullable().optional(), holders_count: z.number().int().nullable().optional(), usd_price_per_unit: z.number().nullable().optional(), burned_liquidity_percentage: z.number().nullable().optional(), locked_liquidity_percentage: z.number().nullable().optional(), top_holders: z.array(topHolder).optional(), total_reserve_in_usd: z.number().nullable().optional(), dev_holding_percentage: z.number().nullable().optional(), initial_snipers_holding_percentage: z.number().nullable().optional(), snipers_holding_percentage: z.number().nullable().optional(), bundlers_holding_percentage: z.number().nullable().optional(), insiders_holding_percentage: z.number().nullable().optional(), markets: z.array(tokenMarket).optional()}); export const tokenValidationResponse = z.object({result_type: resultType, malicious_score: z.string(), attack_types: z.record(z.string(), attackType), chain: tokenScanSupportedChains, address: z.string(), metadata: z.union([solanaMetadata, bitcoinMetadataToken, evmMetadataToken]), fees: fees, features: z.array(feature).optional(), trading_limits: tradingLimits, financial_stats: financialStats}); export const bulkScanErrorType = z.enum(["PENDING_SCAN_RESULT", "NOT_A_TOKEN"]); export const bulkScanError = z.object({message: bulkScanErrorType}); export const bulkTokenValidationResponse = z.object({results: z.record(z.string(), tokenValidationResponse), errors: z.record(z.string(), bulkScanError).optional()}); export const validateResponse = async ( schema: { readonly '~standard': { readonly validate: (value: unknown) => ({ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }) | Promise<{ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }> } }, value: unknown ): Promise => { const result = await schema['~standard'].validate(value) if (result.issues !== undefined) { const detail = result.issues .map(({ message, path }) => path?.length ? `${path.map((segment) => String(typeof segment === 'object' ? segment.key : segment)).join('.')}: ${message}` : message ) .join('; ') throw new Error(`Response does not match the schema: ${detail}`, { cause: { issues: result.issues } }) } return result.value }; /** * Scan Token Bulk * * POST /v0/token-bulk/scan * * Also accepts auth: ClientID */ export const createApiV0TokenBulkScan = async (args: CreateApiV0TokenBulkScanArgs, options?: CreateApiV0TokenBulkScanOptions) => { const {baseUrl = "https://api.blockaid.io", fetch: fetchFn = fetch, APIKey} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('content-type', "application/json") headers.set('accept', "application/json") if (APIKey !== undefined) headers.set("X-API-Key", APIKey) const url = `${baseUrl.replace(/\/+$/, '')}/v0/token-bulk/scan${query.size > 0 ? `?${query}` : ''}` const res = await fetchFn(url, {method: "POST", headers, body: JSON.stringify(args.body)}) if (!res.ok) throw new Error(`Request failed with status ${res.status}`, {cause: {status: res.status, body: await res.text()}}) return validateResponse(bulkTokenValidationResponse, await res.json()) };