- 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
249 lines
6.3 KiB
TypeScript
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;
|
|
}
|
|
}
|