import {z} from 'zod' export type SolanaEncodingSchema = 'base58' | 'base64'; export type SolanaChain = 'mainnet' | 'testnet' | 'devnet' | 'eclipse_mainnet' | 'eclipse_devnet' | 'sonic_mainnet'; export type SolanaCaip2Chain = '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp' | 'EtWTRABZaYq6iMfeYKouRu166VU2xqa1' | 'EAQLJCV2mh23BsK2P9oYpV5CHVLDNHTxY'; export type SolanaOptions = 'validation' | 'simulation' | 'gas_estimation'; export type RoutersChainAgnosticModelsRequestAccount = {account_id: string, account_creation_timestamp?: string | undefined, user_age?: number | undefined, user_country_code?: string | undefined, account_addresses?: Array | undefined}; export type RoutersChainAgnosticModelsRequestConnection = {user_agent?: string | undefined, ip_address: unknown, origin?: string | undefined, walletconnect_name?: string | undefined, walletconnect_description?: string | undefined}; export type SolanaRequestMetadataSchema = {url?: (string | null) | undefined, non_dapp?: (boolean | null) | undefined, account?: RoutersChainAgnosticModelsRequestAccount | undefined, connection?: RoutersChainAgnosticModelsRequestConnection | undefined}; export type SolanaWalletMethod = 'signAndSendTransaction' | 'signAndSendAllTransactions' | 'signTransaction' | 'signAllTransactions'; export type SolanaExecutionMode = 'standard' | 'jito_bundle'; export type SolanaMessageScanRequestSchema = {encoding?: SolanaEncodingSchema | undefined, chain?: (SolanaChain | SolanaCaip2Chain) | undefined, options?: Array | undefined, account_address: string, transactions: Array, metadata: SolanaRequestMetadataSchema, method?: SolanaWalletMethod | undefined, execution_mode?: SolanaExecutionMode | undefined}; export type CreateApiV0SolanaMessageScanArgs = {body: SolanaMessageScanRequestSchema}; export type CreateApiV0SolanaMessageScanOptions = {baseUrl?: string | undefined, APIKey?: string | undefined, fetch?: typeof fetch | undefined}; export const solanaEncodingSchema = z.enum(["base58", "base64"]); export const solanaResponseStatus = z.enum(["SUCCESS", "ERROR"]); export const solanaNativeToken = z.enum(["SOL", "ETH"]); export const solanaNativeAssetDetailsSchema = z.object({type: solanaNativeToken, decimals: z.number().int(), logo: z.string().nullable().optional()}); export const solanaAssetDiffSchema = z.object({usd_price: z.number().nullable().optional(), summary: z.string().nullable().optional(), value: z.number(), raw_value: z.number().int()}); export const solanaNativeAssetDiff = z.object({asset: solanaNativeAssetDetailsSchema, in: solanaAssetDiffSchema.optional(), out: solanaAssetDiffSchema.optional(), asset_type: z.string()}); export const solanaSplFungibleAssetDetailsSchema = z.object({type: z.literal("TOKEN").optional(), name: z.string(), symbol: z.string(), address: z.string(), decimals: z.number().int(), logo: z.string().nullable().optional()}); export const solanaSplFungibleAssetDiff = z.object({asset: solanaSplFungibleAssetDetailsSchema, in: solanaAssetDiffSchema.optional(), out: solanaAssetDiffSchema.optional(), asset_type: z.string()}); export const solanaSplNonFungibleAssetDetailsSchema = z.object({type: z.literal("NFT").optional(), name: z.string(), symbol: z.string(), address: z.string(), decimals: z.literal(0).optional(), logo: z.string().nullable().optional()}); export const solanaSplNonFungibleAssetDiff = z.object({asset: solanaSplNonFungibleAssetDetailsSchema, in: solanaAssetDiffSchema.optional(), out: solanaAssetDiffSchema.optional(), asset_type: z.string()}); export const solanaCnftDetailsSchema = z.object({type: z.literal("CNFT").optional(), name: z.string(), symbol: z.string(), address: z.string(), decimals: z.literal(0).optional(), logo: z.string().nullable().optional()}); export const solanaCnftAssetDiff = z.object({asset: solanaCnftDetailsSchema, in: solanaAssetDiffSchema.optional(), out: solanaAssetDiffSchema.optional(), asset_type: z.string()}); export const solanaCnftDelegation = z.object({asset: solanaCnftDetailsSchema, asset_type: z.string(), delegate: z.string(), delegation: solanaAssetDiffSchema}); export const solanaFungibleSplTokenDelegation = z.object({asset: solanaSplFungibleAssetDetailsSchema, asset_type: z.string(), delegate: z.string(), delegation: solanaAssetDiffSchema}); export const solanaNonFungibleSplTokenDelegation = z.object({asset: solanaSplNonFungibleAssetDetailsSchema, asset_type: z.string(), delegate: z.string(), delegation: solanaAssetDiffSchema}); export const solanaNativeSOLOwnershipDiff = z.object({asset: solanaNativeAssetDetailsSchema, in: solanaAssetDiffSchema.optional(), out: solanaAssetDiffSchema.optional(), pre_owner: z.string().nullable(), post_owner: z.string().nullable(), asset_type: z.string()}); export const solanaStakedNative = z.enum(["STAKED_SOL", "STAKED_ETH"]); export const solanaStakedNativeDetailsSchema = z.object({type: solanaStakedNative, decimals: z.number().int(), logo: z.string().nullable().optional()}); export const solanaStakedSOLOwnershipDiff = z.object({asset: solanaStakedNativeDetailsSchema, in: solanaAssetDiffSchema.optional(), out: solanaAssetDiffSchema.optional(), pre_owner: z.string().nullable(), post_owner: z.string().nullable(), asset_type: z.string()}); export const solanaFungibleSOLOwnershipDiff = z.object({asset: solanaSplFungibleAssetDetailsSchema, in: solanaAssetDiffSchema.optional(), out: solanaAssetDiffSchema.optional(), pre_owner: z.string().nullable(), post_owner: z.string().nullable(), asset_type: z.string()}); export const solanaNonFungibleSOLOwnershipDiff = z.object({asset: solanaSplNonFungibleAssetDetailsSchema, in: solanaAssetDiffSchema.optional(), out: solanaAssetDiffSchema.optional(), pre_owner: z.string().nullable(), post_owner: z.string().nullable(), asset_type: z.string()}); export const solanaPdaAccountSchema = z.object({account_address: z.string(), description: z.string().nullable().optional(), type: z.literal("PDA").optional(), was_written_to: z.boolean(), was_created: z.boolean(), owner: z.string()}); export const solanaSystemAccountDetailsSchema = z.object({account_address: z.string(), description: z.string().nullable().optional(), type: z.literal("SYSTEM_ACCOUNT").optional(), was_written_to: z.boolean(), was_created: z.boolean()}); export const solanaProgramAccountDetailsSchema = z.object({account_address: z.string(), description: z.string().nullable().optional(), type: z.enum(["PROGRAM", "NATIVE_PROGRAM"]).optional(), was_written_to: z.boolean(), was_created: z.boolean()}); export const solanaTokenAccountDetailsSchema = z.object({account_address: z.string(), description: z.string().nullable().optional(), type: z.literal("TOKEN_ACCOUNT").optional(), was_written_to: z.boolean(), was_created: z.boolean(), mint_address: z.string(), owner_address: z.string()}); export const solanaFungibleMintAccountDetailsSchema = z.object({account_address: z.string(), description: z.string().nullable().optional(), type: z.literal("FUNGIBLE_MINT_ACCOUNT").optional(), was_written_to: z.boolean(), was_created: z.boolean(), name: z.string(), symbol: z.string(), logo: z.string().nullable()}); export const solanaNonFungibleMintAccountDetailsSchema = z.object({account_address: z.string(), description: z.string().nullable().optional(), type: z.literal("NON_FUNGIBLE_MINT_ACCOUNT").optional(), was_written_to: z.boolean(), was_created: z.boolean(), name: z.string(), symbol: z.string(), uri: z.string(), logo: z.string().nullable()}); export const solanaCnftMintAccountDetailsSchema = z.object({account_address: z.string(), description: z.string().nullable().optional(), type: z.literal("CNFT_MINT_ACCOUNT").optional(), was_written_to: z.boolean(), was_created: z.boolean(), name: z.string(), symbol: z.string(), uri: z.string(), logo: z.string().nullable()}); export const solanaTotalUsdDiffSchema = z.object({in: z.number(), out: z.number(), total: z.number().optional()}); export const solanaAccountSummary = z.object({account_assets_diff: z.array(z.union([solanaNativeAssetDiff, solanaSplFungibleAssetDiff, solanaSplNonFungibleAssetDiff, solanaCnftAssetDiff])).optional(), account_delegations: z.array(z.union([solanaCnftDelegation, solanaFungibleSplTokenDelegation, solanaNonFungibleSplTokenDelegation])), account_ownerships_diff: z.array(z.union([solanaNativeSOLOwnershipDiff, solanaStakedSOLOwnershipDiff, solanaFungibleSOLOwnershipDiff, solanaNonFungibleSOLOwnershipDiff])), total_usd_diff: solanaTotalUsdDiffSchema, total_usd_exposure: z.record(z.string(), z.number()).optional()}); export const solanaTransactionAction = z.enum(["native_wrap", "native_transfer", "token_transfer", "swap", "mint", "stake", "approval", "proxy_upgrade", "ownership_change", "set_code_account"]); export const solanaSimulationResultSchema = z.object({assets_diff: z.record(z.string(), z.array(z.union([solanaNativeAssetDiff, solanaSplFungibleAssetDiff, solanaSplNonFungibleAssetDiff, solanaCnftAssetDiff]))).optional(), delegations: z.record(z.string(), z.array(z.union([solanaCnftDelegation, solanaFungibleSplTokenDelegation, solanaNonFungibleSplTokenDelegation]))).optional(), assets_ownership_diff: z.record(z.string(), z.array(z.union([solanaNativeSOLOwnershipDiff, solanaStakedSOLOwnershipDiff, solanaFungibleSOLOwnershipDiff, solanaNonFungibleSOLOwnershipDiff]))), accounts_details: z.array(z.union([solanaPdaAccountSchema, solanaSystemAccountDetailsSchema, solanaProgramAccountDetailsSchema, solanaTokenAccountDetailsSchema, solanaFungibleMintAccountDetailsSchema, solanaNonFungibleMintAccountDetailsSchema, solanaCnftMintAccountDetailsSchema])).optional(), account_summary: solanaAccountSummary, transaction_actions: z.array(z.union([solanaTransactionAction, z.string()])).nullable().optional()}); export const solanaVerdict = z.enum(["Benign", "Warning", "Malicious", "Error"]); export const solanaValidationFeatureType = z.enum(["Benign", "Warning", "Malicious", "Info"]); export const solanaValidationFeature = z.object({type: solanaValidationFeatureType, feature_id: z.string(), description: z.string(), address: z.string().nullable()}); export const solanaValidationResultSchema = z.object({result_type: solanaVerdict, description: z.string(), reason: z.string(), classification: z.string(), features: z.array(z.string()), extended_features: z.array(solanaValidationFeature)}); export const solanaAccountRentFeeSchema = z.object({account_address: z.string(), account_type: z.string(), lamports: z.string()}); export const solanaGasEstimationSchema = z.object({network_fee: z.string(), priority_fee: z.string(), account_rent_fees: z.array(solanaAccountRentFeeSchema).optional(), total: z.string(), used: z.string()}); export const solanaResponseResult = z.object({simulation: solanaSimulationResultSchema, validation: solanaValidationResultSchema, gas_estimation: solanaGasEstimationSchema}); export const solanamodulesTransactionScanningControllersSchemasErrorApiErrorDetails = z.object({type: z.literal("ApiError").optional(), category: z.literal("NODE_ERROR").optional(), message: z.string().optional()}).and(z.record(z.string(), z.unknown())); export const solanamodulesTransactionScanningControllersSchemasErrorTransactionErrorDetails = z.object({type: z.literal("TransactionError").optional(), category: z.literal("REVERT").optional(), message: z.string().optional(), transaction_index: z.number().int()}).and(z.record(z.string(), z.unknown())); export const solanamodulesTransactionScanningControllersSchemasErrorInstructionErrorDetails = z.object({type: z.literal("InstructionError").optional(), category: z.literal("REVERT").optional(), message: z.string().optional(), number: z.number().int().nullable().optional(), code: z.string().nullable().optional(), transaction_index: z.number().int(), instruction_index: z.number().int(), program_account: z.string().nullable().optional()}).and(z.record(z.string(), z.unknown())); export const solanaResponseSchema = z.object({encoding: solanaEncodingSchema, status: solanaResponseStatus, result: solanaResponseResult.optional(), error: z.string().nullable().optional(), error_details: z.discriminatedUnion("type", [solanamodulesTransactionScanningControllersSchemasErrorApiErrorDetails, solanamodulesTransactionScanningControllersSchemasErrorTransactionErrorDetails, solanamodulesTransactionScanningControllersSchemasErrorInstructionErrorDetails]).nullable().optional(), request_id: z.string().nullable()}); 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 Transaction * * POST /v0/solana/message/scan * * Also accepts auth: ClientID */ export const createApiV0SolanaMessageScan = async (args: CreateApiV0SolanaMessageScanArgs, options?: CreateApiV0SolanaMessageScanOptions) => { 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/solana/message/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(solanaResponseSchema, await res.json()) };