import {z} from 'zod' export type PolicyType = 'TRANSFER' | 'STAKE' | 'CONTRACT_CALL' | 'TYPED_MESSAGE' | 'APPROVE' | 'MINT' | 'BURN' | 'RAW' | 'COMPLIANCE' | 'DEPLOYMENT' | 'PROGRAM_CALL' | 'DAPP_CONNECTION' | 'UPGRADE' | 'ORDER' | 'AML_CHAINALYSIS_V2_SCREENING' | 'AML_CHAINALYSIS_V2_POST_SCREENING' | 'AML_ELLIPTIC_HOLISTIC_SCREENING' | 'AML_ELLIPTIC_HOLISTIC_POST_SCREENING' | 'TR_NOTABENE_SCREENING' | 'TR_NOTABENE_POST_SCREENING'; export type GetApiPolicyDraftArgs = {policyType: PolicyType}; export type GetApiPolicyDraftOptions = {baseUrl?: string | undefined, fetch?: typeof fetch | undefined}; export const policyType = z.enum(["TRANSFER", "STAKE", "CONTRACT_CALL", "TYPED_MESSAGE", "APPROVE", "MINT", "BURN", "RAW", "COMPLIANCE", "DEPLOYMENT", "PROGRAM_CALL", "DAPP_CONNECTION", "UPGRADE", "ORDER", "AML_CHAINALYSIS_V2_SCREENING", "AML_CHAINALYSIS_V2_POST_SCREENING", "AML_ELLIPTIC_HOLISTIC_SCREENING", "AML_ELLIPTIC_HOLISTIC_POST_SCREENING", "TR_NOTABENE_SCREENING", "TR_NOTABENE_POST_SCREENING"]); export const policyUserIds = z.array(z.string()); export const policyGroupIds = z.array(z.string()); export const policyOperator = z.literal("INCLUDES"); export const initiatorConfig = z.object({users: policyUserIds.optional(), groups: policyGroupIds.optional(), services: z.array(z.string()).optional(), fbAdminGroups: z.array(z.string()).optional(), exchange: z.array(z.string()).optional(), operator: policyOperator.optional()}); export const initiatorConfigPattern = z.union([z.literal("*"), initiatorConfig]); export const assetTypesConfig = z.array(z.union([z.string(), z.literal("*")])); export const assetConfig = z.object({nftTransfer: z.boolean(), assetTypes: assetTypesConfig.optional(), operator: policyOperator.optional()}); export const accountType2 = z.enum(["EXCHANGE", "UNMANAGED", "VAULT", "GLOBAL_WHITELIST", "NETWORK_CONNECTION", "FIAT_ACCOUNT", "CONNECTED_ACCOUNT", "ONE_TIME_ADDRESS", "SRC_DST_NA", "MULTIPLE_VENUES", "END_USER_WALLET", "OEC_PARTNER", "CONTRACT", "INTERNAL", "EXTERNAL", "DAPP_CONNECTION", "UNKNOWN", "*", "ANY"]); export const accountIdentifier = z.object({type: accountType2, id: z.string().optional(), subType: z.string().optional(), address: z.string().optional()}); export const policyTag = z.object({id: z.string()}); export const sourceConfig = z.object({type: z.array(accountType2).optional(), subType: z.array(accountIdentifier).optional(), ids: z.array(accountIdentifier).optional(), tags: z.array(policyTag).optional(), operator: policyOperator, matchFrom: z.literal("ACCOUNT").optional()}); export const destinationConfig = z.object({type: z.array(accountType2).optional(), subType: z.array(accountIdentifier).optional(), ids: z.array(accountIdentifier).optional(), tags: z.array(policyTag).optional(), operator: policyOperator, matchFrom: z.enum(["ACCOUNT", "SOURCE"]).optional(), addressType: z.enum(["*", "WHITELISTED", "ONE_TIME"])}); export const accountConfig = z.object({type: z.array(accountType2).optional(), subType: z.array(accountIdentifier).optional(), ids: z.array(accountIdentifier).optional(), tags: z.array(policyTag).optional(), operator: policyOperator, matchFrom: z.enum(["ACCOUNT", "SOURCE"]).optional()}); export const orderSide = z.enum(["BUY", "SELL", "ANY"]); export const approversConfig = z.object({canInitiatorApprove: z.boolean(), operator: z.enum(["AND", "OR"]).optional(), allowOperatorAsAuthorizer: z.boolean().optional(), approvalGroups: z.array(z.object({threshold: z.number(), users: policyUserIds.optional(), groups: policyGroupIds.optional()})).optional()}); export const designatedSignersConfig = z.object({type: z.enum(["SINGLE", "MULTIPLE", "INITIATOR"]), users: policyUserIds.optional(), groups: policyGroupIds.optional()}); export const verdictConfig = z.object({action: z.enum(["ALLOW", "BLOCK", "2-TIER", "ACCEPT", "REJECT", "ALERT", "SCREEN", "PASS", "WAIT", "FREEZE", "CANCEL"]), approvers: approversConfig.optional(), designatedSigners: designatedSignersConfig.optional()}); export const amountRangeMinMax2 = z.object({min: z.string(), max: z.string().optional()}); export const policyCurrency = z.enum(["NATIVE", "USD", "EUR"]); export const timePeriodMatchType = z.enum(["PER_SINGLE_MATCH", "ACROSS_ALL_MATCHES"]); export const timePeriodConfig = z.object({seconds: z.string(), initiator: timePeriodMatchType, source: timePeriodMatchType, destination: timePeriodMatchType}); export const amountOverTimeConfig = z.object({range: amountRangeMinMax2, currency: policyCurrency, timePeriod: timePeriodConfig}); export const amountConfig = z.object({range: amountRangeMinMax2, currency: policyCurrency.optional()}); export const contractMethodConfig = z.object({methodCalls: z.array(z.string()), operator: z.enum(["EITHER", "EXACT"]), payloadSuffix: z.string().optional()}); export const contractMethodPattern = z.union([z.literal("*"), contractMethodConfig]); export const programCallConfig = z.object({allowedSolanaProgramCalls: z.enum(["*", "WHITELISTED"])}); export const screeningMetadataConfig = z.object({direction: z.enum(["INBOUND", "OUTBOUND", "ANY"]).optional(), provider: z.enum(["CHAINALYSIS", "CHAINALYSIS_V2", "ELLIPTIC", "ELLIPTIC_HOLISTIC", "NOTABENE"]).nullable().optional(), riskRating: z.enum(["LOW", "MEDIUM", "HIGH", "SEVERE", "ANY"]).optional(), riskScore: z.string().optional(), exposureType: z.enum(["DIRECT", "INDIRECT", "ANY"]).optional(), category: z.array(z.string()).optional(), name: z.array(z.string()).optional(), categoryId: z.array(z.string()).optional(), status: z.enum(["COMPLETED", "PENDING", "REJECTED", "FAILED", "CANCELED", "BLOCKING_TIME_EXPIRED"]).optional(), sourceAddress: z.string().optional(), destAddress: z.string().optional()}); export const amountRange = z.object({range: amountRangeMinMax2}); export const dAppAddressConfig = z.object({globalWhitelisted: z.array(z.string()), tenantWhitelisted: z.array(z.string()), urls: z.array(z.string()), operator: policyOperator}); export const derivationPathConfig = z.object({path: z.array(z.number()), partial: z.boolean().optional()}); export const policyRule = z.object({name: z.string(), id: z.string(), policyEngineVersion: z.literal("v2"), type: policyType, subType: policyType.optional(), initiator: initiatorConfigPattern, asset: assetConfig.optional(), source: sourceConfig, destination: destinationConfig.optional(), account: accountConfig.optional(), side: orderSide.optional(), verdict: verdictConfig, amountOverTime: amountOverTimeConfig.optional(), amount: amountConfig.optional(), externalDescriptor: z.string().optional(), method: contractMethodPattern.optional(), isGlobalPolicy: z.boolean().optional(), programCall: programCallConfig.optional(), screeningMetadata: screeningMetadataConfig.optional(), quoteAsset: assetConfig.optional(), baseAsset: assetConfig.optional(), quoteAmount: amountRange.optional(), baseAmount: amountRange.optional(), dAppAddress: dAppAddressConfig.optional(), derivationPath: derivationPathConfig.optional(), index: z.number().optional()}); export const policyMetadataEntry = z.object({editedBy: z.string().optional(), editedAt: z.string().optional(), publishedBy: z.string().optional(), publishedAt: z.string().optional()}); export const policyMetadata = z.record(z.string(), policyMetadataEntry); export const draftResponse = z.object({status: z.string(), rules: z.array(policyRule), draftId: z.string(), metadata: policyMetadata}); export const policyRuleError = z.object({errorMessage: z.string(), errorCode: z.number(), errorCodeName: z.string(), errorField: z.enum(["operator", "operators", "authorizationGroups", "designatedSigner", "designatedSigners", "contractMethods", "amountAggregation", "src", "dst"])}); export const policyRuleCheckResult = z.object({index: z.number(), status: z.enum(["ok", "failure"]), errors: z.array(policyRuleError)}); export const policyCheckResult = z.object({errors: z.number(), results: z.array(policyRuleCheckResult)}); export const policyValidation = z.object({status: z.string(), checkResult: policyCheckResult}); export const draftReviewAndValidationResponse = z.object({draftResponse: draftResponse, validation: policyValidation}); 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 }; /** * Get the active draft by policy type * * GET /policy/draft */ export const getApiPolicyDraft = async (args: GetApiPolicyDraftArgs, options?: GetApiPolicyDraftOptions) => { const {baseUrl = "https://api.fireblocks.io/v1", fetch: fetchFn = fetch} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('accept', "application/json") if (args.policyType != null) query.append("policyType", String(args.policyType)) const url = `${baseUrl.replace(/\/+$/, '')}/policy/draft${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(draftReviewAndValidationResponse, await res.json()) };