import {z} from 'zod' export type V1Chain = string; export type V1Account = {account_id: string, account_creation_timestamp?: string | undefined, user_age?: number | undefined, user_country_code?: string | undefined, account_addresses?: Array | undefined}; export type V1Connection = {user_agent?: string | undefined, ip_address: unknown, origin?: string | undefined, walletconnect_name?: string | undefined, walletconnect_description?: string | undefined}; export type V1ScanRequestMetadata = {account?: V1Account | undefined, connection?: V1Connection | undefined}; export type V1TokenScanRequest = {asset: string, chain: V1Chain, metadata?: V1ScanRequestMetadata | undefined}; export type CreateApiV1TokenScanArgs = {body: V1TokenScanRequest}; export type CreateApiV1TokenScanOptions = {baseUrl?: string | undefined, APIKey?: string | undefined, fetch?: typeof fetch | undefined}; export const v1EntityType = z.enum(["TOKEN", "ADDRESS", "TRANSACTION", "SIGNATURE", "WEBSITE"]); export const v1Chain = z.string(); export const v1TokenMetadataDetails = z.object({address: z.string(), chain: v1Chain}); export const v1TokenMetadata = z.object({entity_type: v1EntityType, details: v1TokenMetadataDetails}); export const v1RiskSeverityLevel = z.enum(["none", "low", "medium", "high", "critical"]); export const v1Severity = z.object({level: v1RiskSeverityLevel, score: z.number().int().gte(0).lte(100)}); export const v1Profile = z.object({id: z.string()}); export const v1Proximity = z.enum(["ownership", "counterparty", "indirect"]); export const v1Evidence = z.record(z.string(), z.unknown()); export const v1Signal = z.object({id: z.string(), name: z.string(), description: z.string(), evidence: v1Evidence}); export const v1NegativeCategory = z.object({category: z.string(), proximity: v1Proximity.optional(), name: z.string(), description: z.string(), signals: z.array(v1Signal)}); export const v1PositiveCategory = z.object({category: z.string(), name: z.string(), description: z.string(), signals: z.array(v1Signal)}); export const v1Risk = z.object({severity: v1Severity, profile: v1Profile, negative_categories: z.array(v1NegativeCategory), positive_categories: z.array(v1PositiveCategory)}); export const v1ExternalLinks = z.object({homepage: z.string().nullable().optional(), twitter: z.string().nullable().optional(), telegram: z.string().nullable().optional()}); export const v1TokenPropertiesMetadata = z.object({entity_type: z.string(), name: z.string().nullable(), symbol: z.string().nullable(), decimals: z.number().int().gte(0).optional(), logo_url: z.string().nullable(), description: z.string().nullable(), external_links: v1ExternalLinks, urls: z.array(z.string())}); export const v1TokenCreation = z.object({timestamp: z.string().nullable().optional(), deployer: z.string().nullable().optional(), creator: z.string().nullable().optional()}); export const v1TokenAuthority = z.object({role: z.string(), address: z.string().nullable()}); export const v1Amount = z.object({value: z.string(), raw_value: z.string()}); export const v1TokenFee = z.object({percentage: z.number().gte(0).lte(100), max_amount: v1Amount.optional()}); export const v1TokenFees = z.object({buy: v1TokenFee.nullable().optional(), sell: v1TokenFee.nullable().optional(), transfer: v1TokenFee.nullable().optional()}); export const v1TokenLimits = z.object({max_buy: v1Amount.nullable().optional(), max_sell: v1Amount.nullable().optional(), max_holding: v1Amount.nullable().optional()}); export const v1TokenTopHolder = z.object({address: z.string(), holding_percentage: z.number().gte(0).lte(100).nullable(), alias: z.string().nullable()}); export const v1TokenBalances = z.object({contract: v1Amount.nullable().optional(), owner: v1Amount.nullable().optional(), deployer: v1Amount.nullable().optional()}); export const v1TokenHoldings = z.object({total_supply: v1Amount.nullable(), holders_count: z.number().int().gte(0).nullable(), top_holders: z.array(v1TokenTopHolder), dev_holding_percentage: z.number().gte(0).lte(100).nullable().optional(), insiders_holding_percentage: z.number().gte(0).lte(100).nullable().optional(), snipers_holding_percentage: z.number().gte(0).lte(100).nullable().optional(), initial_snipers_holding_percentage: z.number().gte(0).lte(100).nullable().optional(), bundlers_holding_percentage: z.number().gte(0).lte(100).nullable().optional(), balances: v1TokenBalances.optional()}); export const v1TokenMarket = z.object({price_usd: z.number().nullable().optional(), volume_24h_usd: z.number().nullable().optional()}); export const v1TokenPool = z.object({market_type: z.enum(["bonding_curve", "amm", "unknown", "null"]).nullable(), market_name: z.string().nullable(), pair_name: z.string().nullable(), address: z.string().nullable(), base_token: z.string(), quote_token: z.string(), reserve_usd: z.number().nullable()}); export const v1TokenLiquidity = z.object({total_reserve_usd: z.number().nullable(), locked_liquidity_percentage: z.number().gte(0).lte(100).nullable(), burned_liquidity_percentage: z.number().gte(0).lte(100).nullable(), pools: z.array(v1TokenPool)}); export const v1TokenProperties = z.object({metadata: v1TokenPropertiesMetadata.optional(), creation: v1TokenCreation.optional(), authorities: z.array(v1TokenAuthority).optional(), fees: v1TokenFees.optional(), limits: v1TokenLimits.optional(), holdings: v1TokenHoldings.optional(), market: v1TokenMarket.optional(), liquidity: v1TokenLiquidity.optional()}); export const v1TokenScanResponse = z.object({request_id: z.string(), metadata: v1TokenMetadata, risk: v1Risk, properties: v1TokenProperties.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 a token * * POST /v1/token/scan * * Also accepts auth: ClientID */ export const createApiV1TokenScan = async (args: CreateApiV1TokenScanArgs, options?: CreateApiV1TokenScanOptions) => { 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(/\/+$/, '')}/v1/token/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(v1TokenScanResponse, await res.json()) };