import {z} from 'zod' export type GetApiV4KytScreeningArgs = {chain: string, tx_hash: string, rule_set_id?: string | undefined, mode?: string | undefined}; export type GetApiV4KytScreeningOptions = {baseUrl: string, ApiKeyAuth?: string | undefined, fetch?: typeof fetch | undefined}; export const labelLabels = z.object({address: z.string().optional(), category: z.string().optional(), chain: z.string().optional(), label: z.string().optional(), source: z.string().optional(), sub_category: z.string().optional(), ts: z.number().int().optional()}); export const txscreeningAddressInfo = z.object({address: z.string().optional(), chain: z.string().optional(), entity: z.string().optional(), entity_name: z.string().optional(), labels: z.array(labelLabels).nullable().optional(), labels_status: z.string().optional(), type: z.string().optional()}); export const screeningTxCounterpartyAnalysis = z.object({active_counter_party: z.number().int().optional(), current_balance_usd: z.number().optional(), first_seen: z.string().optional(), label_coverage: z.number().optional(), last_seen: z.string().optional(), net_flow_usd: z.number().optional(), risky_inflow_ratio: z.number().optional(), risky_outflow_ratio: z.number().optional(), time_windows: z.string().optional(), total_inflow_usd: z.number().optional(), total_outflow_usd: z.number().optional(), tx_count: z.number().int().optional(), velocity: z.number().optional()}); export const entityLocalized = z.object({en: z.string().optional(), zh: z.string().optional()}); export const entityEntity = z.object({country: entityLocalized.optional(), description: entityLocalized.optional(), entity_id: z.string().optional(), entity_name: entityLocalized.optional(), link: z.string().optional(), other_link: z.string().optional()}); export const riskCategory = z.enum(["", "Sanctioned", "TerroristFinancing", "ChildAbuse", "Hack", "Scam", "Ransomware", "Darkweb", "Mixing", "Laundering", "NoKYCExchange", "Gambling", "Blocked", "Blacklisted", "UnregisteredVASP", "RiskyEscrow", "HumanTrafficking", "PoliticalFigure", "Unknown", "Guarantee"]); export const riskDirection = z.enum(["inflow", "outflow", "both"]); export const riskExposureHopForDB = z.object({address: z.string().optional(), amount: z.number().optional(), amount_usd: z.number().optional(), chain: z.string().optional(), direction: z.string().optional(), timestamp: z.number().int().optional(), token_symbol: z.string().optional(), tx_hash: z.string().optional()}); export const riskExposurePathForDB = z.object({amount: z.number().optional(), amount_usd: z.number().optional(), hops: z.array(riskExposureHopForDB).optional(), share: z.number().optional()}); export const riskExposureContextForDB = z.object({amount: z.number().optional(), amount_usd: z.number().optional(), counterparty_address: z.string().optional(), direction: z.string().optional(), hop_count: z.number().int().optional(), path_count: z.number().int().optional(), paths: z.array(riskExposurePathForDB).optional(), paths_aggregated: z.boolean().optional(), share: z.number().optional(), tx_count: z.number().int().optional()}); export const riskLevel = z.enum(["None", "Low", "Medium", "High"]); export const riskOrigin = z.enum(["self", "counterparty"]); export const riskSubOrigin = z.enum(["", "label", "entity", "behavior", "structural", "user_blacklist", "address_set", "direct", "indirect", "concentration"]); export const riskTriggerContextForDB = z.object({rule_id: z.string().optional(), rule_name: z.string().optional(), trigger_id: z.string().optional()}); export const riskRiskFactor = z.object({category: riskCategory.optional(), chain: z.string().optional(), description: z.string().optional(), direction: riskDirection.optional(), exposure: riskExposureContextForDB.optional(), id: z.string().optional(), level: riskLevel.optional(), observed_at: z.string().optional(), origin: riskOrigin.optional(), rule_id: z.string().optional(), sub_origin: riskSubOrigin.optional(), trigger: riskTriggerContextForDB.optional(), weight: z.number().optional()}); export const typesRiskResp = z.object({address: z.string().optional(), chain: z.string().optional(), entities: z.array(entityEntity).nullable().optional(), entity: entityEntity.nullable().optional(), entity_ids: z.array(z.string()).nullable().optional(), labels: z.array(labelLabels).nullable().optional(), raw_score: z.number().int().optional(), risk_factors: z.array(riskRiskFactor).nullable().optional(), risk_level: riskLevel.optional(), risk_reasons: z.array(z.string()).nullable().optional(), risk_score: z.number().int().optional()}); export const typesFundFlowTx = z.object({amount: z.string().optional(), chain: z.string().optional(), from: z.string().optional(), hash: z.string().optional(), timestamp: z.number().int().optional(), to: z.string().optional(), token_symbol: z.string().optional(), usd_value: z.number().optional()}); export const typesFundFlow = z.object({address: z.string().optional(), address_categories: z.record(z.string(), z.array(z.string())).optional(), address_risk: z.record(z.string(), typesRiskResp).optional(), chain: z.string().optional(), transactions: z.array(typesFundFlowTx).nullable().optional()}); export const txscreeningIssue = z.object({address_key: z.string().optional(), code: z.string().optional(), message: z.string().optional()}); export const txscreeningAddressRiskFactor = z.object({effective_raw_score: z.number().optional(), factor: riskRiskFactor.optional()}); export const txscreeningSummary = z.object({categories: z.array(riskCategory).nullable().optional(), raw_score: z.number().int().optional(), risk_level: z.string().optional(), risk_score: z.number().int().optional()}); export const screeningTopCounterParty = z.object({address: z.string().optional(), chain: z.string().optional(), direction: z.string().optional(), entity_name: z.string().optional(), entity_type: z.string().optional(), labels: z.array(labelLabels).nullable().optional(), last_interaction: z.string().optional(), name: z.string().optional(), ratio: z.number().optional(), risk_level: z.string().optional(), tx_count: z.string().optional(), volume_usd: z.number().optional()}); export const screeningExposureHop = z.object({address: z.string().optional(), amount: z.number().optional(), amount_usd: z.number().optional(), chain: z.string().optional(), direction: z.string().optional(), timestamp: z.number().int().optional(), token_symbol: z.string().optional(), tx_hash: z.string().optional()}); export const screeningExposurePath = z.object({amount: z.number().optional(), amount_usd: z.number().optional(), hops: z.array(screeningExposureHop).optional(), share: z.number().optional()}); export const screeningExposureContext = z.object({amount: z.number().optional(), amount_usd: z.number().optional(), counterparty_address: z.string().optional(), direction: z.string().optional(), hop_count: z.number().int().optional(), path_count: z.number().int().optional(), paths: z.array(screeningExposurePath).optional(), paths_aggregated: z.boolean().optional(), share: z.number().optional(), tx_count: z.string().optional()}); export const screeningMatchedEntity = z.object({entity_id: z.string().optional(), entity_name: z.string().optional()}); export const screeningTriggerContext = z.object({matched_entities: z.array(screeningMatchedEntity).optional(), rule_id: z.string().optional(), rule_name: z.string().optional(), trigger_id: z.string().optional()}); export const screeningRiskFactor = z.object({category: z.string().optional(), exposure: screeningExposureContext.optional(), level: z.string().optional(), origin: z.string().optional(), sub_origin: z.string().optional(), trigger: screeningTriggerContext.optional(), weight: z.number().optional()}); export const txscreeningAddressAssessment = z.object({assessed_at: z.string().optional(), completeness: z.string().optional(), counterparty: screeningTxCounterpartyAnalysis.nullable().optional(), entity: z.string().optional(), entity_name: z.string().optional(), fund_flow: typesFundFlow.optional(), issues: z.array(txscreeningIssue).optional(), labels: z.array(labelLabels).nullable().optional(), mode: z.string().optional(), risk_factors: z.array(txscreeningAddressRiskFactor).nullable().optional(), summary: txscreeningSummary.optional(), top_counterparty: z.array(screeningTopCounterParty).nullable().optional(), triggered_rules: z.array(screeningRiskFactor).nullable().optional()}); export const txscreeningAddressScreening = z.object({error: z.string().optional(), mode: z.string().optional(), result: txscreeningAddressAssessment.optional(), roles: z.array(z.string()).nullable().optional(), status: z.string().optional(), transfer_ids: z.array(z.string()).nullable().optional()}); export const txscreeningFactorRef = z.object({factor_id: z.string().optional(), screened_address: z.string().optional(), transfer_ids: z.array(z.string()).nullable().optional()}); export const txscreeningFinding = z.object({category: riskCategory.optional(), evidence: z.array(txscreeningFactorRef).nullable().optional(), kind: z.string().optional(), raw_score: z.number().int().optional(), risk_level: z.string().optional(), source_address: z.string().optional()}); export const txscreeningOverview = z.object({candidate_count: z.number().int().optional(), categories: z.array(riskCategory).nullable().optional(), completeness: z.string().optional(), failed_address_count: z.number().int().optional(), findings: z.array(txscreeningFinding).nullable().optional(), key_transfer_count: z.number().int().optional(), raw_score: z.number().int().optional(), risk_level: z.string().optional(), risk_score: z.number().int().optional(), screened_address_count: z.number().int().optional()}); export const txscreeningPolicy = z.object({max_key_transfers: z.number().int().optional(), max_screened_addresses: z.number().int().optional(), version: z.string().optional()}); export const txscreeningTransactionInfo = z.object({block_hash: z.string().optional(), block_number: z.number().int().optional(), chain: z.string().optional(), from: z.string().optional(), hash: z.string().optional(), model: z.string().optional(), status: z.string().optional(), timestamp: z.string().optional(), to: z.string().optional()}); export const txscreeningAggregate = z.object({categories: z.array(riskCategory).nullable().optional(), completeness: z.string().optional(), findings: z.array(txscreeningFinding).nullable().optional(), raw_score: z.number().int().optional(), risk_level: z.string().optional(), risk_score: z.number().int().optional()}); export const txscreeningSelection = z.object({reasons: z.array(z.string()).nullable().optional(), status: z.string().optional()}); export const txscreeningUTXOInput = z.object({addresses: z.array(z.string()).optional(), amount: z.string().optional(), coinbase: z.boolean().optional(), index: z.number().int().optional(), output_index: z.number().int().optional(), raw_amount: z.string().optional(), script_pubkey: z.string().optional(), script_type: z.string().optional(), tx_hash: z.string().optional(), usd_value: z.number().optional()}); export const txscreeningUTXOOutput = z.object({addresses: z.array(z.string()).optional(), amount: z.string().optional(), index: z.number().int().optional(), raw_amount: z.string().optional(), script_pubkey: z.string().optional(), script_type: z.string().optional(), usd_value: z.number().optional()}); export const txscreeningUTXOMovement = z.object({inputs: z.array(txscreeningUTXOInput).optional(), outputs: z.array(txscreeningUTXOOutput).optional()}); export const txscreeningTransfer = z.object({amount: z.string().optional(), applied: z.boolean().optional(), decimals: z.number().int().optional(), from: z.string().optional(), id: z.string().optional(), log_index: z.number().int().optional(), participant_addresses: z.array(z.string()).nullable().optional(), raw_amount: z.string().optional(), risk: txscreeningAggregate.optional(), selection: txscreeningSelection.optional(), to: z.string().optional(), token_address: z.string().optional(), token_symbol: z.string().optional(), trace_address: z.array(z.number().int()).nullable().optional(), type: z.string().optional(), usd_value: z.number().optional(), utxo: txscreeningUTXOMovement.optional()}); export const getApiV4KytScreeningResponse = z.object({code: z.number().int().optional(), data: z.object({address_info: z.record(z.string(), txscreeningAddressInfo).nullable().optional(), address_order: z.array(z.string()).nullable().optional(), address_screenings: z.record(z.string(), txscreeningAddressScreening).nullable().optional(), completeness: z.string().optional(), created_at: z.string().optional(), error: z.string().optional(), issues: z.array(txscreeningIssue).nullable().optional(), overview: txscreeningOverview.optional(), rule_name: z.string().optional(), rule_set_id: z.string().optional(), rule_set_name: z.string().optional(), screening_id: z.string().optional(), selection_policy: txscreeningPolicy.optional(), status: z.string().optional(), transaction: txscreeningTransactionInfo.optional(), transfers: z.array(txscreeningTransfer).nullable().optional(), updated_at: z.string().optional()}).nullable().optional(), message: z.string().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 }; /** * Submit screening for a blockchain transaction * * GET /v4/kyt/screening * * Also accepts auth: ApiSecretAuth */ export const getApiV4KytScreening = async (args: GetApiV4KytScreeningArgs, options: GetApiV4KytScreeningOptions) => { const {baseUrl, fetch: fetchFn = fetch, ApiKeyAuth} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('accept', "application/json") if (args.chain != null) query.append("chain", String(args.chain)) if (args.tx_hash != null) query.append("tx_hash", String(args.tx_hash)) if (args.rule_set_id != null) query.append("rule_set_id", String(args.rule_set_id)) if (args.mode != null) query.append("mode", String(args.mode)) if (ApiKeyAuth !== undefined) headers.set("X-API-Key", ApiKeyAuth) const url = `${baseUrl.replace(/\/+$/, '')}/v4/kyt/screening${query.size > 0 ? `?${query}` : ''}` const res = await fetchFn(url, {method: "GET", headers}) if (!res.ok) throw new Error(`Request failed with status ${res.status}`, {cause: {status: res.status, body: await res.text()}}) return validateResponse(getApiV4KytScreeningResponse, await res.json()) };