Skip to content

Commit a3ab279

Browse files
sktbrdclaude
andcommitted
fix(treasury): honest subnet and auction figures for the KPI cards
Runtime verification caught both: splits pay through the warehouse, so the split-address filter summed zero — subnet earnings now walk warehouse→treasury USDC transfers and keep the ones the final split emitted in, the ledger's own attribution rule. And the settled-auction count saturated at one subgraph page (Gnars is past 2000 auctions) — now cursor-paged by id. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 908a919 commit a3ab279

2 files changed

Lines changed: 50 additions & 22 deletions

File tree

src/services/dao.ts

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,26 +63,41 @@ export const fetchTotalAuctionSalesWei = cache(async (): Promise<bigint> => {
6363
});
6464

6565
const SETTLED_AUCTION_IDS_GQL = /* GraphQL */ `
66-
query SettledAuctionIds($dao: String!) {
67-
auctions(where: { dao: $dao, settled: true }, first: 1000) {
66+
query SettledAuctionIds($dao: String!, $after: ID!) {
67+
auctions(
68+
where: { dao: $dao, settled: true, id_gt: $after }
69+
first: 1000
70+
orderBy: id
71+
orderDirection: asc
72+
) {
6873
id
6974
}
7075
}
7176
`;
7277

7378
/**
74-
* Count of settled auctions. The subgraph pages at 1000, so this saturates
75-
* there — at the DAO's ~1/day cadence that is years away, and the KPI note
76-
* degrades to "1000 auctions settled", not a wrong number. `0` = count
77-
* unavailable; callers omit the note rather than claiming zero history.
79+
* Count of settled auctions. There is no aggregate on the DAO entity (and
80+
* `tokensCount` counts founder mints too), so this cursors through id-only
81+
* pages — ~8 requests for Gnars' ~7k auctions, refreshed at the page's ISR
82+
* cadence. The 20-page guard is a runaway stop, not an expected ceiling.
83+
* `0` = count unavailable; callers omit the note rather than claiming zero.
7884
*/
7985
export const fetchSettledAuctionCount = cache(async (): Promise<number> => {
8086
try {
81-
const data = await subgraphQuery<{ auctions?: Array<{ id: string }> }>(
82-
SETTLED_AUCTION_IDS_GQL,
83-
{ dao: DAO_ADDRESSES.token.toLowerCase() },
84-
);
85-
return data.auctions?.length ?? 0;
87+
const dao = DAO_ADDRESSES.token.toLowerCase();
88+
let count = 0;
89+
let after = "";
90+
for (let page = 0; page < 20; page += 1) {
91+
const data = await subgraphQuery<{ auctions?: Array<{ id: string }> }>(
92+
SETTLED_AUCTION_IDS_GQL,
93+
{ dao, after },
94+
);
95+
const ids = data.auctions ?? [];
96+
count += ids.length;
97+
if (ids.length < 1000) return count;
98+
after = ids[ids.length - 1].id;
99+
}
100+
return count;
86101
} catch {
87102
return 0;
88103
}

src/services/treasury-inflows.ts

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -291,17 +291,19 @@ export interface SubnetEarnings {
291291
}
292292

293293
/**
294-
* All-time Morpheus subnet earnings: every USDC transfer from the final split
295-
* to the treasury. The paged inflows feed above is a window and cannot sum
296-
* honestly; this asks Alchemy for exactly the split→treasury lane and walks
297-
* every page (the claim history is tiny). `null` = could not determine —
298-
* the KPI card renders a dash, never a fabricated 0.
294+
* All-time Morpheus subnet earnings. Splits pay through the warehouse (see the
295+
* SPLITS_WAREHOUSE note above — the split address is never the `from`), so
296+
* this walks every warehouse→treasury USDC transfer and keeps the ones whose
297+
* transaction the subnet's final split emitted in, the same attribution rule
298+
* the inflows ledger uses. The paged inflows feed above is a window and cannot
299+
* sum honestly; this lane's full history is tiny, and the per-tx receipts are
300+
* immutable and day-cached. `null` = could not determine — the KPI card
301+
* renders a dash, never a fabricated 0.
299302
*/
300303
export const loadSubnetEarnings = cache(async (): Promise<SubnetEarnings | null> => {
301304
if (!ALCHEMY_KEY) return null;
302305
try {
303-
let totalUsdc = 0;
304-
let claimCount = 0;
306+
const transfers: Array<{ value: number; hash: string }> = [];
305307
let pageKey: string | undefined;
306308
do {
307309
const res = await fetch(`https://base-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}`, {
@@ -313,7 +315,7 @@ export const loadSubnetEarnings = cache(async (): Promise<SubnetEarnings | null>
313315
method: "alchemy_getAssetTransfers",
314316
params: [
315317
{
316-
fromAddress: SUBNET_FINAL_SPLIT,
318+
fromAddress: SPLITS_WAREHOUSE,
317319
toAddress: DAO_ADDRESSES.treasury,
318320
contractAddresses: [USDC],
319321
category: ["erc20"],
@@ -327,16 +329,27 @@ export const loadSubnetEarnings = cache(async (): Promise<SubnetEarnings | null>
327329
});
328330
if (!res.ok) throw new Error(`Alchemy ${res.status}`);
329331
const json = (await res.json()) as {
330-
result?: { transfers?: Array<{ value: number | null }>; pageKey?: string };
332+
result?: {
333+
transfers?: Array<{ value: number | null; hash?: string }>;
334+
pageKey?: string;
335+
};
331336
error?: { message?: string };
332337
};
333338
if (json.error) throw new Error(json.error.message ?? "Alchemy error");
334339
for (const t of json.result?.transfers ?? []) {
335-
totalUsdc += t.value ?? 0;
336-
claimCount += 1;
340+
if (t.hash) transfers.push({ value: t.value ?? 0, hash: t.hash });
337341
}
338342
pageKey = json.result?.pageKey;
339343
} while (pageKey);
344+
345+
const sources = await Promise.all(transfers.map((t) => splitSourceForTx(t.hash)));
346+
let totalUsdc = 0;
347+
let claimCount = 0;
348+
for (let i = 0; i < transfers.length; i += 1) {
349+
if (sources[i] !== "subnet") continue;
350+
totalUsdc += transfers[i].value;
351+
claimCount += 1;
352+
}
340353
return { totalUsdc, claimCount };
341354
} catch {
342355
return null;

0 commit comments

Comments
 (0)