Files
the_order/packages/api-client/src/finance.ts
defiQUG 3f7cc0f854 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
2026-04-07 22:59:32 -07:00

249 lines
6.3 KiB
TypeScript

import axios, { AxiosInstance } from 'axios';
export interface Payment {
id: string;
amount: number;
currency: string;
status: 'pending' | 'completed' | 'failed' | 'refunded';
paymentMethod: string;
createdAt: string;
description?: string;
}
export interface LedgerEntry {
id: string;
accountId: string;
amount: number;
currency: string;
type: 'debit' | 'credit';
description: string;
timestamp: 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 {
protected client: AxiosInstance;
constructor(baseURL?: string) {
const apiBaseURL =
baseURL ||
(typeof window !== 'undefined'
? process.env.NEXT_PUBLIC_FINANCE_SERVICE_URL || 'http://localhost:4003'
: 'http://localhost:4003');
this.client = axios.create({
baseURL: apiBaseURL,
headers: {
'Content-Type': 'application/json',
},
});
this.client.interceptors.request.use(
(config) => {
const token = this.getAuthToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error),
);
}
private getAuthToken(): string | null {
if (typeof window === 'undefined') return null;
return localStorage.getItem('auth_token');
}
setAuthToken(token: string): void {
if (typeof window !== 'undefined') {
localStorage.setItem('auth_token', token);
}
}
clearAuthToken(): void {
if (typeof window !== 'undefined') {
localStorage.removeItem('auth_token');
}
}
async createPayment(data: {
amount: number;
currency: string;
paymentMethod: string;
description?: string;
}): Promise<Payment> {
const response = await this.client.post<{ payment: Payment }>('/api/v1/payments', data);
return response.data.payment;
}
async getPayment(paymentId: string): Promise<Payment> {
const response = await this.client.get<Payment>(`/api/v1/payments/${paymentId}`);
return response.data;
}
async listPayments(filters?: {
status?: string;
page?: number;
pageSize?: number;
}): Promise<{ payments: Payment[]; total: number }> {
const response = await this.client.get<{ payments: Payment[]; total: number }>('/api/v1/payments', {
params: filters,
});
return response.data;
}
async getLedgerEntries(filters?: {
accountId?: string;
type?: 'debit' | 'credit';
startDate?: string;
endDate?: string;
page?: number;
pageSize?: number;
}): Promise<{ entries: LedgerEntry[]; total: number }> {
const response = await this.client.get<{ entries: LedgerEntry[]; total: number }>(
'/api/v1/ledger',
{ params: filters },
);
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;
}
}