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
9 changes: 8 additions & 1 deletion apps/ui/scripts/render-og-preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import fs from "node:fs";
import path from "node:path";
import { Resvg } from "@resvg/resvg-js";
import type { ReactNode } from "react";
import satori from "satori";
import { html } from "satori-html";
import { glyphsForMarkup, renderCardMarkup } from "../src/lib/og-image.ts";
Expand Down Expand Up @@ -158,7 +159,13 @@ for (const [name, variant] of Object.entries(VARIANTS)) {
loadFont("Inter", 400, glyphs),
]);

const svg = await satori(html(markup) as never, {
// satori-html returns satori's own `VNode`; satori's published signature says
// `ReactNode` because React is its reference renderer. Same runtime object,
// two packages that do not declare each other -- stated once, narrowly.
// `as ReactNode` and not `as never`: the latter also accepted `undefined`,
// which is what a renderCardMarkup returning nothing would have handed over.
// Mirrors scripts/refresh-og-image.ts, which renders the same card in CI.
const svg = await satori(html(markup) as ReactNode, {
width: 1200,
height: 630,
fonts: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { classNames } from "@/lib/metagraphed/format";
import { TimeAgo } from "@jsonbored/ui-kit";
import { StateBlock } from "@/components/metagraphed/states/state-block";
import { Panel } from "@/components/metagraphed/primitives";
import { readNumber } from "@/lib/metagraphed/read-key";

interface Props {
schemas: SchemaInfo[];
Expand Down Expand Up @@ -304,10 +305,9 @@ function weight(s: SchemaInfo): number {
}

function numericField(s: SchemaInfo, keys: string[]): number | null {
const rec = s as unknown as Record<string, unknown>;
for (const k of keys) {
const v = rec[k];
if (typeof v === "number" && Number.isFinite(v)) return v;
const v = readNumber(s, k);
if (v !== undefined) return v;
}
return null;
}
Expand Down
11 changes: 6 additions & 5 deletions apps/ui/src/components/metagraphed/endpoint-detail-drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,12 @@ export function EndpointDetailDrawer({
// Filter: state (down/warn/other), and pool membership (this endpoint's pool).
const [stateFilter, setStateFilter] = useState<"all" | "down" | "warn" | "other">("all");
const [poolOnly, setPoolOnly] = useState(false);
const endpointPoolId = String(
(endpoint as unknown as { pool_id?: string; pool?: string }).pool_id ??
(endpoint as unknown as { pool_id?: string; pool?: string }).pool ??
"",
);
// No assertion needed for either: `pool` is on the Endpoint contract, and
// `pool_id` -- which is not -- arrives through its index signature as
// `unknown`, which `String()` accepts. The old cast restated both as
// optional strings, so it was claiming a contract field that does not exist
// rather than reading one that might.
const endpointPoolId = String(endpoint.pool_id ?? endpoint.pool ?? "");

const rows = useMemo(() => {
let list = allRows;
Expand Down
9 changes: 3 additions & 6 deletions apps/ui/src/components/metagraphed/hero-feature-row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import { formatNumber } from "@/lib/metagraphed/format";
import type { ChainActivity, Subnet } from "@/lib/metagraphed/types";
import { useHydrated } from "@/hooks/use-hydrated";
import { readNumber, readString } from "@/lib/metagraphed/read-key";

/**
* The UTC calendar-day string (YYYY-MM-DD) "today" means for this card,
Expand Down Expand Up @@ -292,14 +293,10 @@ function pickFeatured(subnets: Subnet[], n: number): Subnet[] {
// participant count as a rough popularity proxy. Skip root (netuid 0).
const app = subnets.filter((s) => s.netuid > 0);
const score = (s: Subnet) => {
const c = (s as unknown as { curation?: string }).curation ?? "";
const c = readString(s, "curation") ?? "";
const curationRank =
c === "adapter" ? 4 : c === "native" ? 3 : c === "verified" ? 3 : c === "pilot" ? 2 : 1;
const size = Number(
(s as unknown as { participants?: number }).participants ??
(s as unknown as { neuron_count?: number }).neuron_count ??
0,
);
const size = Number(readNumber(s, "participants") ?? readNumber(s, "neuron_count") ?? 0);
return curationRank * 1e6 + size;
};
return [...app].sort((a, b) => score(b) - score(a)).slice(0, n);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import { networkParametersQuery } from "@/lib/metagraphed/queries";
import { formatNumber, formatTao } from "@/lib/metagraphed/format";
import type { NetworkParameters } from "@/lib/metagraphed/types";
import { readKey } from "@/lib/metagraphed/read-key";

type ParameterKind = "percent" | "tao" | "count" | "raw";

Expand Down Expand Up @@ -66,7 +67,6 @@ export interface ParameterGroup {
}

export function buildParameterGroups(parameters: NetworkParameters): ParameterGroup[] {
const raw = parameters as unknown as Record<string, unknown>;
const consumed = new Set<string>(["queried_at"]);
const groups: ParameterGroup[] = PARAMETER_GROUPS.map((group) => ({
label: group.label,
Expand All @@ -78,20 +78,20 @@ export function buildParameterGroups(parameters: NetworkParameters): ParameterGr
label: meta.label,
hint: meta.hint,
kind: meta.kind,
value: raw[key] ?? null,
value: readKey(parameters, key) ?? null,
};
}),
}));

const leftoverKeys = Object.keys(raw).filter((key) => !consumed.has(key));
const leftoverKeys = Object.keys(parameters).filter((key) => !consumed.has(key));
if (leftoverKeys.length > 0) {
groups.push({
label: "Other",
rows: leftoverKeys.map((key) => ({
key,
label: key,
kind: "raw" as const,
value: raw[key] ?? null,
value: readKey(parameters, key) ?? null,
})),
});
}
Expand Down
5 changes: 2 additions & 3 deletions apps/ui/src/components/metagraphed/resource-explorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
type Severity,
} from "@/components/metagraphed/subnet-filter-context";
import type { Endpoint, RpcPool, Surface } from "@/lib/metagraphed/types";
import { readString } from "@/lib/metagraphed/read-key";

type Seg = "endpoints" | "surfaces" | "schemas";

Expand Down Expand Up @@ -529,9 +530,7 @@ function SurfacesView({
}
const rows = filter.isAll
? allRows
: allRows.filter((s) =>
filter.isActive(((s as unknown as { health?: string }).health ?? "unknown") as Severity),
);
: allRows.filter((s) => filter.isActive((readString(s, "health") ?? "unknown") as Severity));
const hidden = allRows.length - rows.length;
if (allRows.length === 0) {
return (
Expand Down
20 changes: 9 additions & 11 deletions apps/ui/src/components/metagraphed/schema-drift-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { SchemaSnapshotSummary } from "@/components/metagraphed/schema-snapshot-
import { useCopy } from "@/hooks/use-copy";
import { formatFreshness, formatFreshnessAbsolute } from "@/lib/metagraphed/freshness";
import type { SchemaInfo } from "@/lib/metagraphed/types";
import { readKey, readString } from "@/lib/metagraphed/read-key";

interface Props {
schema: SchemaInfo | null;
Expand Down Expand Up @@ -156,23 +157,20 @@ function EvidenceSection({
copied: boolean;
onCopy: (v: string) => void;
}) {
const rec = schema as unknown as Record<string, unknown>;
const links: Array<{ label: string; href: string }> = [];
for (const key of ["url", "snapshot_url", "prev_snapshot_url", "artifact_path"]) {
const v = rec[key];
if (typeof v === "string" && v.length > 0) {
const v = readString(schema, key);
if (v !== undefined && v.length > 0) {
links.push({ label: key.replace(/_/g, " "), href: v });
}
}
const evidence = rec.evidence;
const evidence = readKey(schema, "evidence");
if (Array.isArray(evidence)) {
for (const e of evidence) {
const u = (e as Record<string, unknown>)?.url;
if (typeof u === "string" && u.startsWith("http")) {
links.push({
label: String((e as Record<string, unknown>)?.source ?? "evidence"),
href: u,
});
for (const entry of evidence) {
if (typeof entry !== "object" || entry === null) continue;
const url = readString(entry, "url");
if (url?.startsWith("http")) {
links.push({ label: readString(entry, "source") ?? "evidence", href: url });
}
}
}
Expand Down
36 changes: 36 additions & 0 deletions apps/ui/src/lib/metagraphed/chain-connection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
buildExtrinsic,
getNextNonce,
getCurrentBlock,
getFreeBalance,
getMaxDelegateTake,
getMinDelegateTake,
getTxDelegateTakeRateLimit,
Expand Down Expand Up @@ -197,6 +198,41 @@ describe("live delegate-take bounds/rate-limit queries", () => {
return { api, delegates, lastRateLimitedBlock };
}

// These three pin what the old `api as unknown as SubtensorQueryApi` could
// not: the assertion named five storage entries and claimed `.toNumber()` on
// every result, so a renamed entry or a retyped codec compiled fine and blew
// up as a bare TypeError inside a wallet flow -- after the user had already
// signed off on an amount.

it("names the entry when the pallet no longer has it, instead of TypeError", async () => {
const api = { query: { subtensorModule: {} } } as unknown as ApiPromise;
await expect(getMaxDelegateTake(api)).rejects.toThrow(/maxDelegateTake is not a storage entry/);
});

it("names the entry when its codec stops being numeric", async () => {
// A u16 that became a struct, an Option, a Bytes -- anything without
// toNumber(). The old code called it regardless.
const api = {
query: {
subtensorModule: {
minDelegateTake: vi.fn(async () => ({ toHuman: () => "0" })),
},
},
} as unknown as ApiPromise;
await expect(getMinDelegateTake(api)).rejects.toThrow(
/minDelegateTake returned a codec with no toNumber/,
);
});

it("rejects a system.account with no free balance rather than reading undefined", async () => {
const api = {
query: { system: { account: vi.fn(async () => ({ nonce: 1 })) } },
} as unknown as ApiPromise;
await expect(getFreeBalance(api, "5Coldkey")).rejects.toThrow(
/AccountInfo with a free balance/,
);
});

it("getMaxDelegateTake returns the live-confirmed 18% bound (11796 parts)", async () => {
const { api } = makeQueryApi({ maxDelegateTake: 11_796 });
await expect(getMaxDelegateTake(api)).resolves.toBe(11_796);
Expand Down
Loading
Loading