Skip to content

Commit 9cbc3e3

Browse files
committed
Update the leaderboard Nuanze endpoints
1 parent 82bcbd7 commit 9cbc3e3

9 files changed

Lines changed: 482 additions & 79 deletions

File tree

apps/e2e/src/nuanze-client/leaderboardQueries.test.ts

Lines changed: 284 additions & 59 deletions
Large diffs are not rendered by default.

packages/nuanze-client/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,14 @@ Nuanze runs a single public deployment that serves mainnet data, so every entry
3030
## API Surface
3131

3232
Each method maps one-to-one onto a public operation. Most are GET; `getFollowedLeaderboard` is a
33-
non-mutating POST whose body carries the followed set:
33+
non-mutating POST whose body names the username whose followed accounts should be ranked:
3434

3535
- `getNews`
3636
- `getMarkets`
3737
- `getMarketByTicker`
3838
- `getFundingRates`
3939
- `getLeaderboard`
40+
- `getSubaccountLeaderboard`
4041
- `getPlatformSummary`
4142
- `getFollowedLeaderboard`
4243
- `getWalletSummary`

packages/nuanze-client/src/NuanzeClient.ts

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
mapNuanzeNewsResponse,
1616
mapNuanzeOpenPositionsResponse,
1717
mapNuanzePlatformSummaryResponse,
18+
mapNuanzeSubaccountLeaderboardResponse,
1819
mapNuanzeWalletPnlResponse,
1920
mapNuanzeWalletPnlSeriesResponse,
2021
mapNuanzeWalletPositionsResponse,
@@ -52,6 +53,8 @@ import {
5253
GetNuanzeOpenPositionsResponse,
5354
GetNuanzePlatformSummaryParams,
5455
GetNuanzePlatformSummaryResponse,
56+
GetNuanzeSubaccountLeaderboardParams,
57+
GetNuanzeSubaccountLeaderboardResponse,
5558
GetNuanzeWalletPnlParams,
5659
GetNuanzeWalletPnlResponse,
5760
GetNuanzeWalletPnlSeriesParams,
@@ -80,6 +83,7 @@ import {
8083
NuanzeServerNewsResponse,
8184
NuanzeServerOpenPositionsResponse,
8285
NuanzeServerPlatformSummaryResponse,
86+
NuanzeServerSubaccountLeaderboardResponse,
8387
NuanzeServerWalletPnlResponse,
8488
NuanzeServerWalletPnlSeriesResponse,
8589
NuanzeServerWalletPositionsResponse,
@@ -106,8 +110,8 @@ export interface NuanzeClientOpts {
106110
* linked signer. It also sends no `x-nado-client-type` header: Nuanze is a public API that does not
107111
* attribute traffic per client, and its `Access-Control-Allow-Headers` does not list the header, so
108112
* sending it would fail CORS preflight in the browser. Most operations are GET;
109-
* {@link NuanzeClient.getFollowedLeaderboard} is a non-mutating POST whose body carries a followed
110-
* set larger than a query string can reliably hold.
113+
* {@link NuanzeClient.getFollowedLeaderboard} is a non-mutating POST whose body identifies the
114+
* username whose follow graph should be ranked.
111115
*/
112116
export class NuanzeClient {
113117
readonly opts: NuanzeClientOpts;
@@ -213,6 +217,25 @@ export class NuanzeClient {
213217
);
214218
}
215219

220+
/**
221+
* Gets the global public leaderboard of username-claimed subaccounts. Results are sorted by
222+
* equity-basis account PnL descending with nulls last. `globalRank` is independent of the active
223+
* privacy and trading filters. Pagination uses a filter-bound opaque cursor.
224+
*
225+
* @throws {NuanzeServerFailureError} With `BAD_REQUEST`, `INVALID_CURSOR`, or
226+
* `CURSOR_FILTER_MISMATCH` when filters or the cursor are invalid.
227+
*/
228+
async getSubaccountLeaderboard(
229+
params: GetNuanzeSubaccountLeaderboardParams = {},
230+
): Promise<GetNuanzeSubaccountLeaderboardResponse> {
231+
return mapNuanzeSubaccountLeaderboardResponse(
232+
await this.getJson<NuanzeServerSubaccountLeaderboardResponse>(
233+
'/leaderboard/subaccounts',
234+
params,
235+
),
236+
);
237+
}
238+
216239
/**
217240
* Gets platform activity summary from five-minute aggregates.
218241
*
@@ -230,14 +253,12 @@ export class NuanzeClient {
230253
}
231254

232255
/**
233-
* Gets leaderboard stats for a set of followed subaccounts. POST rather than GET so the body can
234-
* carry up to 300 `subaccountHex` values. The response preserves request order. Subaccounts with
235-
* no data in the window are backfilled with `pnl: null`, `globalRank: null`, zero counts, and an
236-
* empty `productIds`. The response is not cached.
256+
* Gets leaderboard stats for the subaccounts a username actively follows, capped at 300 and
257+
* sorted by PnL descending with nulls last. Subaccounts with no PnL in the window are included by
258+
* default with null PnL/rank, zero counts, and no products. The response is not cached.
237259
*
238-
* @throws {NuanzeServerFailureError} With `INVALID_SUBACCOUNT` if a hex is not 32-byte
239-
* 0x-prefixed, and `BAD_REQUEST` if the set is empty, exceeds 300, or `timeframe` is not a
240-
* documented value.
260+
* @throws {NuanzeServerFailureError} With `BAD_REQUEST` when params are invalid, or
261+
* `USERNAME_NOT_FOUND` when the supplied username has no claimed identity.
241262
*/
242263
async getFollowedLeaderboard(
243264
params: GetNuanzeFollowedLeaderboardParams,

packages/nuanze-client/src/dataMappers.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
NuanzePositioningTotals,
2424
NuanzeSeriesPoint,
2525
NuanzeStockFundamentals,
26+
NuanzeSubaccountLeaderboardItem,
2627
NuanzeWalletPosition,
2728
NuanzeWalletTrade,
2829
} from './types/clientModelTypes';
@@ -42,6 +43,7 @@ import {
4243
GetNuanzeNewsResponse,
4344
GetNuanzeOpenPositionsResponse,
4445
GetNuanzePlatformSummaryResponse,
46+
GetNuanzeSubaccountLeaderboardResponse,
4547
GetNuanzeWalletPnlResponse,
4648
GetNuanzeWalletPnlSeriesResponse,
4749
GetNuanzeWalletPositionsResponse,
@@ -75,6 +77,7 @@ import {
7577
NuanzeServerSeriesPoint,
7678
NuanzeServerSidePositioningCell,
7779
NuanzeServerStockFundamentals,
80+
NuanzeServerSubaccountLeaderboardItem,
7881
NuanzeServerWalletPosition,
7982
NuanzeServerWalletTrade,
8083
} from './types/serverModelTypes';
@@ -92,6 +95,7 @@ import {
9295
NuanzeServerNewsResponse,
9396
NuanzeServerOpenPositionsResponse,
9497
NuanzeServerPlatformSummaryResponse,
98+
NuanzeServerSubaccountLeaderboardResponse,
9599
NuanzeServerWalletPnlResponse,
96100
NuanzeServerWalletPnlSeriesResponse,
97101
NuanzeServerWalletPositionsResponse,
@@ -335,6 +339,8 @@ export function mapNuanzeLeaderboardItem(
335339
wins: server.wins,
336340
losses: server.losses,
337341
winRate: mapNuanzeDecimal(server.winRate),
342+
productIds: server.productIds,
343+
productCount: server.productCount,
338344
};
339345
}
340346

@@ -362,6 +368,29 @@ export function mapNuanzeFollowedLeaderboardItem(
362368
): NuanzeFollowedLeaderboardItem {
363369
return {
364370
subaccountHex: server.subaccountHex,
371+
username: server.username,
372+
displayName: server.displayName,
373+
pnl: mapNuanzeDecimal(server.pnl),
374+
wins: server.wins,
375+
losses: server.losses,
376+
winRate: mapNuanzeDecimal(server.winRate),
377+
trades: server.trades,
378+
productIds: server.productIds,
379+
productCount: server.productCount,
380+
globalRank: server.globalRank,
381+
};
382+
}
383+
384+
/**
385+
* Maps a server-side public subaccount leaderboard row.
386+
*/
387+
export function mapNuanzeSubaccountLeaderboardItem(
388+
server: NuanzeServerSubaccountLeaderboardItem,
389+
): NuanzeSubaccountLeaderboardItem {
390+
return {
391+
subaccountHex: server.subaccountHex,
392+
username: server.username,
393+
displayName: server.displayName,
365394
pnl: mapNuanzeDecimal(server.pnl),
366395
wins: server.wins,
367396
losses: server.losses,
@@ -373,6 +402,21 @@ export function mapNuanzeFollowedLeaderboardItem(
373402
};
374403
}
375404

405+
/**
406+
* Maps a server-side `GET /leaderboard/subaccounts` response.
407+
*/
408+
export function mapNuanzeSubaccountLeaderboardResponse(
409+
server: NuanzeServerSubaccountLeaderboardResponse,
410+
): GetNuanzeSubaccountLeaderboardResponse {
411+
return {
412+
timeframe: server.timeframe,
413+
totalCount: server.totalCount,
414+
items: server.items.map(mapNuanzeSubaccountLeaderboardItem),
415+
nextCursor: server.nextCursor,
416+
asOf: server.asOf,
417+
};
418+
}
419+
376420
/**
377421
* Maps a server-side `POST /wallets/leaderboard` response.
378422
*/

packages/nuanze-client/src/types/clientModelTypes.ts

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -660,15 +660,18 @@ export interface NuanzeMarketDetail extends NuanzeMarket {
660660
}
661661

662662
/**
663-
* Per-subaccount row from `POST /wallets/leaderboard`. Unknown or window-younger
664-
* subaccounts are backfilled with `pnl: null`, `globalRank: null`, zero counts, and
665-
* an empty `productIds`.
663+
* Per-subaccount row from `POST /wallets/leaderboard`. Untraded followed subaccounts are included
664+
* by default with null PnL/rank/name fields where unavailable, zero counts, and no products.
666665
*/
667666
export interface NuanzeFollowedLeaderboardItem {
668667
/**
669668
* Lowercase bytes32 subaccount hex (owner + name), the SDK `subaccountToHex` form.
670669
*/
671670
subaccountHex: string;
671+
/** Canonical username, or null when the followed subaccount has not claimed one. */
672+
username: string | null;
673+
/** User-facing display name, or null when the followed subaccount has not claimed one. */
674+
displayName: string | null;
672675
/**
673676
* Equity-basis account PnL for the requested timeframe, or null when the subaccount
674677
* has no data in the window.
@@ -693,6 +696,42 @@ export interface NuanzeFollowedLeaderboardItem {
693696
globalRank: number | null;
694697
}
695698

699+
/**
700+
* Username-claimed subaccount row from `GET /leaderboard/subaccounts`.
701+
*/
702+
export interface NuanzeSubaccountLeaderboardItem {
703+
/**
704+
* Lowercase bytes32 subaccount hex (owner + name), the SDK `subaccountToHex` form.
705+
*/
706+
subaccountHex: string;
707+
/** Canonical username claimed by this subaccount. */
708+
username: string;
709+
/** User-facing display name, or null when unavailable. */
710+
displayName: string | null;
711+
/**
712+
* Equity-basis account PnL for the requested timeframe, or null for an untraded subaccount
713+
* returned when `includeUntraded` is enabled.
714+
*/
715+
pnl: BigNumber | null;
716+
/** Close-derived win count. */
717+
wins: number;
718+
/** Close-derived loss count. */
719+
losses: number;
720+
/** Win rate, or null when there are no closed trades. */
721+
winRate: BigNumber | null;
722+
/** Perp fill count in the window. */
723+
trades: number;
724+
/** Traded product IDs, sorted by fill count descending. */
725+
productIds: number[];
726+
/** Number of distinct traded products. Equals `productIds.length`. */
727+
productCount: number;
728+
/**
729+
* Rank across all subaccounts for the timeframe, independent of page filters, or null when the
730+
* subaccount has no PnL in the window.
731+
*/
732+
globalRank: number | null;
733+
}
734+
696735
/**
697736
* Leaderboard row. PnL is equity-basis account PnL, not realized PnL.
698737
*/
@@ -719,6 +758,10 @@ export interface NuanzeLeaderboardItem {
719758
losses: number;
720759
/** Win rate, or null when there are no closed trades. */
721760
winRate: BigNumber | null;
761+
/** Traded product IDs, sorted by fill count descending. */
762+
productIds: number[];
763+
/** Number of distinct traded products. Equals `productIds.length`. */
764+
productCount: number;
722765
}
723766

724767
/**

packages/nuanze-client/src/types/clientTypes.ts

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
NuanzeSeriesMetric,
3737
NuanzeSeriesPoint,
3838
NuanzeSourceInterval,
39+
NuanzeSubaccountLeaderboardItem,
3940
NuanzeWalletPosition,
4041
NuanzeWalletTrade,
4142
} from './clientModelTypes';
@@ -169,18 +170,52 @@ export interface GetNuanzeLeaderboardResponse {
169170
asOf: string;
170171
}
171172

173+
/**
174+
* Params for `NuanzeClient.getSubaccountLeaderboard`.
175+
*/
176+
export interface GetNuanzeSubaccountLeaderboardParams {
177+
/** Ranking window, default `30d`. */
178+
timeframe?: NuanzeLeaderboardTimeframe;
179+
/** Page size, 1-200, default 100. */
180+
limit?: number;
181+
/**
182+
* Opaque cursor from the previous page. It is bound to the normalized filters and must be
183+
* returned unchanged.
184+
*/
185+
cursor?: string;
186+
/** Include subaccounts with Private Mode enabled, default false. */
187+
includePrivate?: boolean;
188+
/** Include subaccounts with no PnL in the requested window, default false. */
189+
includeUntraded?: boolean;
190+
}
191+
192+
/**
193+
* Response of `NuanzeClient.getSubaccountLeaderboard`.
194+
*/
195+
export interface GetNuanzeSubaccountLeaderboardResponse {
196+
/** Echoed timeframe. */
197+
timeframe: NuanzeLeaderboardTimeframe;
198+
/** Total username-claimed subaccounts matching the active filters. */
199+
totalCount: number;
200+
/** Ranked rows for this page. */
201+
items: NuanzeSubaccountLeaderboardItem[];
202+
/** Opaque cursor for the next page, or null when this is the final page. */
203+
nextCursor: string | null;
204+
/** When the response was generated, as a UTC ISO 8601 string. */
205+
asOf: string;
206+
}
207+
172208
/**
173209
* Params for `NuanzeClient.getFollowedLeaderboard`. `timeframe` is required and applies to
174210
* every per-subaccount figure.
175211
*/
176212
export interface GetNuanzeFollowedLeaderboardParams {
177-
/**
178-
* Followed subaccounts as bytes32 hex (owner + name). Mixed case is accepted and
179-
* normalized to lowercase. 1-300 items; the response preserves this order.
180-
*/
181-
subaccounts: string[];
213+
/** Username whose active follow graph should be ranked. */
214+
username: string;
182215
/** Ranking window for every per-subaccount figure. */
183216
timeframe: NuanzeLeaderboardTimeframe;
217+
/** Include followed subaccounts with no PnL in the requested window, default true. */
218+
includeUntraded?: boolean;
184219
}
185220

186221
/**
@@ -189,9 +224,9 @@ export interface GetNuanzeFollowedLeaderboardParams {
189224
export interface GetNuanzeFollowedLeaderboardResponse {
190225
/** Echoed timeframe. */
191226
timeframe: NuanzeLeaderboardTimeframe;
192-
/** One row per requested hex, in request order. */
227+
/** Actively followed subaccounts, sorted by PnL descending with nulls last. */
193228
items: NuanzeFollowedLeaderboardItem[];
194-
/** Number of rows returned. Equals `items.length` and the request length. */
229+
/** Number of rows returned. Equals `items.length`. */
195230
count: number;
196231
/** When the response was generated, as a UTC ISO 8601 string. */
197232
asOf: string;

packages/nuanze-client/src/types/nuanzeErrorCodes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const NUANZE_ERROR_CODES = [
1313
'MARKET_SELECTOR_MISMATCH',
1414
'MARKET_NOT_FOUND',
1515
'WALLET_NOT_FOUND',
16+
'USERNAME_NOT_FOUND',
1617
'UNSUPPORTED_INTERVAL',
1718
'UNSUPPORTED_BUCKET',
1819
'RANGE_TOO_LARGE',

packages/nuanze-client/src/types/serverModelTypes.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,13 +163,34 @@ export interface NuanzeServerLeaderboardItem {
163163
wins: number;
164164
losses: number;
165165
winRate: string | null;
166+
productIds: number[];
167+
productCount: number;
166168
}
167169

168170
/**
169171
* Followed-leaderboard row as returned on the wire.
170172
*/
171173
export interface NuanzeServerFollowedLeaderboardItem {
172174
subaccountHex: string;
175+
username: string | null;
176+
displayName: string | null;
177+
pnl: string | null;
178+
wins: number;
179+
losses: number;
180+
winRate: string | null;
181+
trades: number;
182+
productIds: number[];
183+
productCount: number;
184+
globalRank: number | null;
185+
}
186+
187+
/**
188+
* Public subaccount leaderboard row as returned on the wire.
189+
*/
190+
export interface NuanzeServerSubaccountLeaderboardItem {
191+
subaccountHex: string;
192+
username: string;
193+
displayName: string | null;
173194
pnl: string | null;
174195
wins: number;
175196
losses: number;

0 commit comments

Comments
 (0)