From c686dd1ceecebd9bf3ebfc619bd201ce02e3487f Mon Sep 17 00:00:00 2001 From: Vickler Charles Date: Thu, 8 Jan 2026 14:15:43 +0100 Subject: [PATCH] feat: add direct Base L2 support for Basenames (*.base.eth) This adds native support for resolving contenthash records from Basenames (*.base.eth) by querying the Base L2 resolver directly, bypassing the Coinbase CCIP gateway which doesn't return contenthash. Changes: - Add BasenamesService to query Base L2 resolver at 0xC6d566A56A1aFf6508b41f6c90ff131615583BCD - Add IConfigurationBase interface for Base RPC configuration - Update NameServiceFactory to route *.base.eth to BasenamesService - Add BASE_RPC_ENDPOINT environment variable (defaults to https://mainnet.base.org) --- .../src/nameservice/BasenamesService.ts | 97 +++++++++++++++++++ .../src/nameservice/index.ts | 10 ++ .../src/configuration/index.ts | 24 +++++ .../src/dependencies/services.ts | 10 ++ packages/dweb-api-types/src/config.ts | 6 ++ 5 files changed, 147 insertions(+) create mode 100644 packages/dweb-api-resolver/src/nameservice/BasenamesService.ts diff --git a/packages/dweb-api-resolver/src/nameservice/BasenamesService.ts b/packages/dweb-api-resolver/src/nameservice/BasenamesService.ts new file mode 100644 index 0000000..30ad8b4 --- /dev/null +++ b/packages/dweb-api-resolver/src/nameservice/BasenamesService.ts @@ -0,0 +1,97 @@ +import { JsonRpcProvider } from "ethers"; +import { ILoggerService } from "dweb-api-types/dist/logger"; +import { IRequestContext } from "dweb-api-types/dist/request-context"; +import { INameService } from "dweb-api-types/dist/name-service"; +import { IConfigurationBase } from "dweb-api-types/dist/config"; +import { getContentHashFallback } from "./utils.js"; +import { namehash } from "ethers"; + +const L2_RESOLVER_ADDRESS = "0xC6d566A56A1aFf6508b41f6c90ff131615583BCD"; +const L2_RESOLVER_ABI = [ + "function contenthash(bytes32 node) view returns (bytes)", +]; + +export class BasenamesService implements INameService { + _configurationService: IConfigurationBase; + provider: JsonRpcProvider; + _logger: ILoggerService; + + constructor( + configurationService: IConfigurationBase, + logger: ILoggerService, + ) { + this._configurationService = configurationService; + const baseConfig = this._configurationService.getConfigBaseBackend(); + const rpc = baseConfig.getBackend(); + + this.provider = new JsonRpcProvider(rpc, undefined, { + staticNetwork: true, + }); + this._logger = logger; + } + + async getContentHash( + request: IRequestContext, + name: string, + ): Promise { + this._logger.debug("BasenamesService: resolving contenthash", { + ...request, + origin: "BasenamesService", + context: { name }, + }); + + try { + const node = namehash(name); + + this._logger.debug("BasenamesService: querying L2 resolver", { + ...request, + origin: "BasenamesService", + context: { name, node, resolver: L2_RESOLVER_ADDRESS }, + }); + + const { Contract } = await import("ethers"); + const contract = new Contract( + L2_RESOLVER_ADDRESS, + L2_RESOLVER_ABI, + this.provider, + ); + + const contenthashBytes = await contract.contenthash(node); + + if (!contenthashBytes || contenthashBytes === "0x") { + this._logger.debug("BasenamesService: no contenthash set", { + ...request, + origin: "BasenamesService", + context: { name }, + }); + return null; + } + + const decoded = getContentHashFallback( + request, + this._logger, + contenthashBytes, + name, + "BasenamesService", + ); + + this._logger.debug("BasenamesService: contenthash resolved", { + ...request, + origin: "BasenamesService", + context: { name, contenthash: decoded }, + }); + + return decoded; + } catch (error: any) { + this._logger.error("BasenamesService: error resolving contenthash", { + ...request, + origin: "BasenamesService", + context: { + name, + error: error.message || error, + }, + }); + return null; + } + } +} diff --git a/packages/dweb-api-resolver/src/nameservice/index.ts b/packages/dweb-api-resolver/src/nameservice/index.ts index d803332..5e16ff6 100644 --- a/packages/dweb-api-resolver/src/nameservice/index.ts +++ b/packages/dweb-api-resolver/src/nameservice/index.ts @@ -11,15 +11,18 @@ export class NameServiceFactory implements INameServiceFactory { _logger: ILoggerService; _ensService: INameService; _web3NameSdkService: INameService; + _basenamesService: INameService | null; constructor( logger: ILoggerService, ensService: INameService, web3NameSdkService: INameService, + basenamesService?: INameService, ) { this._logger = logger; this._ensService = ensService; this._web3NameSdkService = web3NameSdkService; + this._basenamesService = basenamesService || null; } getNameServiceForDomain( @@ -33,6 +36,13 @@ export class NameServiceFactory implements INameServiceFactory { }); return this._web3NameSdkService; } + if (domain.endsWith(".base.eth") && this._basenamesService) { + this._logger.debug("Using BasenamesService for domain " + domain, { + ...request, + origin: "NameServiceFactory", + }); + return this._basenamesService; + } this._logger.debug("Using EnsService for domain " + domain, { ...request, origin: "NameServiceFactory", diff --git a/packages/dweb-api-server/src/configuration/index.ts b/packages/dweb-api-server/src/configuration/index.ts index 6aa952c..7a6bc89 100644 --- a/packages/dweb-api-server/src/configuration/index.ts +++ b/packages/dweb-api-server/src/configuration/index.ts @@ -6,6 +6,7 @@ import { IConfigurationEthereum, IConfigurationEthereumFailover, IConfigurationGnosis, + IConfigurationBase, ICacheConfig, IConfigurationIpfs, IConfigurationServerAsk, @@ -37,6 +38,9 @@ const configuration = { gnosis: { rpc: process.env.GNO_RPC_ENDPOINT || "https://rpc.gnosischain.com", }, + base: { + rpc: process.env.BASE_RPC_ENDPOINT || "https://mainnet.base.org", + }, // Storage backends ipfs: { backend: process.env.IPFS_TARGET || "http://localhost:8080", @@ -195,6 +199,10 @@ export class TestConfigurationService implements ServerConfiguration { return this.getServerConfiguration().getConfigGnosisBackend(); }; + getConfigBaseBackend = () => { + return this.getServerConfiguration().getConfigBaseBackend(); + }; + getCacheConfig = () => { return this.getServerConfiguration().getCacheConfig(); }; @@ -362,6 +370,20 @@ export const configurationToIConfigurationGnosis = (config: { }; }; +export const configurationToIConfigurationBase = (config: { + base: { + rpc: string; + }; +}): IConfigurationBase => { + return { + getConfigBaseBackend: () => { + return { + getBackend: () => config.base.rpc, + }; + }, + }; +}; + export const configurationToICacheConfig = (config: { cache: { ttl: number; @@ -547,6 +569,7 @@ export type ServerConfiguration = IConfigurationServerRouter & IConfigurationEthereum & IConfigurationEthereumFailover & IConfigurationGnosis & + IConfigurationBase & ICacheConfig & IConfigurationServerDnsquery & IDomainQueryConfig & @@ -567,6 +590,7 @@ export const configurationToServerConfiguration = ( ...configurationToIConfigurationEthereum(config), ...configurationToIConfigurationEthereumFailover(config), ...configurationToIConfigurationGnosis(config), + ...configurationToIConfigurationBase(config), ...configurationToICacheConfig(config), ...configurationToIDomainQueryConfig(config), ...configurationToIRedisConfig(config), diff --git a/packages/dweb-api-server/src/dependencies/services.ts b/packages/dweb-api-server/src/dependencies/services.ts index f4fb0bb..51bcf45 100644 --- a/packages/dweb-api-server/src/dependencies/services.ts +++ b/packages/dweb-api-server/src/dependencies/services.ts @@ -48,6 +48,7 @@ import { NameServiceFactory } from "dweb-api-resolver/dist/nameservice/index"; import {} from "dweb-api-resolver/dist/resolver/index"; import { Web3NameSdkService } from "dweb-api-resolver/dist/nameservice/Web3NameSdkService"; import { EnsService } from "dweb-api-resolver/dist/nameservice/EnsService"; +import { BasenamesService } from "dweb-api-resolver/dist/nameservice/BasenamesService"; export const createApplicationConfigurationBindingsManager = () => { const configuration = new EnvironmentBinding({ @@ -167,6 +168,12 @@ export const createApplicationConfigurationBindingsManager = () => { [EnvironmentConfiguration.Development]: (_env) => new TestResolverService(), }); + const basenamesService = new EnvironmentBinding({ + [EnvironmentConfiguration.Production]: (env) => + new BasenamesService(configuration.getBinding(env), logger.getBinding(env)), + [EnvironmentConfiguration.Development]: (_env) => new TestResolverService(), + }); + const domainRateLimit = new EnvironmentBinding({ [EnvironmentConfiguration.Production]: (env) => new DomainRateLimitService( @@ -192,12 +199,14 @@ export const createApplicationConfigurationBindingsManager = () => { logger.getBinding(env), ensService.getBinding(env), web3NameSdk.getBinding(env), + basenamesService.getBinding(env), ), [EnvironmentConfiguration.Development]: (env) => new NameServiceFactory( logger.getBinding(env), ensService.getBinding(env), web3NameSdk.getBinding(env), + basenamesService.getBinding(env), ), }); @@ -249,6 +258,7 @@ export const createApplicationConfigurationBindingsManager = () => { kuboApi, web3NameSdk, ensService, + basenamesService, domainRateLimit, arweaveResolver, nameServiceFactory, diff --git a/packages/dweb-api-types/src/config.ts b/packages/dweb-api-types/src/config.ts index 807a13c..0bca5a8 100644 --- a/packages/dweb-api-types/src/config.ts +++ b/packages/dweb-api-types/src/config.ts @@ -45,6 +45,12 @@ export interface IConfigurationGnosis { }; } +export interface IConfigurationBase { + getConfigBaseBackend: () => { + getBackend: () => string; + }; +} + export type IConfigurationLogger = { getLoggerConfig: () => { getLevel: () => "warn" | "error" | "info" | "debug";