diff --git a/environment.d.ts b/environment.d.ts index 2c665f3..a0e1e00 100644 --- a/environment.d.ts +++ b/environment.d.ts @@ -15,6 +15,8 @@ interface EnvironmentVariables { readonly POAP_API_TOKEN: string readonly REDIS_URL: string readonly ENS_API_URL: string + readonly IPFS_GATEWAY_URL: string + readonly ARWEAVE_GATEWAY_URL: string } // Cloudflare Workers diff --git a/src/service/ens-metadata/service.ts b/src/service/ens-metadata/service.ts index 6be755f..bb69593 100644 --- a/src/service/ens-metadata/service.ts +++ b/src/service/ens-metadata/service.ts @@ -4,7 +4,7 @@ import { database } from '#/database' import { apiLogger } from '#/logger' import type { Address, DB } from '#/types' import type { Environment } from '#/types/index' -import { arrayToChunks, isAddress, raise } from '#/utilities.ts' +import { arrayToChunks, isAddress, raise, resolveDecentralizedURI } from '#/utilities.ts' import type { ENSProfile } from './types' export type ENSProfileResponse = ENSProfile & { type: 'error' | 'success' } @@ -29,11 +29,13 @@ type Row = { export class ENSMetadataService implements IENSMetadataService { readonly #db: Kysely readonly #url: string + readonly #gateways: { ipfs: string; arweave: string } // biome-ignore lint/correctness/noUndeclaredVariables: constructor(env: Env) { this.#db = database(env) this.#url = env.ENS_API_URL + this.#gateways = { ipfs: env.IPFS_GATEWAY_URL, arweave: env.ARWEAVE_GATEWAY_URL } } async getAddress(ensNameOrAddress: Address | string): Promise
{ @@ -118,7 +120,7 @@ export class ENSMetadataService implements IENSMetadataService { }) } catch (_error) {} - return ensProfileData as ENSProfile + return this.#resolveRecordURIs(ensProfileData) as ENSProfile } catch (error) { console.log('error', error) } @@ -194,7 +196,7 @@ export class ENSMetadataService implements IENSMetadataService { } catch (error) { console.log('cache failed', error) } - return ensProfileData as ENSProfile + return this.#resolveRecordURIs(ensProfileData) as ENSProfile } catch (error) { console.log('error', error) } @@ -206,7 +208,7 @@ export class ENSMetadataService implements IENSMetadataService { formattedSecondTry.records = formattedSecondTry?.records ? (JSON.parse(formattedSecondTry?.records) as string) : '' - return secondTry as ENSProfile + return this.#resolveRecordURIs(secondTry as ENSProfile) } return { @@ -221,7 +223,7 @@ export class ENSMetadataService implements IENSMetadataService { if (cachedProfile) { returnedRecord.records = returnedRecord?.records ? (JSON.parse(returnedRecord?.records) as string) : '' } - return returnedRecord as ENSProfile + return this.#resolveRecordURIs(returnedRecord) } /** @@ -289,6 +291,7 @@ export class ENSMetadataService implements IENSMetadataService { if (record.name) { await this.cacheRecord(record) record.records = JSON.parse(record?.records || '') as string + this.#resolveRecordURIs(record) } // record.records = JSON.parse(record?.records || '') as string; } @@ -296,6 +299,7 @@ export class ENSMetadataService implements IENSMetadataService { for (const record of filteredCache) { if (record.name) { record.records = JSON.parse(record?.records || '') as string + this.#resolveRecordURIs(record) } } @@ -334,6 +338,19 @@ export class ENSMetadataService implements IENSMetadataService { }, {}) } + #resolveRecordURIs(profile: ENSProfile): ENSProfile { + if (profile.records && typeof profile.records === 'object') { + const records = profile.records as unknown as Record + for (const key of Object.keys(records)) { + const value = records[key] + if (typeof value === 'string') { + records[key] = resolveDecentralizedURI(value, this.#gateways) + } + } + } + return profile + } + static #toTableRow(namedata: ENSProfile): { name: string address: string diff --git a/src/utilities.ts b/src/utilities.ts index 3a45ba8..00fe088 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -23,6 +23,22 @@ export function arrayToChunks(array: T[], chunkSize: number): T[][] { return chunks } +export function resolveDecentralizedURI( + uri: string | undefined | null, + gateways: { ipfs: string; arweave: string } +): string | undefined | null { + if (!uri) return uri + if (uri.startsWith('ipfs://')) { + const gateway = gateways.ipfs.endsWith('/') ? gateways.ipfs : `${gateways.ipfs}/` + return uri.replace('ipfs://', gateway) + } + if (uri.startsWith('ar://')) { + const gateway = gateways.arweave.endsWith('/') ? gateways.arweave : `${gateways.arweave}/` + return uri.replace('ar://', gateway) + } + return uri +} + // removed properties with undefined values from object export function removeUndefined(object: T): T { return JSON.parse(JSON.stringify(object)) as T diff --git a/wrangler.toml b/wrangler.toml index df14bc7..fffc84e 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -14,7 +14,7 @@ placement = { mode = "smart" } compatibility_date = "2023-10-30" # end of globally inheritable configuration # -vars = { ENVIRONMENT = "development" } +vars = { ENVIRONMENT = "development", IPFS_GATEWAY_URL = "https://ipfs.io/ipfs/", ARWEAVE_GATEWAY_URL = "https://arweave.net/" } services = [{ binding = "ens", service = "ens" }] kv_namespaces = [ { binding = "EFP_DATA_CACHE", id = "5092581c2d524711a04560d335966a60", preview_id = "608971607bd2469e8972a0811f8de589" },