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:
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user