diff --git a/tests/data-api.test.mjs b/tests/data-api.test.mjs index 3e8c60690..9d502f673 100644 --- a/tests/data-api.test.mjs +++ b/tests/data-api.test.mjs @@ -1173,6 +1173,92 @@ test("GET /api/v1/validators/:hotkey resolves cross-subnet validator detail", as expect(queryText()).toMatch(/validator_permit = TRUE/); }); +// #4832 Tier 2: the live-`neurons` routes with no shared D1 loader (the +// handler builds its own inline SELECT) or a loader this Worker mirrors +// directly, matching the established metagraph/validators pattern above. + +test("GET /api/v1/subnets/:netuid/concentration shapes the live stake/emission distribution", async () => { + mockRows.current = [NEURON_ROW]; + const res = await req("/api/v1/subnets/4/concentration"); + expect(res.status).toBe(200); + const body = await res.json(); + expect(body.netuid).toBe(4); + expect(body.stake).not.toBeNull(); + expect(queryText()).toContain("FROM neurons WHERE netuid ="); +}); + +test("GET /api/v1/subnets/:netuid/performance shapes the live reward-flow distribution", async () => { + mockRows.current = [NEURON_ROW]; + const res = await req("/api/v1/subnets/4/performance"); + expect(res.status).toBe(200); + const body = await res.json(); + expect(body.netuid).toBe(4); + expect(queryText()).toContain("FROM neurons WHERE netuid ="); +}); + +test("GET /api/v1/chain/concentration shapes the network-wide distribution across every subnet", async () => { + mockRows.current = [{ ...NEURON_ROW, netuid: 4 }]; + const res = await req("/api/v1/chain/concentration"); + expect(res.status).toBe(200); + const body = await res.json(); + expect(body.subnet_count).toBe(1); + expect(queryText()).toContain("FROM neurons"); + expect(queryText()).not.toContain("WHERE netuid"); +}); + +test("GET /api/v1/chain/performance shapes the network-wide reward-flow distribution", async () => { + mockRows.current = [{ ...NEURON_ROW, netuid: 4 }]; + const res = await req("/api/v1/chain/performance"); + expect(res.status).toBe(200); + const body = await res.json(); + expect(body.subnet_count).toBe(1); +}); + +test("GET /api/v1/chain/yield shapes the network-wide emission-yield distribution", async () => { + mockRows.current = [{ ...NEURON_ROW, netuid: 4 }]; + const res = await req("/api/v1/chain/yield"); + expect(res.status).toBe(200); + const body = await res.json(); + expect(body.subnet_count).toBe(1); +}); + +test("GET /api/v1/subnets/:netuid/yield shapes one subnet's emission-yield distribution", async () => { + mockRows.current = [NEURON_ROW]; + const res = await req("/api/v1/subnets/4/yield"); + expect(res.status).toBe(200); + const body = await res.json(); + expect(body.netuid).toBe(4); + expect(body.neurons[0].hotkey).toBe("5Hot"); + expect(queryText()).toContain("FROM neurons WHERE netuid ="); +}); + +test("GET /api/v1/accounts/:ss58/portfolio shapes one wallet's cross-subnet neuron portfolio", async () => { + mockRows.current = [{ ...NEURON_ROW, netuid: 4 }]; + const res = await req(`/api/v1/accounts/${SS58}/portfolio`); + expect(res.status).toBe(200); + const body = await res.json(); + expect(body.ss58).toBe(SS58); + expect(body.positions[0].netuid).toBe(4); + expect(queryText()).toContain("FROM neurons WHERE hotkey ="); +}); + +test("GET /api/v1/accounts returns the global accounts leaderboard", async () => { + mockRows.current = [{ ...NEURON_ROW, netuid: 4 }]; + const res = await req("/api/v1/accounts"); + expect(res.status).toBe(200); + const body = await res.json(); + expect(body.accounts[0].hotkey).toBe("5Hot"); + expect(queryText()).toContain("WHERE hotkey IS NOT NULL"); +}); + +test("GET /api/v1/accounts respects an explicit sort/limit", async () => { + mockRows.current = []; + const res = await req("/api/v1/accounts?sort=total_stake&limit=5"); + const body = await res.json(); + expect(body.sort).toBe("total_stake"); + expect(body.limit).toBe(5); +}); + // #4771: POST /api/v1/internal/neurons-sync -- the one write route in this // otherwise-read-only Worker (see workers/data-api.mjs's handleNeuronsSync). function neuronSyncRow(overrides = {}) { diff --git a/tests/request-handlers-entities.test.mjs b/tests/request-handlers-entities.test.mjs index a65fbfa0a..898542904 100644 --- a/tests/request-handlers-entities.test.mjs +++ b/tests/request-handlers-entities.test.mjs @@ -35,6 +35,11 @@ import { handleSubnetHyperparamsHistory, handleSubnetConcentration, handleSubnetPerformance, + handleChainConcentration, + handleChainPerformance, + handleChainYield, + handleAccountPortfolio, + handleAccountsList, handleSubnetConcentrationHistory, handleSubnetPerformanceHistory, handleSubnetYieldHistory, @@ -7833,6 +7838,308 @@ describe("D1 -> Postgres serving-cutover flag (#4656 followup)", () => { assert.equal(body.data.marker, undefined); assert.ok(captures.sql.length > 0); }); + + // #4832 Tier 2a: the 8 flat-`neurons` handlers (concentration, performance, + // yield, portfolio, accounts list) across the subnet/chain/account scopes. + + test("handleSubnetConcentration: flag=postgres uses Postgres data, D1 never queried", async () => { + const { env, captures } = dbWith({ neurons: [neuronRow()] }); + env.METAGRAPH_NEURONS_SOURCE = "postgres"; + env.DATA_API = { + fetch: async () => + Response.json({ schema_version: 1, marker: "pg", netuid: NETUID }), + }; + const body = await json( + await handleSubnetConcentration( + req(`/api/v1/subnets/${NETUID}/concentration`), + env, + NETUID, + url(`/api/v1/subnets/${NETUID}/concentration`), + ), + ); + assert.equal(body.data.marker, "pg"); + assert.deepEqual(captures.sql, []); + }); + + test("handleSubnetConcentration: flag=postgres falls back to D1 on failure", async () => { + const { env, captures } = dbWith({ neurons: [neuronRow()] }); + env.METAGRAPH_NEURONS_SOURCE = "postgres"; + env.DATA_API = { + fetch: async () => { + throw new Error("boom"); + }, + }; + const body = await json( + await handleSubnetConcentration( + req(`/api/v1/subnets/${NETUID}/concentration`), + env, + NETUID, + url(`/api/v1/subnets/${NETUID}/concentration`), + ), + ); + assert.equal(body.data.marker, undefined); + assert.ok(captures.sql.length > 0); + }); + + test("handleSubnetPerformance: flag=postgres uses Postgres data, D1 never queried", async () => { + const { env, captures } = dbWith({ neurons: [neuronRow()] }); + env.METAGRAPH_NEURONS_SOURCE = "postgres"; + env.DATA_API = { + fetch: async () => + Response.json({ schema_version: 1, marker: "pg", netuid: NETUID }), + }; + const body = await json( + await handleSubnetPerformance( + req(`/api/v1/subnets/${NETUID}/performance`), + env, + NETUID, + url(`/api/v1/subnets/${NETUID}/performance`), + ), + ); + assert.equal(body.data.marker, "pg"); + assert.deepEqual(captures.sql, []); + }); + + test("handleSubnetPerformance: flag=postgres falls back to D1 on failure", async () => { + const { env, captures } = dbWith({ neurons: [neuronRow()] }); + env.METAGRAPH_NEURONS_SOURCE = "postgres"; + env.DATA_API = { + fetch: async () => { + throw new Error("boom"); + }, + }; + const body = await json( + await handleSubnetPerformance( + req(`/api/v1/subnets/${NETUID}/performance`), + env, + NETUID, + url(`/api/v1/subnets/${NETUID}/performance`), + ), + ); + assert.equal(body.data.marker, undefined); + assert.ok(captures.sql.length > 0); + }); + + test("handleSubnetYield: flag=postgres uses Postgres data, D1 never queried", async () => { + const { env, captures } = dbWith({ neurons: [neuronRow()] }); + env.METAGRAPH_NEURONS_SOURCE = "postgres"; + env.DATA_API = { + fetch: async () => + Response.json({ schema_version: 1, marker: "pg", neurons: [] }), + }; + const body = await json( + await handleSubnetYield( + req(`/api/v1/subnets/${NETUID}/yield`), + env, + NETUID, + url(`/api/v1/subnets/${NETUID}/yield`), + ), + ); + assert.equal(body.data.marker, "pg"); + assert.deepEqual(captures.sql, []); + }); + + test("handleSubnetYield: flag=postgres falls back to D1 on failure", async () => { + const { env, captures } = dbWith({ neurons: [neuronRow()] }); + env.METAGRAPH_NEURONS_SOURCE = "postgres"; + env.DATA_API = { + fetch: async () => { + throw new Error("boom"); + }, + }; + const body = await json( + await handleSubnetYield( + req(`/api/v1/subnets/${NETUID}/yield`), + env, + NETUID, + url(`/api/v1/subnets/${NETUID}/yield`), + ), + ); + assert.equal(body.data.marker, undefined); + assert.ok(captures.sql.length > 0); + }); + + test("handleChainConcentration: flag=postgres uses Postgres data, D1 never queried", async () => { + const { env, captures } = dbWith({ neurons: [neuronRow()] }); + env.METAGRAPH_NEURONS_SOURCE = "postgres"; + env.DATA_API = { + fetch: async () => Response.json({ schema_version: 1, marker: "pg" }), + }; + const body = await json( + await handleChainConcentration( + req("/api/v1/chain/concentration"), + env, + url("/api/v1/chain/concentration"), + ), + ); + assert.equal(body.data.marker, "pg"); + assert.deepEqual(captures.sql, []); + }); + + test("handleChainConcentration: flag=postgres falls back to D1 on failure", async () => { + const { env, captures } = dbWith({ neurons: [neuronRow()] }); + env.METAGRAPH_NEURONS_SOURCE = "postgres"; + env.DATA_API = { + fetch: async () => { + throw new Error("boom"); + }, + }; + const body = await json( + await handleChainConcentration( + req("/api/v1/chain/concentration"), + env, + url("/api/v1/chain/concentration"), + ), + ); + assert.equal(body.data.marker, undefined); + assert.ok(captures.sql.length > 0); + }); + + test("handleChainPerformance: flag=postgres uses Postgres data, D1 never queried", async () => { + const { env, captures } = dbWith({ neurons: [neuronRow()] }); + env.METAGRAPH_NEURONS_SOURCE = "postgres"; + env.DATA_API = { + fetch: async () => Response.json({ schema_version: 1, marker: "pg" }), + }; + const body = await json( + await handleChainPerformance( + req("/api/v1/chain/performance"), + env, + url("/api/v1/chain/performance"), + ), + ); + assert.equal(body.data.marker, "pg"); + assert.deepEqual(captures.sql, []); + }); + + test("handleChainPerformance: flag=postgres falls back to D1 on failure", async () => { + const { env, captures } = dbWith({ neurons: [neuronRow()] }); + env.METAGRAPH_NEURONS_SOURCE = "postgres"; + env.DATA_API = { + fetch: async () => { + throw new Error("boom"); + }, + }; + const body = await json( + await handleChainPerformance( + req("/api/v1/chain/performance"), + env, + url("/api/v1/chain/performance"), + ), + ); + assert.equal(body.data.marker, undefined); + assert.ok(captures.sql.length > 0); + }); + + test("handleChainYield: flag=postgres uses Postgres data, D1 never queried", async () => { + const { env, captures } = dbWith({ neurons: [neuronRow()] }); + env.METAGRAPH_NEURONS_SOURCE = "postgres"; + env.DATA_API = { + fetch: async () => Response.json({ schema_version: 1, marker: "pg" }), + }; + const body = await json( + await handleChainYield( + req("/api/v1/chain/yield"), + env, + url("/api/v1/chain/yield"), + ), + ); + assert.equal(body.data.marker, "pg"); + assert.deepEqual(captures.sql, []); + }); + + test("handleChainYield: flag=postgres falls back to D1 on failure", async () => { + const { env, captures } = dbWith({ neurons: [neuronRow()] }); + env.METAGRAPH_NEURONS_SOURCE = "postgres"; + env.DATA_API = { + fetch: async () => { + throw new Error("boom"); + }, + }; + const body = await json( + await handleChainYield( + req("/api/v1/chain/yield"), + env, + url("/api/v1/chain/yield"), + ), + ); + assert.equal(body.data.marker, undefined); + assert.ok(captures.sql.length > 0); + }); + + test("handleAccountPortfolio: flag=postgres uses Postgres data, D1 never queried", async () => { + const { env, captures } = dbWith({ neurons: [neuronRow()] }); + env.METAGRAPH_NEURONS_SOURCE = "postgres"; + env.DATA_API = { + fetch: async () => + Response.json({ schema_version: 1, marker: "pg", positions: [] }), + }; + const body = await json( + await handleAccountPortfolio( + req(`/api/v1/accounts/${SS58}/portfolio`), + env, + SS58, + ), + ); + assert.equal(body.data.marker, "pg"); + assert.deepEqual(captures.sql, []); + }); + + test("handleAccountPortfolio: flag=postgres falls back to D1 on failure", async () => { + const { env, captures } = dbWith({ neurons: [neuronRow()] }); + env.METAGRAPH_NEURONS_SOURCE = "postgres"; + env.DATA_API = { + fetch: async () => { + throw new Error("boom"); + }, + }; + const body = await json( + await handleAccountPortfolio( + req(`/api/v1/accounts/${SS58}/portfolio`), + env, + SS58, + ), + ); + assert.equal(body.data.marker, undefined); + assert.ok(captures.sql.length > 0); + }); + + test("handleAccountsList: flag=postgres uses Postgres data, D1 never queried", async () => { + const { env, captures } = dbWith({ neurons: [neuronRow()] }); + env.METAGRAPH_NEURONS_SOURCE = "postgres"; + env.DATA_API = { + fetch: async () => + Response.json({ schema_version: 1, marker: "pg", accounts: [] }), + }; + const body = await json( + await handleAccountsList( + req("/api/v1/accounts"), + env, + url("/api/v1/accounts"), + ), + ); + assert.equal(body.data.marker, "pg"); + assert.deepEqual(captures.sql, []); + }); + + test("handleAccountsList: flag=postgres falls back to D1 on failure", async () => { + const { env, captures } = dbWith({ neurons: [neuronRow()] }); + env.METAGRAPH_NEURONS_SOURCE = "postgres"; + env.DATA_API = { + fetch: async () => { + throw new Error("boom"); + }, + }; + const body = await json( + await handleAccountsList( + req("/api/v1/accounts"), + env, + url("/api/v1/accounts"), + ), + ); + assert.equal(body.data.marker, undefined); + assert.ok(captures.sql.length > 0); + }); }); // ---- Cross-handler contract smoke tests ------------------------------------- diff --git a/workers/data-api.mjs b/workers/data-api.mjs index 5e2665330..69e723241 100644 --- a/workers/data-api.mjs +++ b/workers/data-api.mjs @@ -49,6 +49,20 @@ import { BLOCKS_SUMMARY_SCAN_CAP, } from "../src/blocks-summary.mjs"; import { buildRuntimeVersionHistory } from "../src/runtime-versions.mjs"; +import { + buildConcentration, + buildChainConcentration, +} from "../src/concentration.mjs"; +import { buildSubnetPerformance } from "../src/subnet-performance.mjs"; +import { buildChainPerformance } from "../src/chain-performance.mjs"; +import { buildChainYield } from "../src/chain-yield.mjs"; +import { buildSubnetYield } from "../src/subnet-yield.mjs"; +import { buildAccountPortfolio } from "../src/account-portfolio.mjs"; +import { + buildAccountsList, + DEFAULT_ACCOUNTS_LIST_SORT, + ACCOUNTS_LIST_LIMIT_DEFAULT, +} from "../src/accounts-list.mjs"; import { decodeChainEventArgs } from "../src/chain-event-args.mjs"; import { buildValidatorNominators, @@ -2124,6 +2138,116 @@ export default { return json(buildValidatorDetail(rows, hotkey)); } + // GET /api/v1/subnets/:netuid/concentration (#4832 Tier 2): stake & + // emission decentralization for one subnet, mirroring + // src/concentration.mjs's the handler's own inline query (no shared + // loader -- this is one of the live-`neurons` routes, distinct from + // the neuron_daily-derived /concentration/history below). + const subnetConcentration = url.pathname.match( + /^\/api\/v1\/subnets\/(\d+)\/concentration$/, + ); + if (subnetConcentration) { + const netuid = Number(subnetConcentration[1]); + const rows = await sql` + SELECT stake_tao, emission_tao, coldkey, validator_permit, captured_at + FROM neurons WHERE netuid = ${netuid}`; + return json(buildConcentration(rows, netuid)); + } + + // GET /api/v1/subnets/:netuid/performance (#4832 Tier 2): reward-flow & + // trust-spread for one subnet, mirroring the handler's own inline query. + const subnetPerformance = url.pathname.match( + /^\/api\/v1\/subnets\/(\d+)\/performance$/, + ); + if (subnetPerformance) { + const netuid = Number(subnetPerformance[1]); + const rows = await sql` + SELECT incentive, dividends, trust, consensus, validator_trust, active, validator_permit, captured_at + FROM neurons WHERE netuid = ${netuid}`; + return json(buildSubnetPerformance(rows, netuid)); + } + + // GET /api/v1/chain/concentration (#4832 Tier 2): network-wide stake & + // emission decentralization across every subnet's neurons, mirroring + // src/concentration.mjs's loadChainConcentration. + if (url.pathname === "/api/v1/chain/concentration") { + const rows = await sql` + SELECT stake_tao, emission_tao, coldkey, validator_permit, netuid, captured_at + FROM neurons`; + return json(buildChainConcentration(rows)); + } + + // GET /api/v1/chain/performance (#4832 Tier 2): network-wide reward-flow + // & trust-spread, mirroring src/chain-performance.mjs's loadChainPerformance. + if (url.pathname === "/api/v1/chain/performance") { + const rows = await sql` + SELECT incentive, dividends, trust, consensus, validator_trust, active, validator_permit, netuid, captured_at + FROM neurons`; + return json(buildChainPerformance(rows)); + } + + // GET /api/v1/chain/yield (#4832 Tier 2): network-wide emission-yield + // distribution, mirroring src/chain-yield.mjs's loadChainYield. + if (url.pathname === "/api/v1/chain/yield") { + const rows = await sql` + SELECT validator_permit, stake_tao, emission_tao, netuid, captured_at + FROM neurons`; + return json(buildChainYield(rows)); + } + + // GET /api/v1/subnets/:netuid/yield (#4832 Tier 2): one subnet's + // emission-yield distribution, mirroring src/subnet-yield.mjs's + // loadSubnetYield. + const subnetYield = url.pathname.match( + /^\/api\/v1\/subnets\/(\d+)\/yield$/, + ); + if (subnetYield) { + const netuid = Number(subnetYield[1]); + const rows = await sql` + SELECT uid, hotkey, validator_permit, stake_tao, emission_tao, captured_at, block_number + FROM neurons WHERE netuid = ${netuid} ORDER BY uid`; + return json(buildSubnetYield(rows, netuid)); + } + + // GET /api/v1/accounts/:ss58/portfolio (#4832 Tier 2): one wallet's + // cross-subnet neuron portfolio, mirroring + // src/account-portfolio.mjs's loadAccountPortfolio. + const acctPortfolio = url.pathname.match( + /^\/api\/v1\/accounts\/([^/]+)\/portfolio$/, + ); + if (acctPortfolio) { + const ss58 = decodeURIComponent(acctPortfolio[1]); + const rows = await sql` + SELECT netuid, uid, stake_tao, emission_tao, rank, trust, incentive, dividends, validator_permit, active, captured_at + FROM neurons WHERE hotkey = ${ss58} ORDER BY netuid`; + return json(buildAccountPortfolio(rows, ss58)); + } + + // GET /api/v1/accounts?sort=&limit= (#4832 Tier 2): the global accounts + // leaderboard, mirroring src/accounts-list.mjs's loadAccountsList. + if (url.pathname === "/api/v1/accounts") { + const sortParam = url.searchParams.get("sort") || undefined; + // Number(null) is 0 (finite), not NaN -- an absent ?limit= must not + // silently clamp to a zero-row page. Only a genuinely PRESENT value + // reaches Number(); an absent/blank one falls back to the default, + // matching parseBoundedIntParam's contract (entities.mjs's D1 path). + const limitRaw = url.searchParams.get("limit"); + const limit = + limitRaw == null || limitRaw === "" + ? ACCOUNTS_LIST_LIMIT_DEFAULT + : Number(limitRaw); + const rows = await sql` + SELECT netuid, uid, hotkey, coldkey, validator_permit, emission_tao, stake_tao, block_number, captured_at + FROM neurons WHERE hotkey IS NOT NULL + ORDER BY hotkey ASC, stake_tao DESC, netuid ASC, uid ASC`; + return json( + buildAccountsList(rows, { + sort: sortParam ?? DEFAULT_ACCOUNTS_LIST_SORT, + limit, + }), + ); + } + return json({ error: "not found" }, 404); }); } catch (err) { diff --git a/workers/request-handlers/entities.mjs b/workers/request-handlers/entities.mjs index e86d43652..6e70ca9bb 100644 --- a/workers/request-handlers/entities.mjs +++ b/workers/request-handlers/entities.mjs @@ -546,7 +546,9 @@ export async function handleSubnetMetagraph(request, env, netuid, url) { export async function handleSubnetYield(request, env, netuid, url) { const validationError = validateEntityQuery(url, ["format"]); if (validationError) return analyticsQueryError(validationError); - const data = await loadSubnetYield(d1Runner(env), netuid); + const data = + (await tryPostgresTier(env, request, "METAGRAPH_NEURONS_SOURCE")) ?? + (await loadSubnetYield(d1Runner(env), netuid)); if (csvRequested(url, request)) { return csvResponse( data.neurons, @@ -810,10 +812,12 @@ export function canonicalAccountsListCachePath(url, request = null) { export async function handleAccountsList(request, env, url) { const parsed = parseAccountsListQuery(url); if (parsed.error) return analyticsQueryError(parsed.error); - const data = await loadAccountsList(d1Runner(env), { - sort: parsed.sort, - limit: parsed.limit, - }); + const data = + (await tryPostgresTier(env, request, "METAGRAPH_NEURONS_SOURCE")) ?? + (await loadAccountsList(d1Runner(env), { + sort: parsed.sort, + limit: parsed.limit, + })); if (csvRequested(url, request)) { return csvResponse( data.accounts, @@ -1098,12 +1102,16 @@ export async function handleSubnetIdentityHistory(request, env, netuid, url) { export async function handleSubnetConcentration(request, env, netuid, url) { const validationError = validateQueryParams(url, []); if (validationError) return analyticsQueryError(validationError); - const rows = await d1All( - env, - `SELECT ${CONCENTRATION_READ_COLUMNS} FROM neurons WHERE netuid = ?`, - [netuid], - ); - const data = buildConcentration(rows, netuid); + const data = + (await tryPostgresTier(env, request, "METAGRAPH_NEURONS_SOURCE")) ?? + buildConcentration( + await d1All( + env, + `SELECT ${CONCENTRATION_READ_COLUMNS} FROM neurons WHERE netuid = ?`, + [netuid], + ), + netuid, + ); return envelopeResponse( request, { @@ -1128,12 +1136,16 @@ export async function handleSubnetConcentration(request, env, netuid, url) { export async function handleSubnetPerformance(request, env, netuid, url) { const validationError = validateQueryParams(url, []); if (validationError) return analyticsQueryError(validationError); - const rows = await d1All( - env, - `SELECT ${PERFORMANCE_READ_COLUMNS} FROM neurons WHERE netuid = ?`, - [netuid], - ); - const data = buildSubnetPerformance(rows, netuid); + const data = + (await tryPostgresTier(env, request, "METAGRAPH_NEURONS_SOURCE")) ?? + buildSubnetPerformance( + await d1All( + env, + `SELECT ${PERFORMANCE_READ_COLUMNS} FROM neurons WHERE netuid = ?`, + [netuid], + ), + netuid, + ); return envelopeResponse( request, { @@ -1156,7 +1168,9 @@ export async function handleSubnetPerformance(request, env, netuid, url) { export async function handleChainConcentration(request, env, url) { const validationError = validateQueryParams(url, []); if (validationError) return analyticsQueryError(validationError); - const data = await loadChainConcentration(d1Runner(env)); + const data = + (await tryPostgresTier(env, request, "METAGRAPH_NEURONS_SOURCE")) ?? + (await loadChainConcentration(d1Runner(env))); return envelopeResponse( request, { @@ -1180,7 +1194,9 @@ export async function handleChainConcentration(request, env, url) { export async function handleChainPerformance(request, env, url) { const validationError = validateQueryParams(url, []); if (validationError) return analyticsQueryError(validationError); - const data = await loadChainPerformance(d1Runner(env)); + const data = + (await tryPostgresTier(env, request, "METAGRAPH_NEURONS_SOURCE")) ?? + (await loadChainPerformance(d1Runner(env))); return envelopeResponse( request, { @@ -1235,7 +1251,9 @@ export async function handleChainIdentityHistory(request, env, url) { export async function handleChainYield(request, env, url) { const validationError = validateQueryParams(url, []); if (validationError) return analyticsQueryError(validationError); - const data = await loadChainYield(d1Runner(env)); + const data = + (await tryPostgresTier(env, request, "METAGRAPH_NEURONS_SOURCE")) ?? + (await loadChainYield(d1Runner(env))); return envelopeResponse( request, { @@ -3120,7 +3138,9 @@ export async function handleAccountSubnets(request, env, ss58) { // (totals, counts, overall return, stake concentration), from the neurons D1 // tier. Richer than /subnets (registration footprint only). Cold/absent → empty. export async function handleAccountPortfolio(request, env, ss58) { - const data = await loadAccountPortfolio(d1Runner(env), ss58); + const data = + (await tryPostgresTier(env, request, "METAGRAPH_NEURONS_SOURCE")) ?? + (await loadAccountPortfolio(d1Runner(env), ss58)); return accountEnvelopeResponse( request, {