/** * Request validation schemas using Zod * Provides type-safe validation for all API endpoints */ import { z } from 'zod'; // Common patterns const ethereumAddress = z.string().regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid Ethereum address'); const accountRefId = z.string().regex(/^0x[a-fA-F0-9]{64}$/, 'Invalid account reference ID'); const walletRefId = z.string().regex(/^0x[a-fA-F0-9]{64}$/, 'Invalid wallet reference ID'); const tokenCode = z.string().regex(/^[A-Z0-9]{1,10}$/, 'Invalid token code'); const lienId = z.string().regex(/^[0-9]+$/, 'Invalid lien ID'); const triggerId = z.string().regex(/^[a-fA-F0-9]{64}$/, 'Invalid trigger ID'); const packetId = z.string().regex(/^[a-fA-F0-9]{64}$/, 'Invalid packet ID'); const lockId = z.string().regex(/^[a-fA-F0-9]{64}$/, 'Invalid lock ID'); const txHash = z.string().regex(/^0x[a-fA-F0-9]{64}$/, 'Invalid transaction hash'); const jurisdictionHash = z.string().regex(/^0x[a-fA-F0-9]{64}$/, 'Invalid jurisdiction hash'); const amount = z.string().min(1, 'Amount is required'); const isoMsgType = z.string().regex(/^[a-z]+\.[0-9]{3}$/, 'Invalid ISO-20022 message type'); // Enums const railEnum = z.enum(['FEDWIRE', 'SWIFT', 'SEPA', 'RTGS']); const lienModeEnum = z.enum(['OFF', 'HARD_FREEZE', 'ENCUMBERED']); const triggerStateEnum = z.enum(['CREATED', 'VALIDATED', 'SUBMITTED_TO_RAIL', 'PENDING', 'SETTLED', 'REJECTED', 'CANCELLED', 'RECALLED']); const packetChannelEnum = z.enum(['PDF', 'AS4', 'EMAIL', 'PORTAL']); const packetStatusEnum = z.enum(['GENERATED', 'DISPATCHED', 'DELIVERED', 'ACKNOWLEDGED', 'FAILED']); const ackStatusEnum = z.enum(['RECEIVED', 'ACCEPTED', 'REJECTED']); const txStatusEnum = z.enum(['PENDING', 'SUCCESS', 'FAILED']); // Token schemas export const deployTokenSchema = z.object({ name: z.string().min(1, 'Name is required'), symbol: tokenCode, decimals: z.number().int().min(0).max(255), issuer: ethereumAddress, defaultLienMode: lienModeEnum.optional().default('ENCUMBERED'), bridgeOnly: z.boolean().optional().default(false), bridge: ethereumAddress.optional(), }); export const updatePolicySchema = z.object({ paused: z.boolean().optional(), bridgeOnly: z.boolean().optional(), bridge: ethereumAddress.optional(), lienMode: lienModeEnum.optional(), forceTransferMode: z.boolean().optional(), routes: z.array(railEnum).optional(), }); export const mintRequestSchema = z.object({ to: ethereumAddress, amount: amount, reasonCode: z.string().optional(), }); export const burnRequestSchema = z.object({ from: ethereumAddress, amount: amount, reasonCode: z.string().optional(), }); export const clawbackRequestSchema = z.object({ from: ethereumAddress, to: ethereumAddress, amount: amount, reasonCode: z.string().optional(), }); export const forceTransferRequestSchema = z.object({ from: ethereumAddress, to: ethereumAddress, amount: amount, reasonCode: z.string().optional(), }); // Lien schemas export const placeLienSchema = z.object({ debtor: z.string().min(1, 'Debtor is required'), amount: amount, expiry: z.number().int().min(0).optional(), priority: z.number().int().min(0).max(255).optional(), reasonCode: z.string().optional(), }); export const reduceLienSchema = z.object({ reduceBy: amount, }); // Compliance schemas export const setComplianceSchema = z.object({ allowed: z.boolean(), riskTier: z.number().int().min(0).max(255).optional(), jurisdictionHash: jurisdictionHash.optional(), }); export const setFrozenSchema = z.object({ frozen: z.boolean(), }); export const setTierSchema = z.object({ tier: z.number().int().min(0).max(255), }); export const setJurisdictionHashSchema = z.object({ jurisdictionHash: jurisdictionHash, }); // Mapping schemas export const linkAccountWalletSchema = z.object({ accountRefId: accountRefId, walletRefId: walletRefId, provider: z.string().optional(), metadata: z.record(z.any()).optional(), }); export const unlinkAccountWalletSchema = z.object({ accountRefId: accountRefId, walletRefId: walletRefId, }); // ISO schemas export const submitInboundMessageSchema = z.object({ msgType: isoMsgType, instructionId: z.string().min(1, 'Instruction ID is required'), endToEndId: z.string().optional(), payloadHash: z.string().min(1, 'Payload hash is required'), payload: z.string().min(1, 'Payload is required'), rail: railEnum.optional(), }); export const submitOutboundMessageSchema = z.object({ msgType: isoMsgType, instructionId: z.string().min(1, 'Instruction ID is required'), endToEndId: z.string().optional(), payloadHash: z.string().min(1, 'Payload hash is required'), payload: z.string().min(1, 'Payload is required'), rail: railEnum.optional(), token: ethereumAddress, amount: amount, accountRefId: z.string().min(1, 'Account reference ID is required'), counterpartyRefId: z.string().min(1, 'Counterparty reference ID is required'), }); // Packet schemas export const generatePacketSchema = z.object({ triggerId: z.string().min(1, 'Trigger ID is required'), channel: packetChannelEnum, options: z.record(z.any()).optional(), }); export const dispatchPacketSchema = z.object({ channel: z.enum(['EMAIL', 'AS4', 'PORTAL']), recipient: z.string().min(1, 'Recipient is required'), }); export const acknowledgePacketSchema = z.object({ status: ackStatusEnum, ackId: z.string().optional(), }); // Bridge schemas export const bridgeLockSchema = z.object({ token: ethereumAddress, amount: amount, targetChain: z.string().min(1, 'Target chain is required'), targetRecipient: ethereumAddress, }); export const bridgeUnlockSchema = z.object({ lockId: z.string().min(1, 'Lock ID is required'), token: ethereumAddress, to: ethereumAddress, amount: amount, sourceChain: z.string().min(1, 'Source chain is required'), sourceTx: z.string().min(1, 'Source transaction is required'), proof: z.string().min(1, 'Proof is required'), }); // Trigger schemas export const markSubmittedSchema = z.object({ railTxRef: z.string().min(1, 'Rail transaction reference is required'), }); export const confirmRejectedSchema = z.object({ reason: z.string().optional(), }); // Query parameter schemas export const listTokensQuerySchema = z.object({ code: tokenCode.optional(), issuer: ethereumAddress.optional(), limit: z.coerce.number().int().min(1).max(100).default(20), offset: z.coerce.number().int().min(0).default(0), }); export const listLiensQuerySchema = z.object({ debtor: z.string().optional(), active: z.coerce.boolean().optional(), limit: z.coerce.number().int().min(1).max(100).default(20), offset: z.coerce.number().int().min(0).default(0), }); export const listPacketsQuerySchema = z.object({ triggerId: z.string().optional(), instructionId: z.string().optional(), status: packetStatusEnum.optional(), limit: z.coerce.number().int().min(1).max(100).default(20), offset: z.coerce.number().int().min(0).default(0), }); export const listTriggersQuerySchema = z.object({ state: triggerStateEnum.optional(), rail: railEnum.optional(), msgType: isoMsgType.optional(), instructionId: z.string().optional(), accountRef: z.string().optional(), walletRef: z.string().optional(), limit: z.coerce.number().int().min(1).max(100).default(20), offset: z.coerce.number().int().min(0).default(0), }); export const getEncumbranceQuerySchema = z.object({ token: ethereumAddress.optional(), }); // Web3-IBAN schemas (for mapping-service) export const addressToIBANSchema = z.object({ address: z.string().min(1, 'Address is required'), }); export const ibanToAddressSchema = z.object({ iban: z.string().min(1, 'IBAN is required'), }); export const validateIBANSchema = z.object({ iban: z.string().min(1, 'IBAN is required'), }); export const validateAddressSchema = z.object({ address: z.string().min(1, 'Address is required'), }); // Webhook schemas export const createWebhookSchema = z.object({ url: z.string().url('Invalid URL'), events: z.array(z.string()).min(1, 'At least one event is required'), secret: z.string().optional(), active: z.boolean().optional().default(true), }); export const updateWebhookSchema = z.object({ url: z.string().url('Invalid URL').optional(), events: z.array(z.string()).optional(), secret: z.string().optional(), active: z.boolean().optional(), }); export const replayWebhooksQuerySchema = z.object({ since: z.string().optional(), }); export const listWebhooksQuerySchema = z.object({ limit: z.coerce.number().int().min(1).max(100).default(20), offset: z.coerce.number().int().min(0).default(0), }); export const getDLQEntriesQuerySchema = z.object({ limit: z.coerce.number().int().min(1).max(100).default(20), offset: z.coerce.number().int().min(0).default(0), }); // Provider connection schema export const connectProviderSchema = z.record(z.any());