- Integrated Zod validation schemas across various API routes to ensure input integrity and improve error handling. - Updated `mapping-service`, `orchestrator`, `packet-service`, and `webhook-service` to utilize validation middleware for request parameters and bodies. - Improved error handling in webhook management, packet generation, and compliance routes to provide clearer feedback on request failures. - Added new validation schemas for various endpoints, enhancing overall API robustness and maintainability. - Updated dependencies in `package.json` to include the new validation library.
276 lines
8.7 KiB
TypeScript
276 lines
8.7 KiB
TypeScript
/**
|
|
* 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());
|
|
|