feat(finance): BTC basket flows, client scoping, and jewelry-box store
- Finance API: baskets, holdings, rebalances, deposits, bridge withdrawals, vault checks. - Schemas: btc-basket; api-client finance types; workspace lockfile update. - Vitest config for finance service; expanded tests. Made-with: Cursor
This commit is contained in:
136
packages/schemas/src/btc-basket.ts
Normal file
136
packages/schemas/src/btc-basket.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const EthAddressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/);
|
||||
|
||||
export const BasketAllocationSchema = z.object({
|
||||
symbol: z.string().min(2).max(16),
|
||||
targetWeightBps: z.number().int().positive().max(10_000),
|
||||
routeHint: z.string().optional(),
|
||||
});
|
||||
|
||||
export type BasketAllocation = z.infer<typeof BasketAllocationSchema>;
|
||||
|
||||
export const BasketStatusSchema = z.enum(['draft', 'active', 'rebalancing', 'closed']);
|
||||
|
||||
export const BasketMandateSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
clientId: z.string().min(1),
|
||||
mandateName: z.string().min(1),
|
||||
chain138VaultAddress: EthAddressSchema,
|
||||
baseAssetSymbol: z.string().min(2).max(16).default('cBTC'),
|
||||
status: BasketStatusSchema,
|
||||
allocations: z.array(BasketAllocationSchema).min(1),
|
||||
createdAt: z.date().or(z.string().datetime()),
|
||||
updatedAt: z.date().or(z.string().datetime()),
|
||||
});
|
||||
|
||||
export type BasketMandate = z.infer<typeof BasketMandateSchema>;
|
||||
|
||||
export const CreateBasketMandateSchema = BasketMandateSchema.omit({
|
||||
id: true,
|
||||
status: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
}).extend({
|
||||
baseAssetSymbol: z.string().min(2).max(16).default('cBTC'),
|
||||
});
|
||||
|
||||
export type CreateBasketMandate = z.infer<typeof CreateBasketMandateSchema>;
|
||||
|
||||
export const BtcDepositStatusSchema = z.enum([
|
||||
'instruction_created',
|
||||
'pending_confirmations',
|
||||
'confirmed',
|
||||
'minted',
|
||||
'frozen',
|
||||
]);
|
||||
|
||||
export const BtcDepositSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
clientId: z.string().min(1),
|
||||
basketId: z.string().uuid(),
|
||||
chain138VaultAddress: EthAddressSchema,
|
||||
depositAddress: z.string().min(16),
|
||||
expectedAmountSats: z.number().int().positive().optional(),
|
||||
confirmationsRequired: z.number().int().positive(),
|
||||
currentConfirmations: z.number().int().nonnegative(),
|
||||
status: BtcDepositStatusSchema,
|
||||
observedTxId: z.string().optional(),
|
||||
freezeReason: z.string().optional(),
|
||||
createdAt: z.date().or(z.string().datetime()),
|
||||
updatedAt: z.date().or(z.string().datetime()),
|
||||
});
|
||||
|
||||
export type BtcDeposit = z.infer<typeof BtcDepositSchema>;
|
||||
|
||||
export const CreateBtcDepositSchema = z.object({
|
||||
clientId: z.string().min(1),
|
||||
basketId: z.string().uuid().optional(),
|
||||
mandateName: z.string().min(1).optional(),
|
||||
chain138VaultAddress: EthAddressSchema,
|
||||
allocations: z.array(BasketAllocationSchema).min(1).optional(),
|
||||
expectedAmountSats: z.number().int().positive().optional(),
|
||||
clientReference: z.string().min(1).optional(),
|
||||
});
|
||||
|
||||
export type CreateBtcDeposit = z.infer<typeof CreateBtcDepositSchema>;
|
||||
|
||||
export const HoldingStatusSchema = z.enum(['pending_funding', 'funded']);
|
||||
|
||||
export const HoldingSchema = z.object({
|
||||
clientId: z.string().min(1),
|
||||
basketId: z.string().uuid(),
|
||||
symbol: z.string().min(2).max(16),
|
||||
allocationWeightBps: z.number().int().min(0).max(10_000),
|
||||
bookValueSats: z.number().int().nonnegative(),
|
||||
status: HoldingStatusSchema,
|
||||
updatedAt: z.date().or(z.string().datetime()),
|
||||
});
|
||||
|
||||
export type Holding = z.infer<typeof HoldingSchema>;
|
||||
|
||||
export const RebalanceStatusSchema = z.enum(['planned', 'queued', 'executed', 'failed']);
|
||||
|
||||
export const RebalanceSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
clientId: z.string().min(1),
|
||||
basketId: z.string().uuid(),
|
||||
sourceSymbol: z.string().min(2).max(16),
|
||||
targetSymbols: z.array(z.string().min(2).max(16)).min(1),
|
||||
reason: z.string().min(1),
|
||||
status: RebalanceStatusSchema,
|
||||
createdAt: z.date().or(z.string().datetime()),
|
||||
updatedAt: z.date().or(z.string().datetime()),
|
||||
});
|
||||
|
||||
export type Rebalance = z.infer<typeof RebalanceSchema>;
|
||||
|
||||
export const BridgeWithdrawalStatusSchema = z.enum(['pending', 'approved', 'submitted', 'failed']);
|
||||
|
||||
export const BridgeWithdrawalSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
clientId: z.string().min(1),
|
||||
basketId: z.string().uuid(),
|
||||
sourceSymbol: z.string().min(2).max(16),
|
||||
destinationSymbol: z.string().min(2).max(16),
|
||||
destinationChainId: z.number().int().positive(),
|
||||
destinationAddress: EthAddressSchema,
|
||||
amount: z.string().min(1),
|
||||
status: BridgeWithdrawalStatusSchema,
|
||||
createdAt: z.date().or(z.string().datetime()),
|
||||
updatedAt: z.date().or(z.string().datetime()),
|
||||
});
|
||||
|
||||
export type BridgeWithdrawal = z.infer<typeof BridgeWithdrawalSchema>;
|
||||
|
||||
export const CreateBridgeWithdrawalSchema = BridgeWithdrawalSchema.omit({
|
||||
id: true,
|
||||
status: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
}).extend({
|
||||
sourceSymbol: z.string().min(2).max(16).optional(),
|
||||
destinationSymbol: z.string().min(2).max(16).optional(),
|
||||
});
|
||||
|
||||
export type CreateBridgeWithdrawal = z.infer<typeof CreateBridgeWithdrawalSchema>;
|
||||
@@ -9,4 +9,4 @@ export * from './vc';
|
||||
export * from './payment';
|
||||
export * from './ledger';
|
||||
export * from './eresidency';
|
||||
|
||||
export * from './btc-basket';
|
||||
|
||||
Reference in New Issue
Block a user