Skip to content
markpaper

src/account/fees.test.ts

v0.3.0 · 3 KB

Download file
import { describe, expect, it } from 'vitest';
import type { InfoCallOptions, InfoRequest, InfoRequester } from '../transport/types.js';
import { fetchUserFees, parseUserFees } from './fees.js';

const USER = `0x${'0'.repeat(38)}Ab`;

describe('parseUserFees', () => {
  it('userAddRate is maker, userCrossRate is taker; bps computed exactly', () => {
    const f = parseUserFees({ userAddRate: '0.00015', userCrossRate: '0.00045', activeReferralDiscount: '0.0' });
    expect(f.makerRate).toBe(0.00015);
    expect(f.takerRate).toBe(0.00045);
    expect(f.makerBps).toBe(1.5);
    expect(f.takerBps).toBe(4.5);
    expect(f.exact).toEqual({ makerRate: '0.00015', takerRate: '0.00045', makerBps: '1.5', takerBps: '4.5', referralDiscount: '0' });
  });

  it('rates that float multiplication would distort stay exact', () => {
    const f = parseUserFees({ userAddRate: '0.00007', userCrossRate: '0.00028' });
    expect(f.makerBps).toBe(0.7);
    expect(f.takerBps).toBe(2.8);
  });

  it('supports maker rebates (negative rates)', () => {
    expect(parseUserFees({ userAddRate: '-0.00002', userCrossRate: '0.00025' }).makerBps).toBe(-0.2);
  });

  it('referral discount defaults to 0 and is parsed when present', () => {
    expect(parseUserFees({ userAddRate: '0', userCrossRate: '0' }).referralDiscount).toBe(0);
    expect(parseUserFees({ userAddRate: '0', userCrossRate: '0', activeReferralDiscount: '0.04' }).referralDiscount).toBe(0.04);
  });

  it('spot rates are optional', () => {
    const f = parseUserFees({ userAddRate: '0.0001', userCrossRate: '0.0003', userSpotAddRate: '0.0004', userSpotCrossRate: '0.0007' });
    expect([f.spotMakerRate, f.spotTakerRate]).toEqual([0.0004, 0.0007]);
    expect(parseUserFees({ userAddRate: '0.0001', userCrossRate: '0.0003' }).spotMakerRate).toBeNull();
  });

  it('malformed answers throw', () => {
    expect(() => parseUserFees(null)).toThrow(expect.objectContaining({ code: 'INVALID_RESPONSE' }));
    expect(() => parseUserFees({ userAddRate: 0.00015, userCrossRate: '0.00045' })).toThrow(/userAddRate/);
    expect(() => parseUserFees({ userAddRate: '0.00015' })).toThrow(/userCrossRate/);
    expect(() => parseUserFees({ userAddRate: '0', userCrossRate: '0', activeReferralDiscount: 'x' })).toThrow(/Referral/i);
  });
});

describe('fetchUserFees', () => {
  it('requests userFees for the lowercased account', async () => {
    const calls: InfoRequest[] = [];
    const info = (async (body: InfoRequest, _opts?: InfoCallOptions) => {
      calls.push(body);
      return { userAddRate: '0.00015', userCrossRate: '0.00045', activeReferralDiscount: '0.0' };
    }) as InfoRequester;
    const f = await fetchUserFees(info, USER);
    expect(calls).toEqual([{ type: 'userFees', user: USER.toLowerCase() }]);
    expect(f.takerBps).toBe(4.5);
  });

  it('rejects a malformed address without a request', async () => {
    const info = (async () => {
      throw new Error('should not be called');
    }) as InfoRequester;
    await expect(fetchUserFees(info, '0xYOUR_ADDRESS')).rejects.toMatchObject({ code: 'INVALID_ARGUMENT' });
  });
});
All files