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:
@@ -21,6 +21,81 @@ export interface LedgerEntry {
|
|||||||
reference?: string;
|
reference?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BasketAllocation {
|
||||||
|
symbol: string;
|
||||||
|
targetWeightBps: number;
|
||||||
|
routeHint?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BasketMandate {
|
||||||
|
id: string;
|
||||||
|
clientId: string;
|
||||||
|
mandateName: string;
|
||||||
|
chain138VaultAddress: string;
|
||||||
|
baseAssetSymbol: string;
|
||||||
|
status: 'draft' | 'active' | 'rebalancing' | 'closed';
|
||||||
|
allocations: BasketAllocation[];
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BtcDeposit {
|
||||||
|
id: string;
|
||||||
|
clientId: string;
|
||||||
|
basketId: string;
|
||||||
|
chain138VaultAddress: string;
|
||||||
|
depositAddress: string;
|
||||||
|
expectedAmountSats?: number;
|
||||||
|
confirmationsRequired: number;
|
||||||
|
currentConfirmations: number;
|
||||||
|
status:
|
||||||
|
| 'instruction_created'
|
||||||
|
| 'pending_confirmations'
|
||||||
|
| 'confirmed'
|
||||||
|
| 'minted'
|
||||||
|
| 'frozen';
|
||||||
|
observedTxId?: string;
|
||||||
|
freezeReason?: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Holding {
|
||||||
|
clientId: string;
|
||||||
|
basketId: string;
|
||||||
|
symbol: string;
|
||||||
|
allocationWeightBps: number;
|
||||||
|
bookValueSats: number;
|
||||||
|
status: 'pending_funding' | 'funded';
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Rebalance {
|
||||||
|
id: string;
|
||||||
|
clientId: string;
|
||||||
|
basketId: string;
|
||||||
|
sourceSymbol: string;
|
||||||
|
targetSymbols: string[];
|
||||||
|
reason: string;
|
||||||
|
status: 'planned' | 'queued' | 'executed' | 'failed';
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BridgeWithdrawal {
|
||||||
|
id: string;
|
||||||
|
clientId: string;
|
||||||
|
basketId: string;
|
||||||
|
sourceSymbol: string;
|
||||||
|
destinationSymbol: string;
|
||||||
|
destinationChainId: number;
|
||||||
|
destinationAddress: string;
|
||||||
|
amount: string;
|
||||||
|
status: 'pending' | 'approved' | 'submitted' | 'failed';
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class FinanceClient {
|
export class FinanceClient {
|
||||||
protected client: AxiosInstance;
|
protected client: AxiosInstance;
|
||||||
|
|
||||||
@@ -28,8 +103,8 @@ export class FinanceClient {
|
|||||||
const apiBaseURL =
|
const apiBaseURL =
|
||||||
baseURL ||
|
baseURL ||
|
||||||
(typeof window !== 'undefined'
|
(typeof window !== 'undefined'
|
||||||
? process.env.NEXT_PUBLIC_FINANCE_SERVICE_URL || 'http://localhost:4005'
|
? process.env.NEXT_PUBLIC_FINANCE_SERVICE_URL || 'http://localhost:4003'
|
||||||
: 'http://localhost:4005');
|
: 'http://localhost:4003');
|
||||||
|
|
||||||
this.client = axios.create({
|
this.client = axios.create({
|
||||||
baseURL: apiBaseURL,
|
baseURL: apiBaseURL,
|
||||||
@@ -38,7 +113,6 @@ export class FinanceClient {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set up request interceptor for authentication
|
|
||||||
this.client.interceptors.request.use(
|
this.client.interceptors.request.use(
|
||||||
(config) => {
|
(config) => {
|
||||||
const token = this.getAuthToken();
|
const token = this.getAuthToken();
|
||||||
@@ -47,7 +121,7 @@ export class FinanceClient {
|
|||||||
}
|
}
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
(error) => Promise.reject(error)
|
(error) => Promise.reject(error),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,8 +148,8 @@ export class FinanceClient {
|
|||||||
paymentMethod: string;
|
paymentMethod: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
}): Promise<Payment> {
|
}): Promise<Payment> {
|
||||||
const response = await this.client.post<Payment>('/api/v1/payments', data);
|
const response = await this.client.post<{ payment: Payment }>('/api/v1/payments', data);
|
||||||
return response.data;
|
return response.data.payment;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPayment(paymentId: string): Promise<Payment> {
|
async getPayment(paymentId: string): Promise<Payment> {
|
||||||
@@ -104,8 +178,71 @@ export class FinanceClient {
|
|||||||
}): Promise<{ entries: LedgerEntry[]; total: number }> {
|
}): Promise<{ entries: LedgerEntry[]; total: number }> {
|
||||||
const response = await this.client.get<{ entries: LedgerEntry[]; total: number }>(
|
const response = await this.client.get<{ entries: LedgerEntry[]; total: number }>(
|
||||||
'/api/v1/ledger',
|
'/api/v1/ledger',
|
||||||
{ params: filters }
|
{ params: filters },
|
||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createBasket(data: {
|
||||||
|
clientId: string;
|
||||||
|
mandateName: string;
|
||||||
|
chain138VaultAddress: string;
|
||||||
|
allocations: BasketAllocation[];
|
||||||
|
baseAssetSymbol?: string;
|
||||||
|
}): Promise<BasketMandate> {
|
||||||
|
const response = await this.client.post<{ basket: BasketMandate }>('/api/v1/baskets', data);
|
||||||
|
return response.data.basket;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createBtcDeposit(data: {
|
||||||
|
clientId: string;
|
||||||
|
basketId?: string;
|
||||||
|
mandateName?: string;
|
||||||
|
chain138VaultAddress: string;
|
||||||
|
allocations?: BasketAllocation[];
|
||||||
|
expectedAmountSats?: number;
|
||||||
|
clientReference?: string;
|
||||||
|
}): Promise<{ deposit: BtcDeposit; basket: BasketMandate; createdBasket: boolean }> {
|
||||||
|
const response = await this.client.post<{
|
||||||
|
deposit: BtcDeposit;
|
||||||
|
basket: BasketMandate;
|
||||||
|
createdBasket: boolean;
|
||||||
|
}>('/api/v1/btc-deposits', data);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBtcDeposit(id: string): Promise<BtcDeposit> {
|
||||||
|
const response = await this.client.get<{ deposit: BtcDeposit }>(`/api/v1/btc-deposits/${id}`);
|
||||||
|
return response.data.deposit;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getHoldings(filters?: { clientId?: string; basketId?: string }): Promise<Holding[]> {
|
||||||
|
const response = await this.client.get<{ holdings: Holding[] }>('/api/v1/holdings', {
|
||||||
|
params: filters,
|
||||||
|
});
|
||||||
|
return response.data.holdings;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRebalances(filters?: { clientId?: string; basketId?: string }): Promise<Rebalance[]> {
|
||||||
|
const response = await this.client.get<{ rebalances: Rebalance[] }>('/api/v1/rebalances', {
|
||||||
|
params: filters,
|
||||||
|
});
|
||||||
|
return response.data.rebalances;
|
||||||
|
}
|
||||||
|
|
||||||
|
async requestBridgeWithdrawal(data: {
|
||||||
|
clientId: string;
|
||||||
|
basketId: string;
|
||||||
|
sourceSymbol?: string;
|
||||||
|
destinationSymbol?: string;
|
||||||
|
destinationChainId: number;
|
||||||
|
destinationAddress: string;
|
||||||
|
amount: string;
|
||||||
|
}): Promise<BridgeWithdrawal> {
|
||||||
|
const response = await this.client.post<{ withdrawal: BridgeWithdrawal }>(
|
||||||
|
'/api/v1/withdrawals/bridge',
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
return response.data.withdrawal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,5 +14,14 @@ export type {
|
|||||||
} from './identity';
|
} from './identity';
|
||||||
export type { SubmitApplicationRequest, AdjudicateRequest } from './eresidency';
|
export type { SubmitApplicationRequest, AdjudicateRequest } from './eresidency';
|
||||||
export type { DocumentUpload, DocumentMetadata } from './intake';
|
export type { DocumentUpload, DocumentMetadata } from './intake';
|
||||||
export type { Payment, LedgerEntry } from './finance';
|
export type {
|
||||||
|
Payment,
|
||||||
|
LedgerEntry,
|
||||||
|
BasketAllocation,
|
||||||
|
BasketMandate,
|
||||||
|
BtcDeposit,
|
||||||
|
Holding,
|
||||||
|
Rebalance,
|
||||||
|
BridgeWithdrawal,
|
||||||
|
} from './finance';
|
||||||
export type { DealRoom, Document } from './dataroom';
|
export type { DealRoom, Document } from './dataroom';
|
||||||
|
|||||||
@@ -6,11 +6,13 @@
|
|||||||
import fetch from 'node-fetch';
|
import fetch from 'node-fetch';
|
||||||
import { createVerify, createPublicKey } from 'crypto';
|
import { createVerify, createPublicKey } from 'crypto';
|
||||||
import { decode as multibaseDecode } from 'multibase';
|
import { decode as multibaseDecode } from 'multibase';
|
||||||
import base58 from 'base58-universal';
|
|
||||||
import { importJWK } from 'jose';
|
import { importJWK } from 'jose';
|
||||||
import forge from 'node-forge';
|
import forge from 'node-forge';
|
||||||
import { verify as ed25519Verify } from '@noble/ed25519';
|
import { verify as ed25519Verify } from '@noble/ed25519';
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||||
|
const { decode: base58Decode }: { decode: (value: string) => Uint8Array } = require('base58-universal');
|
||||||
|
|
||||||
export interface DIDDocument {
|
export interface DIDDocument {
|
||||||
id: string;
|
id: string;
|
||||||
'@context': string[];
|
'@context': string[];
|
||||||
@@ -89,7 +91,7 @@ export class DIDResolver {
|
|||||||
if (multibaseKey.startsWith('z')) {
|
if (multibaseKey.startsWith('z')) {
|
||||||
try {
|
try {
|
||||||
const base58Encoded = multibaseKey.slice(1);
|
const base58Encoded = multibaseKey.slice(1);
|
||||||
const decoded = base58.decode(base58Encoded);
|
const decoded = base58Decode(base58Encoded);
|
||||||
return Buffer.from(decoded);
|
return Buffer.from(decoded);
|
||||||
} catch {
|
} catch {
|
||||||
throw new Error('Failed to decode multibase key');
|
throw new Error('Failed to decode multibase key');
|
||||||
@@ -296,4 +298,3 @@ export class DIDResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,6 +122,9 @@ export async function prepareCredentialImage(
|
|||||||
if (imageData.startsWith('data:')) {
|
if (imageData.startsWith('data:')) {
|
||||||
// Extract base64 data
|
// Extract base64 data
|
||||||
const base64Data = imageData.split(',')[1];
|
const base64Data = imageData.split(',')[1];
|
||||||
|
if (!base64Data) {
|
||||||
|
throw new Error('Invalid image data URL: missing base64 payload');
|
||||||
|
}
|
||||||
imageBuffer = Buffer.from(base64Data, 'base64');
|
imageBuffer = Buffer.from(base64Data, 'base64');
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = Buffer.from(imageData);
|
imageBuffer = Buffer.from(imageData);
|
||||||
@@ -180,4 +183,3 @@ export function getRecommendedImageSpecs(): {
|
|||||||
maxSizeKB: 100, // Max 100KB recommended
|
maxSizeKB: 100, // Max 100KB recommended
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { query } from './client';
|
import { query } from './client';
|
||||||
import { listDocuments, getDocumentById } from './schema';
|
|
||||||
|
|
||||||
export interface DocumentSearchResult {
|
export interface DocumentSearchResult {
|
||||||
documents: Array<{
|
documents: Array<{
|
||||||
@@ -102,8 +101,8 @@ export async function searchDocuments(
|
|||||||
/**
|
/**
|
||||||
* Get search suggestions
|
* Get search suggestions
|
||||||
*/
|
*/
|
||||||
export async function getSearchSuggestions(query: string, limit = 10): Promise<string[]> {
|
export async function getSearchSuggestions(searchTerm: string, limit = 10): Promise<string[]> {
|
||||||
if (!query || query.length < 2) {
|
if (!searchTerm || searchTerm.length < 2) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,9 +112,8 @@ export async function getSearchSuggestions(query: string, limit = 10): Promise<s
|
|||||||
WHERE title ILIKE $1
|
WHERE title ILIKE $1
|
||||||
ORDER BY title
|
ORDER BY title
|
||||||
LIMIT $2`,
|
LIMIT $2`,
|
||||||
[`%${query}%`, limit]
|
[`%${searchTerm}%`, limit]
|
||||||
);
|
);
|
||||||
|
|
||||||
return result.rows.map((row) => row.title);
|
return result.rows.map((row) => row.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -320,9 +320,11 @@ export function extractTemplateVariables(template_content: string): string[] {
|
|||||||
const matches = template_content.matchAll(/\{\{(\w+(?:\.\w+)*)\}\}/g);
|
const matches = template_content.matchAll(/\{\{(\w+(?:\.\w+)*)\}\}/g);
|
||||||
|
|
||||||
for (const match of matches) {
|
for (const match of matches) {
|
||||||
variables.add(match[1]);
|
const variableName = match[1];
|
||||||
|
if (variableName) {
|
||||||
|
variables.add(variableName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array.from(variables).sort();
|
return Array.from(variables).sort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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 './payment';
|
||||||
export * from './ledger';
|
export * from './ledger';
|
||||||
export * from './eresidency';
|
export * from './eresidency';
|
||||||
|
export * from './btc-basket';
|
||||||
|
|||||||
@@ -77,9 +77,9 @@ export function createSecurityTestData() {
|
|||||||
export async function testAuthenticationBypass(
|
export async function testAuthenticationBypass(
|
||||||
makeRequest: (headers?: Record<string, string>) => Promise<{ status: number }>
|
makeRequest: (headers?: Record<string, string>) => Promise<{ status: number }>
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const testCases = [
|
const testCases: Array<Record<string, string> | undefined> = [
|
||||||
// Missing token
|
// Missing token
|
||||||
{},
|
undefined,
|
||||||
// Invalid token
|
// Invalid token
|
||||||
{ Authorization: 'Bearer invalid-token' },
|
{ Authorization: 'Bearer invalid-token' },
|
||||||
// Expired token
|
// Expired token
|
||||||
@@ -226,4 +226,3 @@ export async function testCSRFProtection(
|
|||||||
|
|
||||||
return true; // CSRF protection is working correctly
|
return true; // CSRF protection is working correctly
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
import { FastifyInstance } from 'fastify';
|
import { FastifyInstance } from 'fastify';
|
||||||
import Fastify from 'fastify';
|
import Fastify from 'fastify';
|
||||||
import { getEnv } from '@the-order/shared';
|
|
||||||
|
|
||||||
export interface ServerFactoryOptions {
|
export interface ServerFactoryOptions {
|
||||||
port?: number;
|
port?: number;
|
||||||
@@ -41,4 +40,3 @@ export async function createTestServer(
|
|||||||
export function closeTestServer(server: FastifyInstance): Promise<void> {
|
export function closeTestServer(server: FastifyInstance): Promise<void> {
|
||||||
return server.close();
|
return server.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,10 @@ export async function reviewWorkflow(
|
|||||||
if (process.env.APPROVAL_SERVICE_URL) {
|
if (process.env.APPROVAL_SERVICE_URL) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${process.env.APPROVAL_SERVICE_URL}/status/${input.documentId}/${input.reviewerId}`);
|
const res = await fetch(`${process.env.APPROVAL_SERVICE_URL}/status/${input.documentId}/${input.reviewerId}`);
|
||||||
if (res.ok) approved = (await res.json()).approved ?? false;
|
if (res.ok) {
|
||||||
|
const approvalResponse = (await res.json()) as { approved?: boolean };
|
||||||
|
approved = approvalResponse.approved ?? false;
|
||||||
|
}
|
||||||
} catch { /* fall through */ }
|
} catch { /* fall through */ }
|
||||||
}
|
}
|
||||||
if (!approved && getApprovalStatus) {
|
if (!approved && getApprovalStatus) {
|
||||||
@@ -151,4 +154,3 @@ export async function checkApprovalStatus(
|
|||||||
// Fallback: assume not approved if we can't check
|
// Fallback: assume not approved if we can't check
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
522
pnpm-lock.yaml
generated
522
pnpm-lock.yaml
generated
@@ -527,31 +527,6 @@ importers:
|
|||||||
specifier: ^5.3.3
|
specifier: ^5.3.3
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
|
|
||||||
packages/secrets:
|
|
||||||
dependencies:
|
|
||||||
'@aws-sdk/client-secrets-manager':
|
|
||||||
specifier: ^3.490.0
|
|
||||||
version: 3.927.0
|
|
||||||
'@azure/identity':
|
|
||||||
specifier: ^4.0.1
|
|
||||||
version: 4.13.0
|
|
||||||
'@azure/keyvault-secrets':
|
|
||||||
specifier: ^4.7.0
|
|
||||||
version: 4.10.0
|
|
||||||
zod:
|
|
||||||
specifier: ^3.22.4
|
|
||||||
version: 3.25.76
|
|
||||||
devDependencies:
|
|
||||||
'@types/node':
|
|
||||||
specifier: ^20.10.6
|
|
||||||
version: 20.19.24
|
|
||||||
typescript:
|
|
||||||
specifier: ^5.3.3
|
|
||||||
version: 5.9.3
|
|
||||||
vitest:
|
|
||||||
specifier: ^1.6.1
|
|
||||||
version: 1.6.1(@types/node@20.19.24)(@vitest/ui@1.6.1)
|
|
||||||
|
|
||||||
packages/shared:
|
packages/shared:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@fastify/cors':
|
'@fastify/cors':
|
||||||
@@ -796,6 +771,9 @@ importers:
|
|||||||
'@fastify/swagger-ui':
|
'@fastify/swagger-ui':
|
||||||
specifier: ^2.0.0
|
specifier: ^2.0.0
|
||||||
version: 2.1.0
|
version: 2.1.0
|
||||||
|
'@the-order/database':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/database
|
||||||
'@the-order/payment-gateway':
|
'@the-order/payment-gateway':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../../packages/payment-gateway
|
version: link:../../packages/payment-gateway
|
||||||
@@ -821,6 +799,9 @@ importers:
|
|||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.3.3
|
specifier: ^5.3.3
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
|
vitest:
|
||||||
|
specifier: ^1.6.1
|
||||||
|
version: 1.6.1(@types/node@20.19.24)(@vitest/ui@1.6.1)
|
||||||
|
|
||||||
services/identity:
|
services/identity:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -899,6 +880,55 @@ importers:
|
|||||||
specifier: ^5.3.3
|
specifier: ^5.3.3
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
|
|
||||||
|
services/legal-documents:
|
||||||
|
dependencies:
|
||||||
|
'@fastify/swagger':
|
||||||
|
specifier: ^8.12.0
|
||||||
|
version: 8.15.0
|
||||||
|
'@fastify/swagger-ui':
|
||||||
|
specifier: ^1.9.3
|
||||||
|
version: 1.10.2
|
||||||
|
'@the-order/database':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/database
|
||||||
|
'@the-order/schemas':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/schemas
|
||||||
|
'@the-order/shared':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/shared
|
||||||
|
'@the-order/storage':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/storage
|
||||||
|
docx:
|
||||||
|
specifier: ^8.5.0
|
||||||
|
version: 8.6.0
|
||||||
|
fastify:
|
||||||
|
specifier: ^4.24.3
|
||||||
|
version: 4.29.1
|
||||||
|
pdf-lib:
|
||||||
|
specifier: ^1.17.1
|
||||||
|
version: 1.17.1
|
||||||
|
pdfkit:
|
||||||
|
specifier: ^0.15.0
|
||||||
|
version: 0.15.2
|
||||||
|
zod:
|
||||||
|
specifier: ^3.22.4
|
||||||
|
version: 3.25.76
|
||||||
|
devDependencies:
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^20.10.0
|
||||||
|
version: 20.19.24
|
||||||
|
tsx:
|
||||||
|
specifier: ^4.7.0
|
||||||
|
version: 4.20.6
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.3.3
|
||||||
|
version: 5.9.3
|
||||||
|
vitest:
|
||||||
|
specifier: ^1.1.0
|
||||||
|
version: 1.6.1(@types/node@20.19.24)(@vitest/ui@1.6.1)
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
/@alloc/quick-lru@5.2.0:
|
/@alloc/quick-lru@5.2.0:
|
||||||
@@ -1081,54 +1111,6 @@ packages:
|
|||||||
- aws-crt
|
- aws-crt
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@aws-sdk/client-secrets-manager@3.927.0:
|
|
||||||
resolution: {integrity: sha512-6hdJ4OHyM4NvOS/uzt06Ko1NLhI04qwIq1nwH1kmo2W6VOc9ozLJnzIRlUCVw6s/F8dQtvfsCzFf0rJshGyT6Q==}
|
|
||||||
engines: {node: '>=18.0.0'}
|
|
||||||
dependencies:
|
|
||||||
'@aws-crypto/sha256-browser': 5.2.0
|
|
||||||
'@aws-crypto/sha256-js': 5.2.0
|
|
||||||
'@aws-sdk/core': 3.927.0
|
|
||||||
'@aws-sdk/credential-provider-node': 3.927.0
|
|
||||||
'@aws-sdk/middleware-host-header': 3.922.0
|
|
||||||
'@aws-sdk/middleware-logger': 3.922.0
|
|
||||||
'@aws-sdk/middleware-recursion-detection': 3.922.0
|
|
||||||
'@aws-sdk/middleware-user-agent': 3.927.0
|
|
||||||
'@aws-sdk/region-config-resolver': 3.925.0
|
|
||||||
'@aws-sdk/types': 3.922.0
|
|
||||||
'@aws-sdk/util-endpoints': 3.922.0
|
|
||||||
'@aws-sdk/util-user-agent-browser': 3.922.0
|
|
||||||
'@aws-sdk/util-user-agent-node': 3.927.0
|
|
||||||
'@smithy/config-resolver': 4.4.2
|
|
||||||
'@smithy/core': 3.17.2
|
|
||||||
'@smithy/fetch-http-handler': 5.3.5
|
|
||||||
'@smithy/hash-node': 4.2.4
|
|
||||||
'@smithy/invalid-dependency': 4.2.4
|
|
||||||
'@smithy/middleware-content-length': 4.2.4
|
|
||||||
'@smithy/middleware-endpoint': 4.3.6
|
|
||||||
'@smithy/middleware-retry': 4.4.6
|
|
||||||
'@smithy/middleware-serde': 4.2.4
|
|
||||||
'@smithy/middleware-stack': 4.2.4
|
|
||||||
'@smithy/node-config-provider': 4.3.4
|
|
||||||
'@smithy/node-http-handler': 4.4.4
|
|
||||||
'@smithy/protocol-http': 5.3.4
|
|
||||||
'@smithy/smithy-client': 4.9.2
|
|
||||||
'@smithy/types': 4.8.1
|
|
||||||
'@smithy/url-parser': 4.2.4
|
|
||||||
'@smithy/util-base64': 4.3.0
|
|
||||||
'@smithy/util-body-length-browser': 4.2.0
|
|
||||||
'@smithy/util-body-length-node': 4.2.1
|
|
||||||
'@smithy/util-defaults-mode-browser': 4.3.5
|
|
||||||
'@smithy/util-defaults-mode-node': 4.2.8
|
|
||||||
'@smithy/util-endpoints': 3.2.4
|
|
||||||
'@smithy/util-middleware': 4.2.4
|
|
||||||
'@smithy/util-retry': 4.2.4
|
|
||||||
'@smithy/util-utf8': 4.2.0
|
|
||||||
'@smithy/uuid': 1.1.0
|
|
||||||
tslib: 2.8.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- aws-crt
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@aws-sdk/client-sfn@3.928.0:
|
/@aws-sdk/client-sfn@3.928.0:
|
||||||
resolution: {integrity: sha512-GiFBGqoh+69XxN3yvykxNibpnjDOnBxzzyfxWl4k8Q73qdKWnr/x+4SiCRqZXKbbUiszbPGexYJG9L5w9emfAA==}
|
resolution: {integrity: sha512-GiFBGqoh+69XxN3yvykxNibpnjDOnBxzzyfxWl4k8Q73qdKWnr/x+4SiCRqZXKbbUiszbPGexYJG9L5w9emfAA==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
@@ -1921,20 +1903,6 @@ packages:
|
|||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@azure-rest/core-client@2.5.1:
|
|
||||||
resolution: {integrity: sha512-EHaOXW0RYDKS5CFffnixdyRPak5ytiCtU7uXDcP/uiY+A6jFRwNGzzJBiznkCzvi5EYpY+YWinieqHb0oY916A==}
|
|
||||||
engines: {node: '>=20.0.0'}
|
|
||||||
dependencies:
|
|
||||||
'@azure/abort-controller': 2.1.2
|
|
||||||
'@azure/core-auth': 1.10.1
|
|
||||||
'@azure/core-rest-pipeline': 1.22.2
|
|
||||||
'@azure/core-tracing': 1.3.1
|
|
||||||
'@typespec/ts-http-runtime': 0.3.2
|
|
||||||
tslib: 2.8.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@azure/abort-controller@2.1.2:
|
/@azure/abort-controller@2.1.2:
|
||||||
resolution: {integrity: sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==}
|
resolution: {integrity: sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
@@ -1968,36 +1936,6 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@azure/core-http-compat@2.3.1:
|
|
||||||
resolution: {integrity: sha512-az9BkXND3/d5VgdRRQVkiJb2gOmDU8Qcq4GvjtBmDICNiQ9udFmDk4ZpSB5Qq1OmtDJGlQAfBaS4palFsazQ5g==}
|
|
||||||
engines: {node: '>=20.0.0'}
|
|
||||||
dependencies:
|
|
||||||
'@azure/abort-controller': 2.1.2
|
|
||||||
'@azure/core-client': 1.10.1
|
|
||||||
'@azure/core-rest-pipeline': 1.22.2
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@azure/core-lro@2.7.2:
|
|
||||||
resolution: {integrity: sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==}
|
|
||||||
engines: {node: '>=18.0.0'}
|
|
||||||
dependencies:
|
|
||||||
'@azure/abort-controller': 2.1.2
|
|
||||||
'@azure/core-util': 1.13.1
|
|
||||||
'@azure/logger': 1.3.0
|
|
||||||
tslib: 2.8.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@azure/core-paging@1.6.2:
|
|
||||||
resolution: {integrity: sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA==}
|
|
||||||
engines: {node: '>=18.0.0'}
|
|
||||||
dependencies:
|
|
||||||
tslib: 2.8.1
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@azure/core-rest-pipeline@1.22.2:
|
/@azure/core-rest-pipeline@1.22.2:
|
||||||
resolution: {integrity: sha512-MzHym+wOi8CLUlKCQu12de0nwcq9k9Kuv43j4Wa++CsCpJwps2eeBQwD2Bu8snkxTtDKDx4GwjuR9E8yC8LNrg==}
|
resolution: {integrity: sha512-MzHym+wOi8CLUlKCQu12de0nwcq9k9Kuv43j4Wa++CsCpJwps2eeBQwD2Bu8snkxTtDKDx4GwjuR9E8yC8LNrg==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
@@ -2050,42 +1988,6 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@azure/keyvault-common@2.0.0:
|
|
||||||
resolution: {integrity: sha512-wRLVaroQtOqfg60cxkzUkGKrKMsCP6uYXAOomOIysSMyt1/YM0eUn9LqieAWM8DLcU4+07Fio2YGpPeqUbpP9w==}
|
|
||||||
engines: {node: '>=18.0.0'}
|
|
||||||
dependencies:
|
|
||||||
'@azure/abort-controller': 2.1.2
|
|
||||||
'@azure/core-auth': 1.10.1
|
|
||||||
'@azure/core-client': 1.10.1
|
|
||||||
'@azure/core-rest-pipeline': 1.22.2
|
|
||||||
'@azure/core-tracing': 1.3.1
|
|
||||||
'@azure/core-util': 1.13.1
|
|
||||||
'@azure/logger': 1.3.0
|
|
||||||
tslib: 2.8.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@azure/keyvault-secrets@4.10.0:
|
|
||||||
resolution: {integrity: sha512-WvXc3h2hqHL1pMzUU7ANE2RBKoxjK3JQc0YNn6GUFvOWQtf2ZR+sH4/5cZu8zAg62v9qLCduBN7065nHKl+AOA==}
|
|
||||||
engines: {node: '>=18.0.0'}
|
|
||||||
dependencies:
|
|
||||||
'@azure-rest/core-client': 2.5.1
|
|
||||||
'@azure/abort-controller': 2.1.2
|
|
||||||
'@azure/core-auth': 1.10.1
|
|
||||||
'@azure/core-http-compat': 2.3.1
|
|
||||||
'@azure/core-lro': 2.7.2
|
|
||||||
'@azure/core-paging': 1.6.2
|
|
||||||
'@azure/core-rest-pipeline': 1.22.2
|
|
||||||
'@azure/core-tracing': 1.3.1
|
|
||||||
'@azure/core-util': 1.13.1
|
|
||||||
'@azure/keyvault-common': 2.0.0
|
|
||||||
'@azure/logger': 1.3.0
|
|
||||||
tslib: 2.8.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@azure/logger@1.3.0:
|
/@azure/logger@1.3.0:
|
||||||
resolution: {integrity: sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==}
|
resolution: {integrity: sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
@@ -2757,6 +2659,16 @@ packages:
|
|||||||
p-limit: 3.1.0
|
p-limit: 3.1.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@fastify/swagger-ui@1.10.2:
|
||||||
|
resolution: {integrity: sha512-f2mRqtblm6eRAFQ3e8zSngxVNEtiYY7rISKQVjPA++ZsWc5WYlPVTb6Bx0G/zy0BIoucNqDr/Q2Vb/kTYkOq1A==}
|
||||||
|
dependencies:
|
||||||
|
'@fastify/static': 6.12.0
|
||||||
|
fastify-plugin: 4.5.1
|
||||||
|
openapi-types: 12.1.3
|
||||||
|
rfdc: 1.4.1
|
||||||
|
yaml: 2.8.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@fastify/swagger-ui@2.1.0:
|
/@fastify/swagger-ui@2.1.0:
|
||||||
resolution: {integrity: sha512-mu0C28kMEQDa3miE8f3LmI/OQSmqaKS3dYhZVFO5y4JdgBIPbzZj6COCoRU/P/9nu7UogzzcCJtg89wwLwKtWg==}
|
resolution: {integrity: sha512-mu0C28kMEQDa3miE8f3LmI/OQSmqaKS3dYhZVFO5y4JdgBIPbzZj6COCoRU/P/9nu7UogzzcCJtg89wwLwKtWg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4665,6 +4577,18 @@ packages:
|
|||||||
'@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
|
'@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@pdf-lib/standard-fonts@1.0.0:
|
||||||
|
resolution: {integrity: sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==}
|
||||||
|
dependencies:
|
||||||
|
pako: 1.0.11
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@pdf-lib/upng@1.0.1:
|
||||||
|
resolution: {integrity: sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==}
|
||||||
|
dependencies:
|
||||||
|
pako: 1.0.11
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@pinojs/redact@0.4.0:
|
/@pinojs/redact@0.4.0:
|
||||||
resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==}
|
resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==}
|
||||||
dev: false
|
dev: false
|
||||||
@@ -5440,6 +5364,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
|
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@swc/helpers@0.3.17:
|
||||||
|
resolution: {integrity: sha512-tb7Iu+oZ+zWJZ3HJqwx8oNwSDIU440hmVMDPhpACWQWnrZHK99Bxs70gT1L2dnr5Hg50ZRWEFkQCAnOVVV0z1Q==}
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@swc/helpers@0.5.5:
|
/@swc/helpers@0.5.5:
|
||||||
resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==}
|
resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6384,7 +6314,6 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
is-array-buffer: 3.0.5
|
is-array-buffer: 3.0.5
|
||||||
dev: true
|
|
||||||
|
|
||||||
/array-includes@3.1.9:
|
/array-includes@3.1.9:
|
||||||
resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==}
|
resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==}
|
||||||
@@ -6522,7 +6451,6 @@ packages:
|
|||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
dependencies:
|
dependencies:
|
||||||
possible-typed-array-names: 1.1.0
|
possible-typed-array-names: 1.1.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/avvio@8.4.0:
|
/avvio@8.4.0:
|
||||||
resolution: {integrity: sha512-CDSwaxINFy59iNwhYnkvALBwZiTydGkOecZyPkqBpABYR1KqGEsET0VOOYDwtleZSUIdeY36DC2bSZ24CO1igA==}
|
resolution: {integrity: sha512-CDSwaxINFy59iNwhYnkvALBwZiTydGkOecZyPkqBpABYR1KqGEsET0VOOYDwtleZSUIdeY36DC2bSZ24CO1igA==}
|
||||||
@@ -6559,6 +6487,11 @@ packages:
|
|||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/base64-js@0.0.8:
|
||||||
|
resolution: {integrity: sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/base64-js@1.5.1:
|
/base64-js@1.5.1:
|
||||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||||
|
|
||||||
@@ -6619,6 +6552,12 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
fill-range: 7.1.1
|
fill-range: 7.1.1
|
||||||
|
|
||||||
|
/brotli@1.3.3:
|
||||||
|
resolution: {integrity: sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==}
|
||||||
|
dependencies:
|
||||||
|
base64-js: 1.5.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/browserslist@4.28.0:
|
/browserslist@4.28.0:
|
||||||
resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==}
|
resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==}
|
||||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||||
@@ -6701,7 +6640,6 @@ packages:
|
|||||||
es-define-property: 1.0.1
|
es-define-property: 1.0.1
|
||||||
get-intrinsic: 1.3.0
|
get-intrinsic: 1.3.0
|
||||||
set-function-length: 1.2.2
|
set-function-length: 1.2.2
|
||||||
dev: true
|
|
||||||
|
|
||||||
/call-bound@1.0.4:
|
/call-bound@1.0.4:
|
||||||
resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
|
resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
|
||||||
@@ -6891,6 +6829,11 @@ packages:
|
|||||||
engines: {node: '>=0.8'}
|
engines: {node: '>=0.8'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/clone@2.1.2:
|
||||||
|
resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==}
|
||||||
|
engines: {node: '>=0.8'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/clsx@2.1.1:
|
/clsx@2.1.1:
|
||||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -6980,6 +6923,10 @@ packages:
|
|||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/core-util-is@1.0.3:
|
||||||
|
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/create-require@1.1.1:
|
/create-require@1.1.1:
|
||||||
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
|
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -6999,6 +6946,10 @@ packages:
|
|||||||
shebang-command: 2.0.0
|
shebang-command: 2.0.0
|
||||||
which: 2.0.2
|
which: 2.0.2
|
||||||
|
|
||||||
|
/crypto-js@4.2.0:
|
||||||
|
resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/cssesc@3.0.0:
|
/cssesc@3.0.0:
|
||||||
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
|
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -7160,6 +7111,30 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
type-detect: 4.1.0
|
type-detect: 4.1.0
|
||||||
|
|
||||||
|
/deep-equal@2.2.3:
|
||||||
|
resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
dependencies:
|
||||||
|
array-buffer-byte-length: 1.0.2
|
||||||
|
call-bind: 1.0.8
|
||||||
|
es-get-iterator: 1.1.3
|
||||||
|
get-intrinsic: 1.3.0
|
||||||
|
is-arguments: 1.2.0
|
||||||
|
is-array-buffer: 3.0.5
|
||||||
|
is-date-object: 1.1.0
|
||||||
|
is-regex: 1.2.1
|
||||||
|
is-shared-array-buffer: 1.0.4
|
||||||
|
isarray: 2.0.5
|
||||||
|
object-is: 1.1.6
|
||||||
|
object-keys: 1.1.1
|
||||||
|
object.assign: 4.1.7
|
||||||
|
regexp.prototype.flags: 1.5.4
|
||||||
|
side-channel: 1.1.0
|
||||||
|
which-boxed-primitive: 1.1.1
|
||||||
|
which-collection: 1.0.2
|
||||||
|
which-typed-array: 1.1.19
|
||||||
|
dev: false
|
||||||
|
|
||||||
/deep-extend@0.6.0:
|
/deep-extend@0.6.0:
|
||||||
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
|
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
|
||||||
engines: {node: '>=4.0.0'}
|
engines: {node: '>=4.0.0'}
|
||||||
@@ -7195,7 +7170,6 @@ packages:
|
|||||||
es-define-property: 1.0.1
|
es-define-property: 1.0.1
|
||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
gopd: 1.2.0
|
gopd: 1.2.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/define-lazy-prop@3.0.0:
|
/define-lazy-prop@3.0.0:
|
||||||
resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
|
resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
|
||||||
@@ -7209,7 +7183,6 @@ packages:
|
|||||||
define-data-property: 1.1.4
|
define-data-property: 1.1.4
|
||||||
has-property-descriptors: 1.0.2
|
has-property-descriptors: 1.0.2
|
||||||
object-keys: 1.1.1
|
object-keys: 1.1.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/degenerator@5.0.1:
|
/degenerator@5.0.1:
|
||||||
resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==}
|
resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==}
|
||||||
@@ -7254,6 +7227,10 @@ packages:
|
|||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/dfa@1.2.0:
|
||||||
|
resolution: {integrity: sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/didyoumean@1.2.2:
|
/didyoumean@1.2.2:
|
||||||
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -7292,6 +7269,18 @@ packages:
|
|||||||
esutils: 2.0.3
|
esutils: 2.0.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/docx@8.6.0:
|
||||||
|
resolution: {integrity: sha512-JEzPozEsuGIyUkEqdGlCv/b1avYeXjR4PjwiLdPwRKdsI9spBCq4WP0QcGYfIANpgYdJSphw4IT8M/a9dpnpvQ==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
deprecated: A API breaking change was introduced here, which was incompatible with semantic versioning
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 20.19.24
|
||||||
|
jszip: 3.10.1
|
||||||
|
nanoid: 5.1.7
|
||||||
|
xml: 1.0.1
|
||||||
|
xml-js: 1.6.11
|
||||||
|
dev: false
|
||||||
|
|
||||||
/dom-helpers@5.2.1:
|
/dom-helpers@5.2.1:
|
||||||
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
|
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -7417,6 +7406,20 @@ packages:
|
|||||||
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
/es-get-iterator@1.1.3:
|
||||||
|
resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==}
|
||||||
|
dependencies:
|
||||||
|
call-bind: 1.0.8
|
||||||
|
get-intrinsic: 1.3.0
|
||||||
|
has-symbols: 1.1.0
|
||||||
|
is-arguments: 1.2.0
|
||||||
|
is-map: 2.0.3
|
||||||
|
is-set: 2.0.3
|
||||||
|
is-string: 1.1.1
|
||||||
|
isarray: 2.0.5
|
||||||
|
stop-iteration-iterator: 1.1.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/es-iterator-helpers@1.2.1:
|
/es-iterator-helpers@1.2.1:
|
||||||
resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==}
|
resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -8212,12 +8215,25 @@ packages:
|
|||||||
optional: true
|
optional: true
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/fontkit@1.9.0:
|
||||||
|
resolution: {integrity: sha512-HkW/8Lrk8jl18kzQHvAw9aTHe1cqsyx5sDnxncx652+CIfhawokEPkeM3BoIC+z/Xv7a0yMr0f3pRRwhGH455g==}
|
||||||
|
dependencies:
|
||||||
|
'@swc/helpers': 0.3.17
|
||||||
|
brotli: 1.3.3
|
||||||
|
clone: 2.1.2
|
||||||
|
deep-equal: 2.2.3
|
||||||
|
dfa: 1.2.0
|
||||||
|
restructure: 2.0.1
|
||||||
|
tiny-inflate: 1.0.3
|
||||||
|
unicode-properties: 1.4.1
|
||||||
|
unicode-trie: 2.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/for-each@0.3.5:
|
/for-each@0.3.5:
|
||||||
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
|
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
dependencies:
|
dependencies:
|
||||||
is-callable: 1.2.7
|
is-callable: 1.2.7
|
||||||
dev: true
|
|
||||||
|
|
||||||
/foreground-child@3.3.1:
|
/foreground-child@3.3.1:
|
||||||
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
||||||
@@ -8282,7 +8298,6 @@ packages:
|
|||||||
|
|
||||||
/functions-have-names@1.2.3:
|
/functions-have-names@1.2.3:
|
||||||
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
|
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/gaxios@6.7.1:
|
/gaxios@6.7.1:
|
||||||
resolution: {integrity: sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==}
|
resolution: {integrity: sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==}
|
||||||
@@ -8513,7 +8528,6 @@ packages:
|
|||||||
/has-bigints@1.1.0:
|
/has-bigints@1.1.0:
|
||||||
resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
|
resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/has-flag@3.0.0:
|
/has-flag@3.0.0:
|
||||||
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
|
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
|
||||||
@@ -8529,7 +8543,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
|
resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
es-define-property: 1.0.1
|
es-define-property: 1.0.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/has-proto@1.2.0:
|
/has-proto@1.2.0:
|
||||||
resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==}
|
resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==}
|
||||||
@@ -8644,6 +8657,10 @@ packages:
|
|||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/immediate@3.0.6:
|
||||||
|
resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/import-fresh@3.3.1:
|
/import-fresh@3.3.1:
|
||||||
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
|
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -8743,7 +8760,6 @@ packages:
|
|||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
hasown: 2.0.2
|
hasown: 2.0.2
|
||||||
side-channel: 1.1.0
|
side-channel: 1.1.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/internmap@2.0.3:
|
/internmap@2.0.3:
|
||||||
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
|
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
|
||||||
@@ -8777,6 +8793,14 @@ packages:
|
|||||||
engines: {node: '>= 0.10'}
|
engines: {node: '>= 0.10'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/is-arguments@1.2.0:
|
||||||
|
resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
dependencies:
|
||||||
|
call-bound: 1.0.4
|
||||||
|
has-tostringtag: 1.0.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/is-array-buffer@3.0.5:
|
/is-array-buffer@3.0.5:
|
||||||
resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
|
resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -8784,7 +8808,6 @@ packages:
|
|||||||
call-bind: 1.0.8
|
call-bind: 1.0.8
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
get-intrinsic: 1.3.0
|
get-intrinsic: 1.3.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/is-async-function@2.1.1:
|
/is-async-function@2.1.1:
|
||||||
resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
|
resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
|
||||||
@@ -8802,7 +8825,6 @@ packages:
|
|||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
dependencies:
|
dependencies:
|
||||||
has-bigints: 1.1.0
|
has-bigints: 1.1.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/is-binary-path@2.1.0:
|
/is-binary-path@2.1.0:
|
||||||
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
|
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
|
||||||
@@ -8817,7 +8839,6 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
has-tostringtag: 1.0.2
|
has-tostringtag: 1.0.2
|
||||||
dev: true
|
|
||||||
|
|
||||||
/is-bun-module@2.0.0:
|
/is-bun-module@2.0.0:
|
||||||
resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==}
|
resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==}
|
||||||
@@ -8828,7 +8849,6 @@ packages:
|
|||||||
/is-callable@1.2.7:
|
/is-callable@1.2.7:
|
||||||
resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
|
resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/is-core-module@2.16.1:
|
/is-core-module@2.16.1:
|
||||||
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
|
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
|
||||||
@@ -8851,7 +8871,6 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
has-tostringtag: 1.0.2
|
has-tostringtag: 1.0.2
|
||||||
dev: true
|
|
||||||
|
|
||||||
/is-docker@3.0.0:
|
/is-docker@3.0.0:
|
||||||
resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
|
resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
|
||||||
@@ -8924,7 +8943,6 @@ packages:
|
|||||||
/is-map@2.0.3:
|
/is-map@2.0.3:
|
||||||
resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
|
resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/is-negative-zero@2.0.3:
|
/is-negative-zero@2.0.3:
|
||||||
resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
|
resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
|
||||||
@@ -8937,7 +8955,6 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
has-tostringtag: 1.0.2
|
has-tostringtag: 1.0.2
|
||||||
dev: true
|
|
||||||
|
|
||||||
/is-number@7.0.0:
|
/is-number@7.0.0:
|
||||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||||
@@ -8961,19 +8978,16 @@ packages:
|
|||||||
gopd: 1.2.0
|
gopd: 1.2.0
|
||||||
has-tostringtag: 1.0.2
|
has-tostringtag: 1.0.2
|
||||||
hasown: 2.0.2
|
hasown: 2.0.2
|
||||||
dev: true
|
|
||||||
|
|
||||||
/is-set@2.0.3:
|
/is-set@2.0.3:
|
||||||
resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==}
|
resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/is-shared-array-buffer@1.0.4:
|
/is-shared-array-buffer@1.0.4:
|
||||||
resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
|
resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
dev: true
|
|
||||||
|
|
||||||
/is-stream@2.0.1:
|
/is-stream@2.0.1:
|
||||||
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
|
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
|
||||||
@@ -8989,7 +9003,6 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
has-tostringtag: 1.0.2
|
has-tostringtag: 1.0.2
|
||||||
dev: true
|
|
||||||
|
|
||||||
/is-symbol@1.1.1:
|
/is-symbol@1.1.1:
|
||||||
resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==}
|
resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==}
|
||||||
@@ -8998,7 +9011,6 @@ packages:
|
|||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
has-symbols: 1.1.0
|
has-symbols: 1.1.0
|
||||||
safe-regex-test: 1.1.0
|
safe-regex-test: 1.1.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/is-typed-array@1.1.15:
|
/is-typed-array@1.1.15:
|
||||||
resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==}
|
resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==}
|
||||||
@@ -9025,7 +9037,6 @@ packages:
|
|||||||
/is-weakmap@2.0.2:
|
/is-weakmap@2.0.2:
|
||||||
resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
|
resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/is-weakref@1.1.1:
|
/is-weakref@1.1.1:
|
||||||
resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==}
|
resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==}
|
||||||
@@ -9040,7 +9051,6 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
get-intrinsic: 1.3.0
|
get-intrinsic: 1.3.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/is-wsl@3.1.0:
|
/is-wsl@3.1.0:
|
||||||
resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
|
resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
|
||||||
@@ -9049,9 +9059,12 @@ packages:
|
|||||||
is-inside-container: 1.0.0
|
is-inside-container: 1.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/isarray@1.0.0:
|
||||||
|
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/isarray@2.0.5:
|
/isarray@2.0.5:
|
||||||
resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
|
resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/isbinaryfile@4.0.10:
|
/isbinaryfile@4.0.10:
|
||||||
resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==}
|
resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==}
|
||||||
@@ -9096,6 +9109,11 @@ packages:
|
|||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/jpeg-exif@1.1.4:
|
||||||
|
resolution: {integrity: sha512-a+bKEcCjtuW5WTdgeXFzswSrdqi0jk4XlEtZlx5A94wCoBpFjfFTbo/Tra5SpNCl/YFZPvcV1dJc+TAYeg6ROQ==}
|
||||||
|
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||||
|
dev: false
|
||||||
|
|
||||||
/js-tokens@4.0.0:
|
/js-tokens@4.0.0:
|
||||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||||
|
|
||||||
@@ -9189,6 +9207,15 @@ packages:
|
|||||||
object.values: 1.2.1
|
object.values: 1.2.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/jszip@3.10.1:
|
||||||
|
resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==}
|
||||||
|
dependencies:
|
||||||
|
lie: 3.3.0
|
||||||
|
pako: 1.0.11
|
||||||
|
readable-stream: 2.3.8
|
||||||
|
setimmediate: 1.0.5
|
||||||
|
dev: false
|
||||||
|
|
||||||
/jwa@1.4.2:
|
/jwa@1.4.2:
|
||||||
resolution: {integrity: sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==}
|
resolution: {integrity: sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -9229,6 +9256,12 @@ packages:
|
|||||||
type-check: 0.4.0
|
type-check: 0.4.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/lie@3.3.0:
|
||||||
|
resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==}
|
||||||
|
dependencies:
|
||||||
|
immediate: 3.0.6
|
||||||
|
dev: false
|
||||||
|
|
||||||
/light-my-request@5.14.0:
|
/light-my-request@5.14.0:
|
||||||
resolution: {integrity: sha512-aORPWntbpH5esaYpGOOmri0OHDOe3wC5M2MQxZ9dvMLZm6DnaAn0kJlcbU9hwsQgLzmZyReKwFwwPkR+nHu5kA==}
|
resolution: {integrity: sha512-aORPWntbpH5esaYpGOOmri0OHDOe3wC5M2MQxZ9dvMLZm6DnaAn0kJlcbU9hwsQgLzmZyReKwFwwPkR+nHu5kA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -9242,6 +9275,13 @@ packages:
|
|||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/linebreak@1.1.0:
|
||||||
|
resolution: {integrity: sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==}
|
||||||
|
dependencies:
|
||||||
|
base64-js: 0.0.8
|
||||||
|
unicode-trie: 2.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/lines-and-columns@1.2.4:
|
/lines-and-columns@1.2.4:
|
||||||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -9590,6 +9630,12 @@ packages:
|
|||||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
/nanoid@5.1.7:
|
||||||
|
resolution: {integrity: sha512-ua3NDgISf6jdwezAheMOk4mbE1LXjm1DfMUDMuJf4AqxLFK3ccGpgWizwa5YV7Yz9EpXwEaWoRXSb/BnV0t5dQ==}
|
||||||
|
engines: {node: ^18 || >=20}
|
||||||
|
hasBin: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
/napi-postinstall@0.3.4:
|
/napi-postinstall@0.3.4:
|
||||||
resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==}
|
resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==}
|
||||||
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
||||||
@@ -9763,10 +9809,17 @@ packages:
|
|||||||
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
|
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
/object-is@1.1.6:
|
||||||
|
resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
dependencies:
|
||||||
|
call-bind: 1.0.8
|
||||||
|
define-properties: 1.2.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/object-keys@1.1.1:
|
/object-keys@1.1.1:
|
||||||
resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
|
resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/object.assign@4.1.7:
|
/object.assign@4.1.7:
|
||||||
resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==}
|
resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==}
|
||||||
@@ -9778,7 +9831,6 @@ packages:
|
|||||||
es-object-atoms: 1.1.1
|
es-object-atoms: 1.1.1
|
||||||
has-symbols: 1.1.0
|
has-symbols: 1.1.0
|
||||||
object-keys: 1.1.1
|
object-keys: 1.1.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/object.entries@1.1.9:
|
/object.entries@1.1.9:
|
||||||
resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==}
|
resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==}
|
||||||
@@ -9977,6 +10029,14 @@ packages:
|
|||||||
netmask: 2.0.2
|
netmask: 2.0.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/pako@0.2.9:
|
||||||
|
resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/pako@1.0.11:
|
||||||
|
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/param-case@2.1.1:
|
/param-case@2.1.1:
|
||||||
resolution: {integrity: sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==}
|
resolution: {integrity: sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -10046,6 +10106,25 @@ packages:
|
|||||||
/pathval@1.1.1:
|
/pathval@1.1.1:
|
||||||
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
|
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
|
||||||
|
|
||||||
|
/pdf-lib@1.17.1:
|
||||||
|
resolution: {integrity: sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==}
|
||||||
|
dependencies:
|
||||||
|
'@pdf-lib/standard-fonts': 1.0.0
|
||||||
|
'@pdf-lib/upng': 1.0.1
|
||||||
|
pako: 1.0.11
|
||||||
|
tslib: 1.14.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/pdfkit@0.15.2:
|
||||||
|
resolution: {integrity: sha512-s3GjpdBFSCaeDSX/v73MI5UsPqH1kjKut2AXCgxQ5OH10lPVOu5q5vLAG0OCpz/EYqKsTSw1WHpENqMvp43RKg==}
|
||||||
|
dependencies:
|
||||||
|
crypto-js: 4.2.0
|
||||||
|
fontkit: 1.9.0
|
||||||
|
jpeg-exif: 1.1.4
|
||||||
|
linebreak: 1.1.0
|
||||||
|
png-js: 1.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/pg-cloudflare@1.2.7:
|
/pg-cloudflare@1.2.7:
|
||||||
resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==}
|
resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
@@ -10215,10 +10294,13 @@ packages:
|
|||||||
mlly: 1.8.0
|
mlly: 1.8.0
|
||||||
pathe: 2.0.3
|
pathe: 2.0.3
|
||||||
|
|
||||||
|
/png-js@1.0.0:
|
||||||
|
resolution: {integrity: sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/possible-typed-array-names@1.1.0:
|
/possible-typed-array-names@1.1.0:
|
||||||
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
|
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/postcss-import@15.1.0(postcss@8.5.6):
|
/postcss-import@15.1.0(postcss@8.5.6):
|
||||||
resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
|
resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
|
||||||
@@ -10341,6 +10423,10 @@ packages:
|
|||||||
ansi-styles: 5.2.0
|
ansi-styles: 5.2.0
|
||||||
react-is: 18.3.1
|
react-is: 18.3.1
|
||||||
|
|
||||||
|
/process-nextick-args@2.0.1:
|
||||||
|
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/process-warning@3.0.0:
|
/process-warning@3.0.0:
|
||||||
resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==}
|
resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==}
|
||||||
dev: false
|
dev: false
|
||||||
@@ -10532,6 +10618,18 @@ packages:
|
|||||||
pify: 2.3.0
|
pify: 2.3.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/readable-stream@2.3.8:
|
||||||
|
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
|
||||||
|
dependencies:
|
||||||
|
core-util-is: 1.0.3
|
||||||
|
inherits: 2.0.4
|
||||||
|
isarray: 1.0.0
|
||||||
|
process-nextick-args: 2.0.1
|
||||||
|
safe-buffer: 5.1.2
|
||||||
|
string_decoder: 1.1.1
|
||||||
|
util-deprecate: 1.0.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/readable-stream@3.6.2:
|
/readable-stream@3.6.2:
|
||||||
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
|
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
@@ -10645,7 +10743,6 @@ packages:
|
|||||||
get-proto: 1.0.1
|
get-proto: 1.0.1
|
||||||
gopd: 1.2.0
|
gopd: 1.2.0
|
||||||
set-function-name: 2.0.2
|
set-function-name: 2.0.2
|
||||||
dev: true
|
|
||||||
|
|
||||||
/registry-auth-token@3.3.2:
|
/registry-auth-token@3.3.2:
|
||||||
resolution: {integrity: sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==}
|
resolution: {integrity: sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==}
|
||||||
@@ -10725,6 +10822,10 @@ packages:
|
|||||||
signal-exit: 4.1.0
|
signal-exit: 4.1.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/restructure@2.0.1:
|
||||||
|
resolution: {integrity: sha512-e0dOpjm5DseomnXx2M5lpdZ5zoHqF1+bqdMJUohoYVVQa7cBdnk7fdmeI6byNWP/kiME72EeTiSypTCVnpLiDg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/ret@0.4.3:
|
/ret@0.4.3:
|
||||||
resolution: {integrity: sha512-0f4Memo5QP7WQyUEAYUO3esD/XjOc3Zjjg5CPsAq1p8sIu0XPeMbHJemKA0BO7tV0X7+A0FoEpbmHXWxPyD3wQ==}
|
resolution: {integrity: sha512-0f4Memo5QP7WQyUEAYUO3esD/XjOc3Zjjg5CPsAq1p8sIu0XPeMbHJemKA0BO7tV0X7+A0FoEpbmHXWxPyD3wQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -10815,6 +10916,10 @@ packages:
|
|||||||
isarray: 2.0.5
|
isarray: 2.0.5
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/safe-buffer@5.1.2:
|
||||||
|
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/safe-buffer@5.2.1:
|
/safe-buffer@5.2.1:
|
||||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||||
|
|
||||||
@@ -10833,7 +10938,6 @@ packages:
|
|||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
is-regex: 1.2.1
|
is-regex: 1.2.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/safe-regex2@3.1.0:
|
/safe-regex2@3.1.0:
|
||||||
resolution: {integrity: sha512-RAAZAGbap2kBfbVhvmnTFv73NWLMvDGOITFYTZBAaY8eR+Ir4ef7Up/e7amo+y1+AH+3PtLkrt9mvcTsG9LXug==}
|
resolution: {integrity: sha512-RAAZAGbap2kBfbVhvmnTFv73NWLMvDGOITFYTZBAaY8eR+Ir4ef7Up/e7amo+y1+AH+3PtLkrt9mvcTsG9LXug==}
|
||||||
@@ -10855,6 +10959,11 @@ packages:
|
|||||||
/safer-buffer@2.1.2:
|
/safer-buffer@2.1.2:
|
||||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||||
|
|
||||||
|
/sax@1.6.0:
|
||||||
|
resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==}
|
||||||
|
engines: {node: '>=11.0.0'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/scheduler@0.23.2:
|
/scheduler@0.23.2:
|
||||||
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
|
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -10896,7 +11005,6 @@ packages:
|
|||||||
get-intrinsic: 1.3.0
|
get-intrinsic: 1.3.0
|
||||||
gopd: 1.2.0
|
gopd: 1.2.0
|
||||||
has-property-descriptors: 1.0.2
|
has-property-descriptors: 1.0.2
|
||||||
dev: true
|
|
||||||
|
|
||||||
/set-function-name@2.0.2:
|
/set-function-name@2.0.2:
|
||||||
resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==}
|
resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==}
|
||||||
@@ -10906,7 +11014,6 @@ packages:
|
|||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
functions-have-names: 1.2.3
|
functions-have-names: 1.2.3
|
||||||
has-property-descriptors: 1.0.2
|
has-property-descriptors: 1.0.2
|
||||||
dev: true
|
|
||||||
|
|
||||||
/set-proto@1.0.0:
|
/set-proto@1.0.0:
|
||||||
resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
|
resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
|
||||||
@@ -10917,6 +11024,10 @@ packages:
|
|||||||
es-object-atoms: 1.1.1
|
es-object-atoms: 1.1.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/setimmediate@1.0.5:
|
||||||
|
resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/setprototypeof@1.2.0:
|
/setprototypeof@1.2.0:
|
||||||
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
|
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
|
||||||
dev: false
|
dev: false
|
||||||
@@ -11119,7 +11230,6 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
internal-slot: 1.1.0
|
internal-slot: 1.1.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/streamsearch@1.1.0:
|
/streamsearch@1.1.0:
|
||||||
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
|
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
|
||||||
@@ -11232,6 +11342,12 @@ packages:
|
|||||||
es-object-atoms: 1.1.1
|
es-object-atoms: 1.1.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/string_decoder@1.1.1:
|
||||||
|
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
|
||||||
|
dependencies:
|
||||||
|
safe-buffer: 5.1.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/string_decoder@1.3.0:
|
/string_decoder@1.3.0:
|
||||||
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
|
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -11451,6 +11567,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
|
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/tiny-inflate@1.0.3:
|
||||||
|
resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/tiny-invariant@1.3.3:
|
/tiny-invariant@1.3.3:
|
||||||
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
|
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
|
||||||
dev: false
|
dev: false
|
||||||
@@ -11578,7 +11698,6 @@ packages:
|
|||||||
|
|
||||||
/tslib@1.14.1:
|
/tslib@1.14.1:
|
||||||
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/tslib@2.8.1:
|
/tslib@2.8.1:
|
||||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||||
@@ -11767,6 +11886,20 @@ packages:
|
|||||||
/undici-types@6.21.0:
|
/undici-types@6.21.0:
|
||||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||||
|
|
||||||
|
/unicode-properties@1.4.1:
|
||||||
|
resolution: {integrity: sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==}
|
||||||
|
dependencies:
|
||||||
|
base64-js: 1.5.1
|
||||||
|
unicode-trie: 2.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/unicode-trie@2.0.0:
|
||||||
|
resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==}
|
||||||
|
dependencies:
|
||||||
|
pako: 0.2.9
|
||||||
|
tiny-inflate: 1.0.3
|
||||||
|
dev: false
|
||||||
|
|
||||||
/universalify@2.0.1:
|
/universalify@2.0.1:
|
||||||
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
|
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
@@ -11847,7 +11980,6 @@ packages:
|
|||||||
|
|
||||||
/util-deprecate@1.0.2:
|
/util-deprecate@1.0.2:
|
||||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/uuid@11.1.0:
|
/uuid@11.1.0:
|
||||||
resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
|
resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
|
||||||
@@ -12038,7 +12170,6 @@ packages:
|
|||||||
is-number-object: 1.1.1
|
is-number-object: 1.1.1
|
||||||
is-string: 1.1.1
|
is-string: 1.1.1
|
||||||
is-symbol: 1.1.1
|
is-symbol: 1.1.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/which-builtin-type@1.2.1:
|
/which-builtin-type@1.2.1:
|
||||||
resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==}
|
resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==}
|
||||||
@@ -12067,7 +12198,6 @@ packages:
|
|||||||
is-set: 2.0.3
|
is-set: 2.0.3
|
||||||
is-weakmap: 2.0.2
|
is-weakmap: 2.0.2
|
||||||
is-weakset: 2.0.4
|
is-weakset: 2.0.4
|
||||||
dev: true
|
|
||||||
|
|
||||||
/which-typed-array@1.1.19:
|
/which-typed-array@1.1.19:
|
||||||
resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==}
|
resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==}
|
||||||
@@ -12080,7 +12210,6 @@ packages:
|
|||||||
get-proto: 1.0.1
|
get-proto: 1.0.1
|
||||||
gopd: 1.2.0
|
gopd: 1.2.0
|
||||||
has-tostringtag: 1.0.2
|
has-tostringtag: 1.0.2
|
||||||
dev: true
|
|
||||||
|
|
||||||
/which@2.0.2:
|
/which@2.0.2:
|
||||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||||
@@ -12151,6 +12280,17 @@ packages:
|
|||||||
is-wsl: 3.1.0
|
is-wsl: 3.1.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/xml-js@1.6.11:
|
||||||
|
resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==}
|
||||||
|
hasBin: true
|
||||||
|
dependencies:
|
||||||
|
sax: 1.6.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/xml@1.0.1:
|
||||||
|
resolution: {integrity: sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/xtend@4.0.2:
|
/xtend@4.0.2:
|
||||||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||||
engines: {node: '>=0.4'}
|
engines: {node: '>=0.4'}
|
||||||
|
|||||||
@@ -9,11 +9,14 @@
|
|||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"start": "node dist/index.js",
|
"start": "node dist/index.js",
|
||||||
"lint": "eslint src --ext .ts",
|
"lint": "eslint src --ext .ts",
|
||||||
"type-check": "tsc --noEmit"
|
"type-check": "tsc --noEmit",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fastify/swagger": "^8.13.0",
|
"@fastify/swagger": "^8.13.0",
|
||||||
"@fastify/swagger-ui": "^2.0.0",
|
"@fastify/swagger-ui": "^2.0.0",
|
||||||
|
"@the-order/database": "workspace:*",
|
||||||
"@the-order/payment-gateway": "workspace:^",
|
"@the-order/payment-gateway": "workspace:^",
|
||||||
"@the-order/schemas": "workspace:*",
|
"@the-order/schemas": "workspace:*",
|
||||||
"@the-order/shared": "workspace:*",
|
"@the-order/shared": "workspace:*",
|
||||||
@@ -23,6 +26,7 @@
|
|||||||
"@types/node": "^20.10.6",
|
"@types/node": "^20.10.6",
|
||||||
"eslint": "^9.17.0",
|
"eslint": "^9.17.0",
|
||||||
"tsx": "^4.7.0",
|
"tsx": "^4.7.0",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3",
|
||||||
|
"vitest": "^1.6.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,62 +1,324 @@
|
|||||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
import { afterEach, beforeAll, describe, expect, it } from 'vitest';
|
||||||
import Fastify, { FastifyInstance } from 'fastify';
|
import { createHmac } from 'crypto';
|
||||||
import { createApiHelpers } from '@the-order/test-utils';
|
import type { FastifyInstance } from 'fastify';
|
||||||
|
|
||||||
describe('Finance Service', () => {
|
const originalEnv = { ...process.env };
|
||||||
let app: FastifyInstance;
|
|
||||||
let api: ReturnType<typeof createApiHelpers>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
function setTestEnv(): void {
|
||||||
app = Fastify({
|
process.env.NODE_ENV = 'development';
|
||||||
logger: false,
|
process.env.PORT = '4003';
|
||||||
});
|
process.env.DATABASE_URL = 'postgresql://postgres:postgres@127.0.0.1:5432/the_order_test';
|
||||||
|
process.env.STORAGE_BUCKET = 'test-bucket';
|
||||||
|
process.env.KMS_KEY_ID = 'test-kms-key';
|
||||||
|
process.env.JWT_SECRET = 'test-jwt-secret-which-is-long-enough-1234567890';
|
||||||
|
}
|
||||||
|
|
||||||
app.get('/health', async () => {
|
function base64Url(value: string): string {
|
||||||
return { status: 'ok', service: 'finance' };
|
return Buffer.from(value).toString('base64url');
|
||||||
});
|
}
|
||||||
|
|
||||||
await app.ready();
|
function createJwt(payload: Record<string, unknown>): string {
|
||||||
api = createApiHelpers(app);
|
const header = base64Url(JSON.stringify({ alg: 'HS256', typ: 'JWT' }));
|
||||||
});
|
const body = base64Url(JSON.stringify(payload));
|
||||||
|
const signature = createHmac('sha256', process.env.JWT_SECRET as string)
|
||||||
|
.update(`${header}.${body}`)
|
||||||
|
.digest('base64url');
|
||||||
|
return `${header}.${body}.${signature}`;
|
||||||
|
}
|
||||||
|
|
||||||
afterEach(async () => {
|
let createServer: (typeof import('./index'))['createServer'];
|
||||||
if (app) {
|
|
||||||
await app.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /health', () => {
|
beforeAll(async () => {
|
||||||
it('should return health status', async () => {
|
setTestEnv();
|
||||||
const response = await api.get('/health');
|
({ createServer } = await import('./index'));
|
||||||
expect(response.status).toBe(200);
|
|
||||||
expect(response.body).toHaveProperty('status');
|
|
||||||
expect(response.body).toHaveProperty('service', 'finance');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('POST /ledger/entry', () => {
|
|
||||||
it('should require authentication', async () => {
|
|
||||||
const response = await api.post('/ledger/entry', {
|
|
||||||
accountId: 'test-account',
|
|
||||||
type: 'debit',
|
|
||||||
amount: 100,
|
|
||||||
currency: 'USD',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect([401, 500]).toContain(response.status);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('POST /payments', () => {
|
|
||||||
it('should require authentication', async () => {
|
|
||||||
const response = await api.post('/payments', {
|
|
||||||
amount: 100,
|
|
||||||
currency: 'USD',
|
|
||||||
paymentMethod: 'credit_card',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect([401, 500]).toContain(response.status);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Finance Service', () => {
|
||||||
|
let server: FastifyInstance;
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
if (server) {
|
||||||
|
await server.close();
|
||||||
|
}
|
||||||
|
process.env = { ...originalEnv, ...process.env };
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns service health', async () => {
|
||||||
|
server = await createServer({ disableSwagger: true });
|
||||||
|
await server.ready();
|
||||||
|
|
||||||
|
const response = await server.inject({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/health',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response.json()).toMatchObject({
|
||||||
|
service: 'finance',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a basket-backed BTC deposit and exposes holdings plus rebalance plan', async () => {
|
||||||
|
server = await createServer({ disableSwagger: true });
|
||||||
|
await server.ready();
|
||||||
|
|
||||||
|
const token = createJwt({
|
||||||
|
id: 'client-1',
|
||||||
|
roles: ['client', 'finance'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const depositResponse = await server.inject({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/api/v1/btc-deposits',
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
payload: {
|
||||||
|
clientId: 'client-1',
|
||||||
|
mandateName: 'Solace BTC jewelry box',
|
||||||
|
chain138VaultAddress: '0x1111111111111111111111111111111111111111',
|
||||||
|
allocations: [
|
||||||
|
{ symbol: 'cBTC', targetWeightBps: 4000 },
|
||||||
|
{ symbol: 'cUSDT', targetWeightBps: 3500 },
|
||||||
|
{ symbol: 'cXAUC', targetWeightBps: 2500 },
|
||||||
|
],
|
||||||
|
expectedAmountSats: 250000000,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(depositResponse.statusCode).toBe(201);
|
||||||
|
const depositPayload = depositResponse.json() as {
|
||||||
|
deposit: { id: string; basketId: string; status: string; confirmationsRequired: number; depositAddress: string };
|
||||||
|
basket: { id: string };
|
||||||
|
createdBasket: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(depositPayload.createdBasket).toBe(true);
|
||||||
|
expect(depositPayload.deposit.status).toBe('instruction_created');
|
||||||
|
expect(depositPayload.deposit.confirmationsRequired).toBe(6);
|
||||||
|
expect(depositPayload.deposit.depositAddress.startsWith('bc1q')).toBe(true);
|
||||||
|
expect(depositPayload.deposit.basketId).toBe(depositPayload.basket.id);
|
||||||
|
|
||||||
|
const holdingsResponse = await server.inject({
|
||||||
|
method: 'GET',
|
||||||
|
url: `/api/v1/holdings?clientId=client-1&basketId=${depositPayload.basket.id}`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(holdingsResponse.statusCode).toBe(200);
|
||||||
|
const holdingsPayload = holdingsResponse.json() as {
|
||||||
|
holdings: Array<{ symbol: string; allocationWeightBps: number; status: string }>;
|
||||||
|
};
|
||||||
|
expect(holdingsPayload.holdings).toHaveLength(3);
|
||||||
|
expect(holdingsPayload.holdings[1]).toMatchObject({
|
||||||
|
symbol: 'cUSDT',
|
||||||
|
allocationWeightBps: 3500,
|
||||||
|
status: 'pending_funding',
|
||||||
|
});
|
||||||
|
|
||||||
|
const rebalancesResponse = await server.inject({
|
||||||
|
method: 'GET',
|
||||||
|
url: `/api/v1/rebalances?basketId=${depositPayload.basket.id}`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(rebalancesResponse.statusCode).toBe(200);
|
||||||
|
const rebalancesPayload = rebalancesResponse.json() as {
|
||||||
|
rebalances: Array<{ sourceSymbol: string; targetSymbols: string[]; status: string }>;
|
||||||
|
};
|
||||||
|
expect(rebalancesPayload.rebalances).toHaveLength(1);
|
||||||
|
expect(rebalancesPayload.rebalances[0]).toMatchObject({
|
||||||
|
sourceSymbol: 'cBTC',
|
||||||
|
status: 'planned',
|
||||||
|
});
|
||||||
|
expect(rebalancesPayload.rebalances[0].targetSymbols).toEqual(['cUSDT', 'cXAUC']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('defaults bridge withdrawals to cBTC -> cWBTC', async () => {
|
||||||
|
server = await createServer({ disableSwagger: true });
|
||||||
|
await server.ready();
|
||||||
|
|
||||||
|
const token = createJwt({
|
||||||
|
id: 'client-2',
|
||||||
|
roles: ['client'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const basketResponse = await server.inject({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/api/v1/baskets',
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
payload: {
|
||||||
|
clientId: 'client-2',
|
||||||
|
mandateName: 'Client 2 basket',
|
||||||
|
chain138VaultAddress: '0x2222222222222222222222222222222222222222',
|
||||||
|
allocations: [{ symbol: 'cBTC', targetWeightBps: 10000 }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const basketId = (basketResponse.json() as { basket: { id: string } }).basket.id;
|
||||||
|
|
||||||
|
const withdrawalResponse = await server.inject({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/api/v1/withdrawals/bridge',
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
payload: {
|
||||||
|
clientId: 'client-2',
|
||||||
|
basketId,
|
||||||
|
destinationChainId: 1,
|
||||||
|
destinationAddress: '0x3333333333333333333333333333333333333333',
|
||||||
|
amount: '125000000',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(withdrawalResponse.statusCode).toBe(201);
|
||||||
|
expect(withdrawalResponse.json()).toMatchObject({
|
||||||
|
withdrawal: {
|
||||||
|
sourceSymbol: 'cBTC',
|
||||||
|
destinationSymbol: 'cWBTC',
|
||||||
|
destinationChainId: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects cross-client access to another client basket', async () => {
|
||||||
|
server = await createServer({ disableSwagger: true });
|
||||||
|
await server.ready();
|
||||||
|
|
||||||
|
const ownerToken = createJwt({
|
||||||
|
id: 'client-owner',
|
||||||
|
roles: ['client'],
|
||||||
|
});
|
||||||
|
const otherClientToken = createJwt({
|
||||||
|
id: 'client-other',
|
||||||
|
roles: ['client'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const basketResponse = await server.inject({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/api/v1/baskets',
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${ownerToken}`,
|
||||||
|
},
|
||||||
|
payload: {
|
||||||
|
clientId: 'client-owner',
|
||||||
|
mandateName: 'Owner basket',
|
||||||
|
chain138VaultAddress: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
||||||
|
allocations: [{ symbol: 'cBTC', targetWeightBps: 10000 }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const basketId = (basketResponse.json() as { basket: { id: string } }).basket.id;
|
||||||
|
|
||||||
|
const holdingsResponse = await server.inject({
|
||||||
|
method: 'GET',
|
||||||
|
url: `/api/v1/holdings?basketId=${basketId}`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${otherClientToken}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(holdingsResponse.statusCode).toBe(403);
|
||||||
|
|
||||||
|
const withdrawalResponse = await server.inject({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/api/v1/withdrawals/bridge',
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${otherClientToken}`,
|
||||||
|
},
|
||||||
|
payload: {
|
||||||
|
clientId: 'client-other',
|
||||||
|
basketId,
|
||||||
|
destinationChainId: 1,
|
||||||
|
destinationAddress: '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
|
||||||
|
amount: '1000',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(withdrawalResponse.statusCode).toBe(403);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects unknown basket ids and mismatched vault addresses on deposit creation', async () => {
|
||||||
|
server = await createServer({ disableSwagger: true });
|
||||||
|
await server.ready();
|
||||||
|
|
||||||
|
const token = createJwt({
|
||||||
|
id: 'client-3',
|
||||||
|
roles: ['client'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const unknownBasketResponse = await server.inject({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/api/v1/btc-deposits',
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
payload: {
|
||||||
|
clientId: 'client-3',
|
||||||
|
basketId: '11111111-1111-4111-8111-111111111111',
|
||||||
|
chain138VaultAddress: '0xcccccccccccccccccccccccccccccccccccccccc',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(unknownBasketResponse.statusCode).toBe(404);
|
||||||
|
|
||||||
|
const basketResponse = await server.inject({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/api/v1/baskets',
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
payload: {
|
||||||
|
clientId: 'client-3',
|
||||||
|
mandateName: 'Vault match basket',
|
||||||
|
chain138VaultAddress: '0xdddddddddddddddddddddddddddddddddddddddd',
|
||||||
|
allocations: [{ symbol: 'cBTC', targetWeightBps: 10000 }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const basketId = (basketResponse.json() as { basket: { id: string } }).basket.id;
|
||||||
|
|
||||||
|
const mismatchedVaultResponse = await server.inject({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/api/v1/btc-deposits',
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
payload: {
|
||||||
|
clientId: 'client-3',
|
||||||
|
basketId,
|
||||||
|
chain138VaultAddress: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mismatchedVaultResponse.statusCode).toBe(400);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates payment payloads on the versioned route', async () => {
|
||||||
|
server = await createServer({ disableSwagger: true });
|
||||||
|
await server.ready();
|
||||||
|
|
||||||
|
const token = createJwt({
|
||||||
|
id: 'finance-user',
|
||||||
|
roles: ['finance'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await server.inject({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/api/v1/payments',
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
payload: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(400);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,191 +1,236 @@
|
|||||||
/**
|
/**
|
||||||
* Finance Service
|
* Finance Service
|
||||||
* Handles payments, ledgers, rate models, and invoicing
|
* Handles payments, ledgers, and BTC jewelry-box workflows.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Fastify, { type FastifyRequest, type FastifyReply } from 'fastify';
|
import { fileURLToPath } from 'url';
|
||||||
|
import Fastify, { type FastifyInstance, type FastifyReply, type FastifyRequest } from 'fastify';
|
||||||
import fastifySwagger from '@fastify/swagger';
|
import fastifySwagger from '@fastify/swagger';
|
||||||
import fastifySwaggerUI from '@fastify/swagger-ui';
|
import fastifySwaggerUI from '@fastify/swagger-ui';
|
||||||
import {
|
import {
|
||||||
|
AppError,
|
||||||
errorHandler,
|
errorHandler,
|
||||||
createLogger,
|
createLogger,
|
||||||
registerSecurityPlugins,
|
registerSecurityPlugins,
|
||||||
addCorrelationId,
|
addCorrelationId,
|
||||||
addRequestLogging,
|
addRequestLogging,
|
||||||
getEnv,
|
getEnv,
|
||||||
createBodySchema,
|
|
||||||
authenticateJWT,
|
authenticateJWT,
|
||||||
requireRole,
|
requireRole,
|
||||||
} from '@the-order/shared';
|
} from '@the-order/shared';
|
||||||
import { CreateLedgerEntrySchema, CreatePaymentSchema } from '@the-order/schemas';
|
import {
|
||||||
import { healthCheck as dbHealthCheck, getPool, createLedgerEntry, createPayment, updatePaymentStatus } from '@the-order/database';
|
type BasketMandate,
|
||||||
|
type CreateBasketMandate,
|
||||||
|
type CreateBtcDeposit,
|
||||||
|
type CreateBridgeWithdrawal,
|
||||||
|
} from '@the-order/schemas';
|
||||||
|
import {
|
||||||
|
healthCheck as dbHealthCheck,
|
||||||
|
getPool,
|
||||||
|
createLedgerEntry,
|
||||||
|
createPayment,
|
||||||
|
updatePaymentStatus,
|
||||||
|
} from '@the-order/database';
|
||||||
import { StripePaymentGateway } from '@the-order/payment-gateway';
|
import { StripePaymentGateway } from '@the-order/payment-gateway';
|
||||||
|
import {
|
||||||
|
JewelryBoxStore,
|
||||||
|
createDefaultBridgeWithdrawal,
|
||||||
|
} from './jewelry-box-store';
|
||||||
|
|
||||||
const logger = createLogger('finance-service');
|
const logger = createLogger('finance-service');
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const server: any = Fastify({
|
|
||||||
logger: logger as any,
|
|
||||||
requestIdLogLabel: 'requestId',
|
|
||||||
disableRequestLogging: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize database pool
|
|
||||||
const env = getEnv();
|
const env = getEnv();
|
||||||
if (env.DATABASE_URL) {
|
const PRIVILEGED_FINANCE_ROLES = new Set(['admin', 'accountant', 'finance']);
|
||||||
getPool({ connectionString: env.DATABASE_URL });
|
|
||||||
|
export type CreateServerOptions = {
|
||||||
|
jewelryBoxStore?: JewelryBoxStore;
|
||||||
|
disableSwagger?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
function isPrivilegedFinanceUser(request: FastifyRequest): boolean {
|
||||||
|
return (request.user?.roles ?? []).some((role) => PRIVILEGED_FINANCE_ROLES.has(role));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize payment gateway
|
function assertClientAccess(request: FastifyRequest, clientId: string): void {
|
||||||
let paymentGateway: StripePaymentGateway | null = null;
|
if (!request.user) {
|
||||||
try {
|
throw new AppError(401, 'UNAUTHORIZED', 'Authentication required');
|
||||||
if (env.PAYMENT_GATEWAY_API_KEY) {
|
|
||||||
paymentGateway = new StripePaymentGateway();
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
logger.warn({ err: error }, 'Payment gateway not configured');
|
if (request.user.id === clientId || isPrivilegedFinanceUser(request)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AppError(403, 'FORBIDDEN', 'Cannot access another client\'s finance basket');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize server
|
function resolveClientScope(request: FastifyRequest, clientId?: string): string | undefined {
|
||||||
async function initializeServer(): Promise<void> {
|
if (clientId) {
|
||||||
// Register Swagger
|
assertClientAccess(request, clientId);
|
||||||
const swaggerUrl = env.SWAGGER_SERVER_URL || (env.NODE_ENV === 'development' ? 'http://localhost:4003' : undefined);
|
return clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isPrivilegedFinanceUser(request) ? undefined : request.user?.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAccessibleBasket(
|
||||||
|
request: FastifyRequest,
|
||||||
|
store: JewelryBoxStore,
|
||||||
|
basketId: string,
|
||||||
|
): BasketMandate {
|
||||||
|
const basket = store.getBasket(basketId);
|
||||||
|
if (!basket) {
|
||||||
|
throw new AppError(404, 'NOT_FOUND', 'Basket not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
assertClientAccess(request, basket.clientId);
|
||||||
|
return basket;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPaymentGateway(): StripePaymentGateway | null {
|
||||||
|
try {
|
||||||
|
return env.PAYMENT_GATEWAY_API_KEY ? new StripePaymentGateway() : null;
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn({ err: error }, 'Payment gateway not configured');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function safeDbHealthCheck(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
return await dbHealthCheck();
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn({ err: error }, 'Database health check failed');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function registerDocs(server: any, disableSwagger = false): Promise<void> {
|
||||||
|
if (disableSwagger) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const swaggerUrl =
|
||||||
|
env.SWAGGER_SERVER_URL || (env.NODE_ENV === 'development' ? 'http://localhost:4003' : undefined);
|
||||||
|
|
||||||
if (!swaggerUrl) {
|
if (!swaggerUrl) {
|
||||||
logger.warn('SWAGGER_SERVER_URL not set, Swagger documentation will not be available');
|
logger.warn('SWAGGER_SERVER_URL not set, Swagger documentation will not be available');
|
||||||
} else {
|
return;
|
||||||
await server.register(fastifySwagger, {
|
|
||||||
openapi: {
|
|
||||||
info: {
|
|
||||||
title: 'Finance Service API',
|
|
||||||
description: 'Payments, ledgers, rate models, and invoicing',
|
|
||||||
version: '1.0.0',
|
|
||||||
},
|
|
||||||
servers: [
|
|
||||||
{
|
|
||||||
url: swaggerUrl,
|
|
||||||
description: env.NODE_ENV || 'Development server',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await server.register(fastifySwaggerUI, {
|
|
||||||
routePrefix: '/docs',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await registerSecurityPlugins(server as any);
|
await server.register(fastifySwagger, {
|
||||||
addCorrelationId(server as any);
|
openapi: {
|
||||||
addRequestLogging(server as any);
|
info: {
|
||||||
server.setErrorHandler(errorHandler as any);
|
title: 'Finance Service API',
|
||||||
|
description: 'Payments, ledgers, and BTC jewelry-box workflows',
|
||||||
|
version: '1.0.0',
|
||||||
|
},
|
||||||
|
servers: [
|
||||||
|
{
|
||||||
|
url: swaggerUrl,
|
||||||
|
description: env.NODE_ENV || 'Development server',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.register(fastifySwaggerUI, {
|
||||||
|
routePrefix: '/docs',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Health check
|
function registerHealthRoute(server: any): void {
|
||||||
server.get(
|
server.get(
|
||||||
'/health',
|
'/health',
|
||||||
{
|
{
|
||||||
schema: {
|
schema: {
|
||||||
description: 'Health check endpoint',
|
description: 'Health check endpoint',
|
||||||
tags: ['health'],
|
tags: ['health'],
|
||||||
response: {
|
response: {
|
||||||
200: {
|
200: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
status: { type: 'string' },
|
status: { type: 'string' },
|
||||||
service: { type: 'string' },
|
service: { type: 'string' },
|
||||||
database: { type: 'string' },
|
database: { type: 'string' },
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
async () => {
|
|
||||||
const dbHealthy = await dbHealthCheck();
|
|
||||||
return {
|
|
||||||
status: dbHealthy ? 'ok' : 'degraded',
|
|
||||||
service: 'finance',
|
|
||||||
database: dbHealthy ? 'connected' : 'disconnected',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Ledger operations
|
|
||||||
server.post(
|
|
||||||
'/ledger/entry',
|
|
||||||
{
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
preHandler: [authenticateJWT as any, requireRole('admin', 'accountant', 'finance') as any],
|
|
||||||
schema: {
|
|
||||||
...createBodySchema(CreateLedgerEntrySchema),
|
|
||||||
description: 'Create a ledger entry',
|
|
||||||
tags: ['ledger'],
|
|
||||||
response: {
|
|
||||||
201: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
entry: {
|
|
||||||
type: 'object',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
async () => {
|
||||||
async (request: FastifyRequest, reply: FastifyReply) => {
|
const dbHealthy = await safeDbHealthCheck();
|
||||||
const body = request.body as {
|
return {
|
||||||
accountId: string;
|
status: dbHealthy ? 'ok' : 'degraded',
|
||||||
type: 'debit' | 'credit';
|
service: 'finance',
|
||||||
amount: number;
|
database: dbHealthy ? 'connected' : 'disconnected',
|
||||||
currency: string;
|
};
|
||||||
description?: string;
|
},
|
||||||
reference?: string;
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
// Save to database
|
function registerLedgerRoute(server: any): void {
|
||||||
const entry = await createLedgerEntry({
|
server.post(
|
||||||
account_id: body.accountId,
|
'/ledger/entry',
|
||||||
type: body.type,
|
{
|
||||||
amount: body.amount,
|
preHandler: [authenticateJWT as never, requireRole('admin', 'accountant', 'finance') as never],
|
||||||
currency: body.currency,
|
schema: {
|
||||||
description: body.description,
|
description: 'Create a ledger entry',
|
||||||
reference: body.reference,
|
tags: ['ledger'],
|
||||||
});
|
body: {
|
||||||
|
|
||||||
return reply.status(201).send({ entry });
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Payment processing
|
|
||||||
server.post(
|
|
||||||
'/payments',
|
|
||||||
{
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
preHandler: [authenticateJWT as any],
|
|
||||||
schema: {
|
|
||||||
...createBodySchema(CreatePaymentSchema),
|
|
||||||
description: 'Process a payment',
|
|
||||||
tags: ['payments'],
|
|
||||||
response: {
|
|
||||||
201: {
|
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
additionalProperties: false,
|
||||||
|
required: ['accountId', 'type', 'amount', 'currency'],
|
||||||
properties: {
|
properties: {
|
||||||
payment: {
|
accountId: { type: 'string' },
|
||||||
type: 'object',
|
type: { type: 'string', enum: ['debit', 'credit'] },
|
||||||
},
|
amount: { type: 'number', exclusiveMinimum: 0 },
|
||||||
|
currency: { type: 'string', minLength: 3, maxLength: 3 },
|
||||||
|
description: { type: 'string' },
|
||||||
|
reference: { type: 'string' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
async (
|
||||||
async (request: FastifyRequest, reply: FastifyReply) => {
|
request: FastifyRequest<{
|
||||||
const body = request.body as {
|
Body: {
|
||||||
amount: number;
|
accountId: string;
|
||||||
currency: string;
|
type: 'debit' | 'credit';
|
||||||
paymentMethod: string;
|
amount: number;
|
||||||
metadata?: Record<string, string>;
|
currency: string;
|
||||||
};
|
description?: string;
|
||||||
|
reference?: string;
|
||||||
|
};
|
||||||
|
}>,
|
||||||
|
reply: FastifyReply,
|
||||||
|
) => {
|
||||||
|
const body = request.body;
|
||||||
|
const entry = await createLedgerEntry({
|
||||||
|
account_id: body.accountId,
|
||||||
|
type: body.type,
|
||||||
|
amount: body.amount,
|
||||||
|
currency: body.currency,
|
||||||
|
description: body.description,
|
||||||
|
reference: body.reference,
|
||||||
|
});
|
||||||
|
|
||||||
// Create payment record
|
return reply.status(201).send({ entry });
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerPaymentsRoute(server: any, paymentGateway: StripePaymentGateway | null): void {
|
||||||
|
const handler = async (
|
||||||
|
request: FastifyRequest<{
|
||||||
|
Body: {
|
||||||
|
amount: number;
|
||||||
|
currency: string;
|
||||||
|
paymentMethod: string;
|
||||||
|
metadata?: Record<string, string>;
|
||||||
|
};
|
||||||
|
}>,
|
||||||
|
reply: FastifyReply,
|
||||||
|
) => {
|
||||||
|
const body = request.body;
|
||||||
const payment = await createPayment({
|
const payment = await createPayment({
|
||||||
amount: body.amount,
|
amount: body.amount,
|
||||||
currency: body.currency,
|
currency: body.currency,
|
||||||
@@ -193,53 +238,324 @@ server.post(
|
|||||||
payment_method: body.paymentMethod,
|
payment_method: body.paymentMethod,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Process payment through gateway if available
|
if (!paymentGateway) {
|
||||||
if (paymentGateway) {
|
|
||||||
try {
|
|
||||||
const result = await paymentGateway.processPayment(
|
|
||||||
body.amount,
|
|
||||||
body.currency,
|
|
||||||
body.paymentMethod,
|
|
||||||
{
|
|
||||||
payment_id: payment.id,
|
|
||||||
...body.metadata,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update payment status
|
|
||||||
const updatedPayment = await updatePaymentStatus(
|
|
||||||
payment.id,
|
|
||||||
result.status,
|
|
||||||
result.transactionId,
|
|
||||||
result.gatewayResponse
|
|
||||||
);
|
|
||||||
|
|
||||||
return reply.status(201).send({ payment: updatedPayment });
|
|
||||||
} catch (error) {
|
|
||||||
logger.error({ err: error, paymentId: payment.id }, 'Payment processing failed');
|
|
||||||
await updatePaymentStatus(payment.id, 'failed', undefined, { error: String(error) });
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// No payment gateway configured - return pending status
|
|
||||||
return reply.status(201).send({ payment });
|
return reply.status(201).send({ payment });
|
||||||
}
|
}
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Start server
|
try {
|
||||||
const start = async () => {
|
const result = await paymentGateway.processPayment(
|
||||||
|
body.amount,
|
||||||
|
body.currency,
|
||||||
|
body.paymentMethod,
|
||||||
|
{
|
||||||
|
payment_id: payment.id,
|
||||||
|
...body.metadata,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedPayment = await updatePaymentStatus(
|
||||||
|
payment.id,
|
||||||
|
result.status,
|
||||||
|
result.transactionId,
|
||||||
|
result.gatewayResponse,
|
||||||
|
);
|
||||||
|
|
||||||
|
return reply.status(201).send({ payment: updatedPayment });
|
||||||
|
} catch (error) {
|
||||||
|
logger.error({ err: error, paymentId: payment.id }, 'Payment processing failed');
|
||||||
|
await updatePaymentStatus(payment.id, 'failed', undefined, { error: String(error) });
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
preHandler: [authenticateJWT as never],
|
||||||
|
schema: {
|
||||||
|
description: 'Process a payment',
|
||||||
|
tags: ['payments'],
|
||||||
|
body: {
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: false,
|
||||||
|
required: ['amount', 'currency', 'paymentMethod'],
|
||||||
|
properties: {
|
||||||
|
amount: { type: 'number', exclusiveMinimum: 0 },
|
||||||
|
currency: { type: 'string', minLength: 3, maxLength: 3 },
|
||||||
|
paymentMethod: { type: 'string' },
|
||||||
|
description: { type: 'string' },
|
||||||
|
metadata: {
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: { type: 'string' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
server.post('/payments', options, handler);
|
||||||
|
server.post('/api/v1/payments', options, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerJewelryBoxRoutes(server: any, store: JewelryBoxStore): void {
|
||||||
|
server.post(
|
||||||
|
'/api/v1/baskets',
|
||||||
|
{
|
||||||
|
preHandler: [authenticateJWT as never],
|
||||||
|
schema: {
|
||||||
|
description: 'Create a Chain 138 basket mandate for BTC settlement flows',
|
||||||
|
tags: ['jewelry-box'],
|
||||||
|
body: {
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: false,
|
||||||
|
required: ['clientId', 'mandateName', 'chain138VaultAddress', 'allocations'],
|
||||||
|
properties: {
|
||||||
|
clientId: { type: 'string', minLength: 1 },
|
||||||
|
mandateName: { type: 'string', minLength: 1 },
|
||||||
|
chain138VaultAddress: { type: 'string', pattern: '^0x[a-fA-F0-9]{40}$' },
|
||||||
|
baseAssetSymbol: { type: 'string', minLength: 2, maxLength: 16 },
|
||||||
|
allocations: {
|
||||||
|
type: 'array',
|
||||||
|
minItems: 1,
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: false,
|
||||||
|
required: ['symbol', 'targetWeightBps'],
|
||||||
|
properties: {
|
||||||
|
symbol: { type: 'string', minLength: 2, maxLength: 16 },
|
||||||
|
targetWeightBps: { type: 'integer', minimum: 1, maximum: 10000 },
|
||||||
|
routeHint: { type: 'string' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async (
|
||||||
|
request: FastifyRequest<{ Body: CreateBasketMandate }>,
|
||||||
|
reply: FastifyReply,
|
||||||
|
) => {
|
||||||
|
assertClientAccess(request, request.body.clientId);
|
||||||
|
const basket = store.createBasket(request.body);
|
||||||
|
return reply.status(201).send({ basket });
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
server.post(
|
||||||
|
'/api/v1/btc-deposits',
|
||||||
|
{
|
||||||
|
preHandler: [authenticateJWT as never],
|
||||||
|
schema: {
|
||||||
|
description: 'Create a BTC deposit instruction and associate it with a jewelry-box basket',
|
||||||
|
tags: ['jewelry-box'],
|
||||||
|
body: {
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: false,
|
||||||
|
required: ['clientId', 'chain138VaultAddress'],
|
||||||
|
properties: {
|
||||||
|
clientId: { type: 'string', minLength: 1 },
|
||||||
|
basketId: { type: 'string' },
|
||||||
|
mandateName: { type: 'string', minLength: 1 },
|
||||||
|
chain138VaultAddress: { type: 'string', pattern: '^0x[a-fA-F0-9]{40}$' },
|
||||||
|
expectedAmountSats: { type: 'integer', minimum: 1 },
|
||||||
|
clientReference: { type: 'string', minLength: 1 },
|
||||||
|
allocations: {
|
||||||
|
type: 'array',
|
||||||
|
minItems: 1,
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: false,
|
||||||
|
required: ['symbol', 'targetWeightBps'],
|
||||||
|
properties: {
|
||||||
|
symbol: { type: 'string', minLength: 2, maxLength: 16 },
|
||||||
|
targetWeightBps: { type: 'integer', minimum: 1, maximum: 10000 },
|
||||||
|
routeHint: { type: 'string' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async (
|
||||||
|
request: FastifyRequest<{ Body: CreateBtcDeposit }>,
|
||||||
|
reply: FastifyReply,
|
||||||
|
) => {
|
||||||
|
assertClientAccess(request, request.body.clientId);
|
||||||
|
|
||||||
|
if (request.body.basketId) {
|
||||||
|
const basket = getAccessibleBasket(request, store, request.body.basketId);
|
||||||
|
if (basket.clientId !== request.body.clientId) {
|
||||||
|
throw new AppError(400, 'CLIENT_MISMATCH', 'Deposit client does not match basket client');
|
||||||
|
}
|
||||||
|
if (basket.chain138VaultAddress !== request.body.chain138VaultAddress) {
|
||||||
|
throw new AppError(
|
||||||
|
400,
|
||||||
|
'VAULT_ADDRESS_MISMATCH',
|
||||||
|
'Deposit vault address does not match the selected basket',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = store.createDeposit(request.body);
|
||||||
|
return reply.status(201).send(result);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
server.get(
|
||||||
|
'/api/v1/btc-deposits/:id',
|
||||||
|
{
|
||||||
|
preHandler: [authenticateJWT as never],
|
||||||
|
schema: {
|
||||||
|
description: 'Get BTC deposit status',
|
||||||
|
tags: ['jewelry-box'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async (
|
||||||
|
request: FastifyRequest<{ Params: { id: string } }>,
|
||||||
|
) => {
|
||||||
|
const deposit = store.getDeposit(request.params.id);
|
||||||
|
if (!deposit) {
|
||||||
|
throw new AppError(404, 'NOT_FOUND', 'Deposit not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
assertClientAccess(request, deposit.clientId);
|
||||||
|
return { deposit };
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
server.get(
|
||||||
|
'/api/v1/holdings',
|
||||||
|
{
|
||||||
|
preHandler: [authenticateJWT as never],
|
||||||
|
schema: {
|
||||||
|
description: 'List basket holdings derived from underlying allocations',
|
||||||
|
tags: ['jewelry-box'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async (
|
||||||
|
request: FastifyRequest<{ Querystring: { clientId?: string; basketId?: string } }>,
|
||||||
|
) => {
|
||||||
|
if (request.query.basketId) {
|
||||||
|
const basket = getAccessibleBasket(request, store, request.query.basketId);
|
||||||
|
if (request.query.clientId && basket.clientId !== request.query.clientId) {
|
||||||
|
throw new AppError(400, 'CLIENT_MISMATCH', 'Requested client does not own the selected basket');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const holdings = store.listHoldings({
|
||||||
|
...request.query,
|
||||||
|
clientId: resolveClientScope(request, request.query.clientId),
|
||||||
|
});
|
||||||
|
return { holdings };
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
server.get(
|
||||||
|
'/api/v1/rebalances',
|
||||||
|
{
|
||||||
|
preHandler: [authenticateJWT as never],
|
||||||
|
schema: {
|
||||||
|
description: 'List planned or queued rebalances for jewelry-box baskets',
|
||||||
|
tags: ['jewelry-box'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async (
|
||||||
|
request: FastifyRequest<{ Querystring: { clientId?: string; basketId?: string } }>,
|
||||||
|
) => {
|
||||||
|
if (request.query.basketId) {
|
||||||
|
const basket = getAccessibleBasket(request, store, request.query.basketId);
|
||||||
|
if (request.query.clientId && basket.clientId !== request.query.clientId) {
|
||||||
|
throw new AppError(400, 'CLIENT_MISMATCH', 'Requested client does not own the selected basket');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rebalances = store.listRebalances({
|
||||||
|
...request.query,
|
||||||
|
clientId: resolveClientScope(request, request.query.clientId),
|
||||||
|
});
|
||||||
|
return { rebalances };
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
server.post(
|
||||||
|
'/api/v1/withdrawals/bridge',
|
||||||
|
{
|
||||||
|
preHandler: [authenticateJWT as never],
|
||||||
|
schema: {
|
||||||
|
description: 'Request a public-chain bridge withdrawal from a jewelry-box basket',
|
||||||
|
tags: ['jewelry-box'],
|
||||||
|
body: {
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: false,
|
||||||
|
required: ['clientId', 'basketId', 'destinationChainId', 'destinationAddress', 'amount'],
|
||||||
|
properties: {
|
||||||
|
clientId: { type: 'string', minLength: 1 },
|
||||||
|
basketId: { type: 'string', minLength: 1 },
|
||||||
|
sourceSymbol: { type: 'string', minLength: 2, maxLength: 16 },
|
||||||
|
destinationSymbol: { type: 'string', minLength: 2, maxLength: 16 },
|
||||||
|
destinationChainId: { type: 'integer', minimum: 1 },
|
||||||
|
destinationAddress: { type: 'string', pattern: '^0x[a-fA-F0-9]{40}$' },
|
||||||
|
amount: { type: 'string', minLength: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async (
|
||||||
|
request: FastifyRequest<{ Body: CreateBridgeWithdrawal }>,
|
||||||
|
reply: FastifyReply,
|
||||||
|
) => {
|
||||||
|
const basket = getAccessibleBasket(request, store, request.body.basketId);
|
||||||
|
if (basket.clientId !== request.body.clientId) {
|
||||||
|
throw new AppError(400, 'CLIENT_MISMATCH', 'Withdrawal client does not match basket client');
|
||||||
|
}
|
||||||
|
|
||||||
|
const withdrawal = store.createBridgeWithdrawal(
|
||||||
|
createDefaultBridgeWithdrawal(request.body as CreateBridgeWithdrawal),
|
||||||
|
);
|
||||||
|
return reply.status(201).send({ withdrawal });
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createServer(options: CreateServerOptions = {}): Promise<FastifyInstance> {
|
||||||
|
if (env.DATABASE_URL) {
|
||||||
|
getPool({ connectionString: env.DATABASE_URL });
|
||||||
|
}
|
||||||
|
|
||||||
|
const server: any = Fastify({
|
||||||
|
logger: logger as any,
|
||||||
|
requestIdLogLabel: 'requestId',
|
||||||
|
disableRequestLogging: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
await registerDocs(server, options.disableSwagger);
|
||||||
|
await registerSecurityPlugins(server as any);
|
||||||
|
addCorrelationId(server);
|
||||||
|
addRequestLogging(server);
|
||||||
|
server.setErrorHandler(errorHandler as any);
|
||||||
|
|
||||||
|
registerHealthRoute(server);
|
||||||
|
registerLedgerRoute(server);
|
||||||
|
registerPaymentsRoute(server, createPaymentGateway());
|
||||||
|
registerJewelryBoxRoutes(server, options.jewelryBoxStore || new JewelryBoxStore());
|
||||||
|
|
||||||
|
return server as FastifyInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function start(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await initializeServer();
|
const server = await createServer();
|
||||||
const env = getEnv();
|
|
||||||
const port = env.PORT || 4003;
|
const port = env.PORT || 4003;
|
||||||
await server.listen({ port, host: '0.0.0.0' });
|
await server.listen({ port, host: '0.0.0.0' });
|
||||||
logger.info({ port }, 'Finance service listening');
|
logger.info({ port }, 'Finance service listening');
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
logger.error({ err }, 'Failed to start server');
|
logger.error({ err: error }, 'Failed to start server');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
start();
|
const isDirectExecution = process.argv[1] === fileURLToPath(import.meta.url);
|
||||||
|
|
||||||
|
if (isDirectExecution) {
|
||||||
|
void start();
|
||||||
|
}
|
||||||
|
|||||||
296
services/finance/src/jewelry-box-store.ts
Normal file
296
services/finance/src/jewelry-box-store.ts
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
import { randomUUID } from 'crypto';
|
||||||
|
import type {
|
||||||
|
BasketAllocation,
|
||||||
|
BasketMandate,
|
||||||
|
BtcDeposit,
|
||||||
|
BridgeWithdrawal,
|
||||||
|
CreateBasketMandate,
|
||||||
|
CreateBridgeWithdrawal,
|
||||||
|
CreateBtcDeposit,
|
||||||
|
Holding,
|
||||||
|
Rebalance,
|
||||||
|
} from '@the-order/schemas';
|
||||||
|
|
||||||
|
const BTC_CONFIRMATIONS_REQUIRED = 6;
|
||||||
|
const DEFAULT_ALLOCATION: BasketAllocation = { symbol: 'cBTC', targetWeightBps: 10_000 };
|
||||||
|
const BTC_BRIDGE_SYMBOL = 'cWBTC';
|
||||||
|
const BECH32_ALPHABET = '023456789acdefghjklmnpqrstuvwxyz';
|
||||||
|
|
||||||
|
function nowIso(): string {
|
||||||
|
return new Date().toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function sumWeights(allocations: BasketAllocation[]): number {
|
||||||
|
return allocations.reduce((total, allocation) => total + allocation.targetWeightBps, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toPseudoBitcoinAddress(seed: string): string {
|
||||||
|
const normalized = seed.replace(/[^a-f0-9]/gi, '').toLowerCase() || 'cb7c138';
|
||||||
|
let out = 'bc1q';
|
||||||
|
for (let index = 0; index < 32; index += 1) {
|
||||||
|
const source = normalized[index % normalized.length] ?? '0';
|
||||||
|
const value = parseInt(source, 16);
|
||||||
|
out += Number.isNaN(value) ? 'q' : BECH32_ALPHABET[value];
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clone<T>(value: T): T {
|
||||||
|
return JSON.parse(JSON.stringify(value)) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class JewelryBoxStore {
|
||||||
|
private readonly baskets = new Map<string, BasketMandate>();
|
||||||
|
private readonly deposits = new Map<string, BtcDeposit>();
|
||||||
|
private readonly rebalances = new Map<string, Rebalance>();
|
||||||
|
private readonly withdrawals = new Map<string, BridgeWithdrawal>();
|
||||||
|
|
||||||
|
createBasket(input: CreateBasketMandate): BasketMandate {
|
||||||
|
const allocations = input.allocations.length > 0 ? input.allocations : [DEFAULT_ALLOCATION];
|
||||||
|
if (sumWeights(allocations) !== 10_000) {
|
||||||
|
throw new Error('Basket allocations must sum to exactly 10,000 bps');
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestamp = nowIso();
|
||||||
|
const basket: BasketMandate = {
|
||||||
|
id: randomUUID(),
|
||||||
|
clientId: input.clientId,
|
||||||
|
mandateName: input.mandateName,
|
||||||
|
chain138VaultAddress: input.chain138VaultAddress,
|
||||||
|
baseAssetSymbol: input.baseAssetSymbol || 'cBTC',
|
||||||
|
status: 'active',
|
||||||
|
allocations: clone(allocations),
|
||||||
|
createdAt: timestamp,
|
||||||
|
updatedAt: timestamp,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.baskets.set(basket.id, basket);
|
||||||
|
this.seedRebalancePlan(basket);
|
||||||
|
return clone(basket);
|
||||||
|
}
|
||||||
|
|
||||||
|
getBasket(id: string): BasketMandate | null {
|
||||||
|
const basket = this.baskets.get(id);
|
||||||
|
return basket ? clone(basket) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
createDeposit(input: CreateBtcDeposit): { deposit: BtcDeposit; basket: BasketMandate; createdBasket: boolean } {
|
||||||
|
let basket = input.basketId ? this.baskets.get(input.basketId) ?? null : null;
|
||||||
|
let createdBasket = false;
|
||||||
|
|
||||||
|
if (input.basketId && !basket) {
|
||||||
|
throw new Error(`Unknown basket: ${input.basketId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!basket) {
|
||||||
|
basket = this.createBasket({
|
||||||
|
clientId: input.clientId,
|
||||||
|
mandateName: input.mandateName || `BTC jewelry box ${input.clientId}`,
|
||||||
|
chain138VaultAddress: input.chain138VaultAddress,
|
||||||
|
allocations: input.allocations && input.allocations.length > 0 ? input.allocations : [DEFAULT_ALLOCATION],
|
||||||
|
baseAssetSymbol: 'cBTC',
|
||||||
|
});
|
||||||
|
createdBasket = true;
|
||||||
|
} else {
|
||||||
|
if (basket.clientId !== input.clientId) {
|
||||||
|
throw new Error('Basket client does not match deposit client');
|
||||||
|
}
|
||||||
|
if (basket.chain138VaultAddress !== input.chain138VaultAddress) {
|
||||||
|
throw new Error('Basket vault address does not match deposit vault address');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestamp = nowIso();
|
||||||
|
const depositId = randomUUID();
|
||||||
|
const deposit: BtcDeposit = {
|
||||||
|
id: depositId,
|
||||||
|
clientId: input.clientId,
|
||||||
|
basketId: basket.id,
|
||||||
|
chain138VaultAddress: basket.chain138VaultAddress,
|
||||||
|
depositAddress: toPseudoBitcoinAddress(depositId),
|
||||||
|
expectedAmountSats: input.expectedAmountSats,
|
||||||
|
confirmationsRequired: BTC_CONFIRMATIONS_REQUIRED,
|
||||||
|
currentConfirmations: 0,
|
||||||
|
status: 'instruction_created',
|
||||||
|
createdAt: timestamp,
|
||||||
|
updatedAt: timestamp,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.deposits.set(deposit.id, deposit);
|
||||||
|
return { deposit: clone(deposit), basket: clone(basket), createdBasket };
|
||||||
|
}
|
||||||
|
|
||||||
|
getDeposit(id: string): BtcDeposit | null {
|
||||||
|
const deposit = this.deposits.get(id);
|
||||||
|
return deposit ? clone(deposit) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
listHoldings(filters: { clientId?: string; basketId?: string }): Holding[] {
|
||||||
|
const baskets = Array.from(this.baskets.values()).filter((basket) => {
|
||||||
|
if (filters.clientId && basket.clientId !== filters.clientId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (filters.basketId && basket.id !== filters.basketId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return baskets.flatMap((basket) => {
|
||||||
|
const fundedSats = Array.from(this.deposits.values())
|
||||||
|
.filter((deposit) => deposit.basketId === basket.id && deposit.status === 'minted')
|
||||||
|
.reduce((total, deposit) => total + (deposit.expectedAmountSats ?? 0), 0);
|
||||||
|
|
||||||
|
return basket.allocations.map((allocation: BasketAllocation) => ({
|
||||||
|
clientId: basket.clientId,
|
||||||
|
basketId: basket.id,
|
||||||
|
symbol: allocation.symbol,
|
||||||
|
allocationWeightBps: allocation.targetWeightBps,
|
||||||
|
bookValueSats: Math.floor((fundedSats * allocation.targetWeightBps) / 10_000),
|
||||||
|
status: fundedSats > 0 ? 'funded' : 'pending_funding',
|
||||||
|
updatedAt: basket.updatedAt,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
listRebalances(filters: { clientId?: string; basketId?: string }): Rebalance[] {
|
||||||
|
return Array.from(this.rebalances.values())
|
||||||
|
.filter((rebalance) => {
|
||||||
|
if (filters.clientId && rebalance.clientId !== filters.clientId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (filters.basketId && rebalance.basketId !== filters.basketId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.map((rebalance) => clone(rebalance));
|
||||||
|
}
|
||||||
|
|
||||||
|
createBridgeWithdrawal(input: CreateBridgeWithdrawal): BridgeWithdrawal {
|
||||||
|
if (!this.baskets.has(input.basketId)) {
|
||||||
|
throw new Error(`Unknown basket: ${input.basketId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestamp = nowIso();
|
||||||
|
const withdrawal: BridgeWithdrawal = {
|
||||||
|
id: randomUUID(),
|
||||||
|
clientId: input.clientId,
|
||||||
|
basketId: input.basketId,
|
||||||
|
sourceSymbol: input.sourceSymbol || 'cBTC',
|
||||||
|
destinationSymbol: input.destinationSymbol || BTC_BRIDGE_SYMBOL,
|
||||||
|
destinationChainId: input.destinationChainId,
|
||||||
|
destinationAddress: input.destinationAddress,
|
||||||
|
amount: input.amount,
|
||||||
|
status: 'pending',
|
||||||
|
createdAt: timestamp,
|
||||||
|
updatedAt: timestamp,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.withdrawals.set(withdrawal.id, withdrawal);
|
||||||
|
return clone(withdrawal);
|
||||||
|
}
|
||||||
|
|
||||||
|
markDepositObserved(
|
||||||
|
depositId: string,
|
||||||
|
observedTxId: string,
|
||||||
|
confirmations: number,
|
||||||
|
expectedAmountSats?: number,
|
||||||
|
): BtcDeposit {
|
||||||
|
const deposit = this.deposits.get(depositId);
|
||||||
|
if (!deposit) {
|
||||||
|
throw new Error(`Unknown BTC deposit: ${depositId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextConfirmations = Math.max(0, confirmations);
|
||||||
|
deposit.observedTxId = observedTxId;
|
||||||
|
deposit.currentConfirmations = nextConfirmations;
|
||||||
|
if (expectedAmountSats !== undefined) {
|
||||||
|
deposit.expectedAmountSats = expectedAmountSats;
|
||||||
|
}
|
||||||
|
deposit.status =
|
||||||
|
nextConfirmations >= deposit.confirmationsRequired ? 'confirmed' : 'pending_confirmations';
|
||||||
|
deposit.updatedAt = nowIso();
|
||||||
|
|
||||||
|
return clone(deposit);
|
||||||
|
}
|
||||||
|
|
||||||
|
markDepositMinted(depositId: string): BtcDeposit {
|
||||||
|
const deposit = this.deposits.get(depositId);
|
||||||
|
if (!deposit) {
|
||||||
|
throw new Error(`Unknown BTC deposit: ${depositId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
deposit.currentConfirmations = Math.max(deposit.currentConfirmations, deposit.confirmationsRequired);
|
||||||
|
deposit.status = 'minted';
|
||||||
|
deposit.updatedAt = nowIso();
|
||||||
|
|
||||||
|
const basket = this.baskets.get(deposit.basketId);
|
||||||
|
if (basket) {
|
||||||
|
basket.updatedAt = deposit.updatedAt;
|
||||||
|
if (basket.allocations.some((allocation: BasketAllocation) => allocation.symbol !== 'cBTC')) {
|
||||||
|
this.promoteRebalancePlan(basket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clone(deposit);
|
||||||
|
}
|
||||||
|
|
||||||
|
freezeDeposit(depositId: string, reason: string): BtcDeposit {
|
||||||
|
const deposit = this.deposits.get(depositId);
|
||||||
|
if (!deposit) {
|
||||||
|
throw new Error(`Unknown BTC deposit: ${depositId}`);
|
||||||
|
}
|
||||||
|
deposit.status = 'frozen';
|
||||||
|
deposit.freezeReason = reason;
|
||||||
|
deposit.updatedAt = nowIso();
|
||||||
|
return clone(deposit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private seedRebalancePlan(basket: BasketMandate): void {
|
||||||
|
const targetSymbols = basket.allocations
|
||||||
|
.map((allocation: BasketAllocation) => allocation.symbol)
|
||||||
|
.filter((symbol: string) => symbol !== 'cBTC');
|
||||||
|
|
||||||
|
if (targetSymbols.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestamp = nowIso();
|
||||||
|
const rebalance: Rebalance = {
|
||||||
|
id: randomUUID(),
|
||||||
|
clientId: basket.clientId,
|
||||||
|
basketId: basket.id,
|
||||||
|
sourceSymbol: 'cBTC',
|
||||||
|
targetSymbols,
|
||||||
|
reason: 'Awaiting funded BTC deposit before Chain 138 rebalance',
|
||||||
|
status: 'planned',
|
||||||
|
createdAt: timestamp,
|
||||||
|
updatedAt: timestamp,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.rebalances.set(rebalance.id, rebalance);
|
||||||
|
}
|
||||||
|
|
||||||
|
private promoteRebalancePlan(basket: BasketMandate): void {
|
||||||
|
for (const rebalance of this.rebalances.values()) {
|
||||||
|
if (rebalance.basketId === basket.id && rebalance.status === 'planned') {
|
||||||
|
rebalance.status = 'queued';
|
||||||
|
rebalance.updatedAt = nowIso();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createDefaultBridgeWithdrawal(
|
||||||
|
input: Omit<CreateBridgeWithdrawal, 'destinationSymbol' | 'sourceSymbol'> & {
|
||||||
|
sourceSymbol?: string;
|
||||||
|
destinationSymbol?: string;
|
||||||
|
},
|
||||||
|
): CreateBridgeWithdrawal {
|
||||||
|
return {
|
||||||
|
...input,
|
||||||
|
sourceSymbol: input.sourceSymbol || 'cBTC',
|
||||||
|
destinationSymbol: input.destinationSymbol || BTC_BRIDGE_SYMBOL,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,45 +1,56 @@
|
|||||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { FastifyInstance } from 'fastify';
|
import { JewelryBoxStore } from '../src/jewelry-box-store';
|
||||||
import { createServer } from '../src/index';
|
|
||||||
|
|
||||||
describe('Finance Service', () => {
|
describe('JewelryBoxStore', () => {
|
||||||
let server: FastifyInstance;
|
it('creates queued rebalance work only for non-cBTC allocations', () => {
|
||||||
|
const store = new JewelryBoxStore();
|
||||||
|
const basket = store.createBasket({
|
||||||
|
clientId: 'client-1',
|
||||||
|
mandateName: 'Multi-asset jewelry box',
|
||||||
|
chain138VaultAddress: '0x4444444444444444444444444444444444444444',
|
||||||
|
allocations: [
|
||||||
|
{ symbol: 'cBTC', targetWeightBps: 2500 },
|
||||||
|
{ symbol: 'cUSDC', targetWeightBps: 5000 },
|
||||||
|
{ symbol: 'cXAUC', targetWeightBps: 2500 },
|
||||||
|
],
|
||||||
|
baseAssetSymbol: 'cBTC',
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
const rebalances = store.listRebalances({ basketId: basket.id });
|
||||||
server = await createServer();
|
expect(rebalances).toHaveLength(1);
|
||||||
await server.ready();
|
expect(rebalances[0]).toMatchObject({
|
||||||
});
|
sourceSymbol: 'cBTC',
|
||||||
|
targetSymbols: ['cUSDC', 'cXAUC'],
|
||||||
afterEach(async () => {
|
status: 'planned',
|
||||||
await server.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Health Check', () => {
|
|
||||||
it('should return 200 on health check', async () => {
|
|
||||||
const response = await server.inject({
|
|
||||||
method: 'GET',
|
|
||||||
url: '/health',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
expect(response.json()).toMatchObject({
|
|
||||||
status: 'ok',
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Payment Processing', () => {
|
it('promotes rebalance plans once a deposit is marked minted', () => {
|
||||||
it('should validate payment request schema', async () => {
|
const store = new JewelryBoxStore();
|
||||||
const response = await server.inject({
|
const { basket, deposit } = store.createDeposit({
|
||||||
method: 'POST',
|
clientId: 'client-2',
|
||||||
url: '/api/v1/payments',
|
chain138VaultAddress: '0x5555555555555555555555555555555555555555',
|
||||||
payload: {
|
allocations: [
|
||||||
// Invalid payload to test validation
|
{ symbol: 'cBTC', targetWeightBps: 3000 },
|
||||||
},
|
{ symbol: 'cUSDT', targetWeightBps: 7000 },
|
||||||
});
|
],
|
||||||
|
expectedAmountSats: 300000000,
|
||||||
expect(response.statusCode).toBe(400);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
store.markDepositObserved(deposit.id, 'btc-tx-1', 6, 300000000);
|
||||||
|
store.markDepositMinted(deposit.id);
|
||||||
|
|
||||||
|
const updatedDeposit = store.getDeposit(deposit.id);
|
||||||
|
expect(updatedDeposit?.status).toBe('minted');
|
||||||
|
|
||||||
|
const holdings = store.listHoldings({ basketId: basket.id });
|
||||||
|
expect(holdings[1]).toMatchObject({
|
||||||
|
symbol: 'cUSDT',
|
||||||
|
bookValueSats: 210000000,
|
||||||
|
status: 'funded',
|
||||||
|
});
|
||||||
|
|
||||||
|
const rebalances = store.listRebalances({ basketId: basket.id });
|
||||||
|
expect(rebalances[0]?.status).toBe('queued');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
18
services/finance/vitest.config.mjs
Normal file
18
services/finance/vitest.config.mjs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const root = '/home/intlc/projects/proxmox/the-order';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@the-order/auth': path.join(root, 'packages/auth/src/index.ts'),
|
||||||
|
'@the-order/shared': path.join(root, 'packages/shared/src/index.ts'),
|
||||||
|
'@the-order/schemas': path.join(root, 'packages/schemas/src/index.ts'),
|
||||||
|
'@the-order/database': path.join(root, 'packages/database/src/index.ts'),
|
||||||
|
'@the-order/payment-gateway': path.join(root, 'packages/payment-gateway/src/index.ts'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
test: {
|
||||||
|
environment: 'node',
|
||||||
|
},
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user