Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ __Gateway request flow__
| `ETH_RPC_ENDPOINT` | `"http://192.168.1.7:8845"` | Primary RPC provider FQDN for ENS resolution. |
| `ETH_RPC_ENDPOINT_FAILOVER_PRIMARY` | `null` | Secondary failover RPC provider FQDN. |
| `GNO_RPC_ENDPOINT` | `https://rpc.gnosischain.com` | Primary RPC endpoint for Gnosis. |
| `BASE_RPC_ENDPOINT` | `https://mainnet.base.org` | Primary RPC endpoint for Base L2 (Basenames `*.base.eth` resolution). |
| `DOMAINSAPI_ENDPOINT` | `null` | API endpoint for custom domain routing logic. Can be set to any endpoint that returns a `200` if you do not need this feature. |
| `LOG_LEVEL` | `"info"` | Set the logging level. |
| `LIMO_HOSTNAME_SUBSTITUTION_CONFIG` | `{ "eth.limo": "eth", "eth.local": "eth", "gno.limo": "gno", "gno.local": "gno" }` | The domains and services corresponding to each domain name for gateway operations. When set via an environment variable, this must be a base64 encoded JSON object. |
Expand Down
97 changes: 97 additions & 0 deletions packages/dweb-api-resolver/src/nameservice/BasenamesService.ts
Original file line number Diff line number Diff line change
@@ -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<string | null> {
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;
}
}
}
10 changes: 10 additions & 0 deletions packages/dweb-api-resolver/src/nameservice/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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",
Expand Down
24 changes: 24 additions & 0 deletions packages/dweb-api-server/src/configuration/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
IConfigurationEthereum,
IConfigurationEthereumFailover,
IConfigurationGnosis,
IConfigurationBase,
ICacheConfig,
IConfigurationIpfs,
IConfigurationServerAsk,
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -195,6 +199,10 @@ export class TestConfigurationService implements ServerConfiguration {
return this.getServerConfiguration().getConfigGnosisBackend();
};

getConfigBaseBackend = () => {
return this.getServerConfiguration().getConfigBaseBackend();
};

getCacheConfig = () => {
return this.getServerConfiguration().getCacheConfig();
};
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -547,6 +569,7 @@ export type ServerConfiguration = IConfigurationServerRouter &
IConfigurationEthereum &
IConfigurationEthereumFailover &
IConfigurationGnosis &
IConfigurationBase &
ICacheConfig &
IConfigurationServerDnsquery &
IDomainQueryConfig &
Expand All @@ -567,6 +590,7 @@ export const configurationToServerConfiguration = (
...configurationToIConfigurationEthereum(config),
...configurationToIConfigurationEthereumFailover(config),
...configurationToIConfigurationGnosis(config),
...configurationToIConfigurationBase(config),
...configurationToICacheConfig(config),
...configurationToIDomainQueryConfig(config),
...configurationToIRedisConfig(config),
Expand Down
10 changes: 10 additions & 0 deletions packages/dweb-api-server/src/dependencies/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ServerConfiguration>({
Expand Down Expand Up @@ -167,6 +168,12 @@ export const createApplicationConfigurationBindingsManager = () => {
[EnvironmentConfiguration.Development]: (_env) => new TestResolverService(),
});

const basenamesService = new EnvironmentBinding<INameService>({
[EnvironmentConfiguration.Production]: (env) =>
new BasenamesService(configuration.getBinding(env), logger.getBinding(env)),
[EnvironmentConfiguration.Development]: (_env) => new TestResolverService(),
});

const domainRateLimit = new EnvironmentBinding<IDomainRateLimitService>({
[EnvironmentConfiguration.Production]: (env) =>
new DomainRateLimitService(
Expand All @@ -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),
),
});

Expand Down Expand Up @@ -249,6 +258,7 @@ export const createApplicationConfigurationBindingsManager = () => {
kuboApi,
web3NameSdk,
ensService,
basenamesService,
domainRateLimit,
arweaveResolver,
nameServiceFactory,
Expand Down
12 changes: 12 additions & 0 deletions packages/dweb-api-server/src/test/cases.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,17 @@
"type": "none",
"contentHash": null,
"additionalInfo": {}
},
{
"name": "example.base.eth",
"type": "ipfs",
"contentHash": "ipfs://bafkreibyh6otmd37y7edohwbjwydmqpxxygarmrur7j4xwhjejnskw3kta",
"additionalInfo": {}
},
{
"name": "norecord.base.eth",
"type": "none",
"contentHash": null,
"additionalInfo": {}
}
]
9 changes: 8 additions & 1 deletion packages/dweb-api-server/src/test/integration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type HarnessType = {
hostnameSubstitionService: IHostnameSubstitutionService;
testEnsService: TestResolverService;
web3NameSdkService: TestResolverService;
testBasenamesService: TestResolverService;
testArweaveResolverService: TestResolverService;
testDomainQuerySuperagentService: TestDomainQuerySuperagentService;
domainQueryService: IDomainQueryService;
Expand Down Expand Up @@ -71,6 +72,9 @@ let buildAppContainer = (): HarnessType => {
web3NameSdkService: services.web3NameSdk.getBinding(
EnvironmentConfiguration.Development,
) as TestResolverService,
testBasenamesService: services.basenamesService.getBinding(
EnvironmentConfiguration.Development,
) as TestResolverService,
testArweaveResolverService: services.arweaveResolver.getBinding(
EnvironmentConfiguration.Development,
) as TestResolverService,
Expand Down Expand Up @@ -225,11 +229,14 @@ const harness =
const resolvers = [
harnessInput.testEnsService,
harnessInput.web3NameSdkService,
harnessInput.testBasenamesService,
];

var theRealTestResolverService: TestResolverService;

if (nameResolvedToEnsName.endsWith("eth")) {
if (nameResolvedToEnsName.endsWith(".base.eth")) {
theRealTestResolverService = harnessInput.testBasenamesService;
} else if (nameResolvedToEnsName.endsWith("eth")) {
theRealTestResolverService = harnessInput.testEnsService;
} else if (nameResolvedToEnsName.endsWith("gno")) {
theRealTestResolverService = harnessInput.web3NameSdkService;
Expand Down
6 changes: 6 additions & 0 deletions packages/dweb-api-types/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ export interface IConfigurationGnosis {
};
}

export interface IConfigurationBase {
getConfigBaseBackend: () => {
getBackend: () => string;
};
}

export type IConfigurationLogger = {
getLoggerConfig: () => {
getLevel: () => "warn" | "error" | "info" | "debug";
Expand Down