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:
defiQUG
2026-04-07 22:59:32 -07:00
parent 923b703d97
commit 3f7cc0f854
18 changed files with 1825 additions and 494 deletions

View File

@@ -1,45 +1,56 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { FastifyInstance } from 'fastify';
import { createServer } from '../src/index';
import { describe, expect, it } from 'vitest';
import { JewelryBoxStore } from '../src/jewelry-box-store';
describe('Finance Service', () => {
let server: FastifyInstance;
describe('JewelryBoxStore', () => {
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 () => {
server = await createServer();
await server.ready();
});
afterEach(async () => {
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',
});
const rebalances = store.listRebalances({ basketId: basket.id });
expect(rebalances).toHaveLength(1);
expect(rebalances[0]).toMatchObject({
sourceSymbol: 'cBTC',
targetSymbols: ['cUSDC', 'cXAUC'],
status: 'planned',
});
});
describe('Payment Processing', () => {
it('should validate payment request schema', async () => {
const response = await server.inject({
method: 'POST',
url: '/api/v1/payments',
payload: {
// Invalid payload to test validation
},
});
expect(response.statusCode).toBe(400);
it('promotes rebalance plans once a deposit is marked minted', () => {
const store = new JewelryBoxStore();
const { basket, deposit } = store.createDeposit({
clientId: 'client-2',
chain138VaultAddress: '0x5555555555555555555555555555555555555555',
allocations: [
{ symbol: 'cBTC', targetWeightBps: 3000 },
{ symbol: 'cUSDT', targetWeightBps: 7000 },
],
expectedAmountSats: 300000000,
});
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');
});
});