Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build-lux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ env:

jobs:
build:
runs-on: [self-hosted, linux, amd64]
runs-on: lux-build
permissions:
contents: read
packages: write
Expand Down Expand Up @@ -80,7 +80,7 @@ jobs:
deploy:
needs: build
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
runs-on: [self-hosted, linux, amd64]
runs-on: lux-build
permissions:
contents: read
steps:
Expand Down
1 change: 1 addition & 0 deletions cspell.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
// words - list of words to be always considered correct
"words": [
"aatx",
"CLOB",
"hanzoai",
"zooai",
"parsdao",
Expand Down
11 changes: 5 additions & 6 deletions lib/api/dchain/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
// D-Chain (DEX) API hooks and types.
// C-Chain native DEX (0x9999) subgraph hooks and types.

export { useDexData } from './useDexData';

export type { UseDexDataResult } from './useDexData';

export type {
DexOrder,
DexTrade,
DexPool,
DexSymbolStats,
DexOverviewStats,
DexMarket,
DexFill,
DexMarketView,
DexOverview,
} from './types';
72 changes: 28 additions & 44 deletions lib/api/dchain/types.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,39 @@
// D-Chain (DEX Chain) API response types for the Lux DexVM indexer.
// C-Chain native DEX (0x9999 V4 PoolManager) subgraph types.
// Source: the explorer's embedded luxfi/graph "dex" (CLOB) subgraph, served at
// `${config.apis.general.endpoint}/v1/graph/cchain/dex/graphql`. Markets are
// emitted by the on-chain Initialize event and fills by the DEXFill event.

export interface DexOrder {
export interface DexMarket {
readonly id: string;
readonly symbol: string;
readonly side: 'buy' | 'sell';
readonly price: string;
readonly quantity: string;
readonly filled: string;
readonly status: 'open' | 'filled' | 'cancelled' | 'partial';
readonly maker: string;
readonly timestamp: string;
// BASE/QUOTE pair the indexer bound from the two currencies' ERC-20 symbols
// (empty/poolId-hex when a token has no clean symbol). Authoritative when set.
readonly symbol?: string;
readonly baseToken: string;
readonly quoteToken: string;
readonly feeTier: number;
readonly volume24h: string;
readonly tradeCount: number;
readonly lastPrice: string;
readonly lastUpdate: number;
}

export interface DexTrade {
export interface DexFill {
readonly id: string;
readonly symbol: string;
readonly price: string;
readonly quantity: string;
readonly buyer: string;
readonly seller: string;
readonly fee: string;
readonly timestamp: string;
readonly blockHeight: number;
readonly market: string;
readonly taker: string;
readonly amountOut: string;
readonly timestamp: number;
readonly txHash: string;
}

export interface DexPool {
readonly id: string;
readonly tokenA: string;
readonly tokenB: string;
readonly reserveA: string;
readonly reserveB: string;
readonly tvl: string;
readonly volume24h: string;
readonly fee: string;
// A market with its base/quote token symbols resolved to a human pair label.
export interface DexMarketView extends DexMarket {
readonly pair: string;
}

export interface DexSymbolStats {
readonly symbol: string;
readonly lastPrice: string;
readonly change24h: number;
readonly volume24h: string;
readonly high24h: string;
readonly low24h: string;
readonly trades24h: number;
}

// Aggregated DEX statistics

export interface DexOverviewStats {
readonly totalPairs: number;
// Aggregated headline stats derived from the live market set.
export interface DexOverview {
readonly totalMarkets: number;
readonly volume24h: string;
readonly activeOrders: number;
readonly tradesToday: number;
readonly totalTrades: number;
}
68 changes: 68 additions & 0 deletions lib/api/dchain/useDexData.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { DexMarket } from './types';

import { expect, test, describe } from 'vitest';

import { buildMarketViews, computeOverview } from './useDexData';

// Real shapes returned by the testnet cchain/dex subgraph.
const MARKET_A: DexMarket = {
id: '0x70ed78d5fb8fd83110ffb5b6726edb3d76e0968d5dba042e59e0c97af1a1f2cd',
baseToken: '0xaa1c9a2527f70aee8c163fc053ccbba0e0df7a37',
quoteToken: '0xf90434869b41d554c6446aacd73c06406b6ce9c1',
feeTier: 3000,
volume24h: '10',
tradeCount: 1,
lastPrice: '0',
lastUpdate: 11,
};

const MARKET_B: DexMarket = {
id: '0x2b0a2ff82be3beb2d2b58d0766b1c6c0b3f9bb44689e469bb49934e4e32802f1',
baseToken: '0xa811aac7a2e132c167650f93f3e14305e6205f95',
quoteToken: '0xccba5e1036dcab7ff4885975d339cafab2a4c0c4',
feeTier: 3000,
volume24h: '1258432996',
tradeCount: 21,
lastPrice: '0',
lastUpdate: 325,
};

describe('buildMarketViews', () => {
test('falls back to a shortened address when a token has no metadata', () => {
const [ view ] = buildMarketViews([ MARKET_A ], new Map());
expect(view.pair).toBe('0xaa1c...7a37/0xf904...e9c1');
});

test('resolves symbols from the metadata map (case-insensitive)', () => {
const symbols = new Map([
[ MARKET_A.baseToken.toLowerCase(), 'LUX' ],
[ MARKET_A.quoteToken.toLowerCase(), 'USDC' ],
]);
const [ view ] = buildMarketViews([ MARKET_A ], symbols);
expect(view.pair).toBe('LUX/USDC');
});

test('preserves the source market fields', () => {
const [ view ] = buildMarketViews([ MARKET_A ], new Map());
expect(view).toMatchObject({ id: MARKET_A.id, feeTier: 3000, tradeCount: 1, volume24h: '10' });
});

test('returns an empty list for no markets (empty state)', () => {
expect(buildMarketViews([], new Map())).toHaveLength(0);
});
});

describe('computeOverview', () => {
test('aggregates market count, big-int volume, and trade count', () => {
const views = buildMarketViews([ MARKET_A, MARKET_B ], new Map());
expect(computeOverview(views)).toEqual({
totalMarkets: 2,
volume24h: '1258433006',
totalTrades: 22,
});
});

test('is zeroed for an empty market set', () => {
expect(computeOverview([])).toEqual({ totalMarkets: 0, volume24h: '0', totalTrades: 0 });
});
});
Loading
Loading