Skip to content
Merged
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
24 changes: 15 additions & 9 deletions apps/host/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,13 @@ import {
setCacheSettings,
type Backend,
} from "@dotli/config/mode";
import { NETWORK_KEY, getNetwork, setNetwork } from "@dotli/config/network";
import {
NETWORK_KEY,
getActiveTldSuffix,
getNetwork,
setNetwork,
withActiveTld,
} from "@dotli/config/network";
import {
parseSettingsFromSearch,
writeSettingsToSearch,
Expand Down Expand Up @@ -438,7 +444,7 @@ async function applyProductBranding(
// the loaded app, and is logged so it stays observable in diagnostics.
} catch (err: unknown) {
log.warn(
`[dot.li manifest] icon fetch failed for ${label}.dot: ${err instanceof Error ? err.message : String(err)}`,
`[dot.li manifest] icon fetch failed for ${withActiveTld(label)}: ${err instanceof Error ? err.message : String(err)}`,
);
}
}
Expand Down Expand Up @@ -1129,7 +1135,7 @@ async function main(): Promise<void> {
showError("UI failed to initialise", err.message);
return;
}
urlBar.innerHTML = `<div class="topbar-url-pill" id="url-pill"><span class="verification-shield-wrap"><svg id="verification-shield" class="verification-shield" viewBox="0 0 24 24" fill="currentColor" stroke="none" aria-describedby="verification-tooltip"><path d="M12 2L3 7v5c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V7l-9-5zm-1 14.59l-3.29-3.3 1.41-1.41L11 13.76l4.88-4.88 1.41 1.41L11 16.59z"/></svg><span class="verification-tooltip" id="verification-tooltip" role="tooltip"><span class="verification-tooltip-title">How was this site loaded?</span><span class="verification-tooltip-row"><span class="verification-tooltip-dot is-verified" aria-hidden="true"></span><strong class="verification-tooltip-label">Verified</strong><span class="verification-tooltip-desc">More secure, checked by your light client.</span></span><span class="verification-tooltip-row"><span class="verification-tooltip-dot is-trusted" aria-hidden="true"></span><strong class="verification-tooltip-label">Trusted</strong><span class="verification-tooltip-desc">Served by an external RPC provider.</span></span></span></span><span class="topbar-url-text"><span class="dot-domain">${escapeHtml(label)}</span><span class="dot-tld">.dot</span></span></div>`;
urlBar.innerHTML = `<div class="topbar-url-pill" id="url-pill"><span class="verification-shield-wrap"><svg id="verification-shield" class="verification-shield" viewBox="0 0 24 24" fill="currentColor" stroke="none" aria-describedby="verification-tooltip"><path d="M12 2L3 7v5c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V7l-9-5zm-1 14.59l-3.29-3.3 1.41-1.41L11 13.76l4.88-4.88 1.41 1.41L11 16.59z"/></svg><span class="verification-tooltip" id="verification-tooltip" role="tooltip"><span class="verification-tooltip-title">How was this site loaded?</span><span class="verification-tooltip-row"><span class="verification-tooltip-dot is-verified" aria-hidden="true"></span><strong class="verification-tooltip-label">Verified</strong><span class="verification-tooltip-desc">More secure, checked by your light client.</span></span><span class="verification-tooltip-row"><span class="verification-tooltip-dot is-trusted" aria-hidden="true"></span><strong class="verification-tooltip-label">Trusted</strong><span class="verification-tooltip-desc">Served by an external RPC provider.</span></span></span></span><span class="topbar-url-text"><span class="dot-domain">${escapeHtml(label)}</span><span class="dot-tld">${escapeHtml(getActiveTldSuffix())}</span></span></div>`;

// Listen for status messages from the sandbox iframe so the loading
// UI continues seamlessly from resolution into content fetching.
Expand Down Expand Up @@ -1180,7 +1186,7 @@ async function main(): Promise<void> {
// sandbox render.
const contentFetchPhase = chainBackend === "rpc-gateway" ? 2 : 4;
advancePhase(0);
showStatus(`Resolving ${label}.dot`);
showStatus(`Resolving ${withActiveTld(label)}`);

try {
const cachedCid = cacheSettings.skipCidCache
Expand Down Expand Up @@ -1209,7 +1215,7 @@ async function main(): Promise<void> {
void recordRecentLabel(label);
void applyProductBranding(label, chainBackend).catch((err: unknown) => {
log.warn(
`[dot.li manifest] branding failed for ${label}.dot: ${err instanceof Error ? err.message : String(err)}`,
`[dot.li manifest] branding failed for ${withActiveTld(label)}: ${err instanceof Error ? err.message : String(err)}`,
);
});
performance.mark("dotli:main:end");
Expand Down Expand Up @@ -1320,7 +1326,7 @@ async function main(): Promise<void> {
if (cid === null) {
cid = await resolveDotNameRemote(label, onResolveProgress);
log.warn(
`[dot.li resolve] fallback ${label}.dot contenthash -> ${cid ?? "null"}`,
`[dot.li resolve] fallback ${withActiveTld(label)} contenthash -> ${cid ?? "null"}`,
);
}
} finally {
Expand All @@ -1340,7 +1346,7 @@ async function main(): Promise<void> {
if (cid === null) {
cid = await resolveDotNameViaRpc(label, onResolveProgress);
log.warn(
`[dot.li resolve] fallback ${label}.dot contenthash -> ${cid ?? "null"}`,
`[dot.li resolve] fallback ${withActiveTld(label)} contenthash -> ${cid ?? "null"}`,
);
}
}
Expand All @@ -1361,7 +1367,7 @@ async function main(): Promise<void> {
stopStatusTick();
performance.mark("dotli:resolve:end");
log.warn(
`[dot.li resolve] RESOLVED ${label}.dot via ${chainBackend} in ${dur(resolveStart)} (total ${elapsed(T0)}) -> ${cid ?? "null"}`,
`[dot.li resolve] RESOLVED ${withActiveTld(label)} via ${chainBackend} in ${dur(resolveStart)} (total ${elapsed(T0)}) -> ${cid ?? "null"}`,
);

if (cid === null) {
Expand All @@ -1387,7 +1393,7 @@ async function main(): Promise<void> {
void recordRecentLabel(label);
void applyProductBranding(label, chainBackend).catch((err: unknown) => {
log.warn(
`[dot.li manifest] branding failed for ${label}.dot: ${err instanceof Error ? err.message : String(err)}`,
`[dot.li manifest] branding failed for ${withActiveTld(label)}: ${err instanceof Error ? err.message : String(err)}`,
);
});

Expand Down
21 changes: 21 additions & 0 deletions apps/host/tests/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,27 @@
* import these instead of re-reading `process.env`.
*/

import {
NETWORK_NAME_TO_SERVICES_CONFIG,
isValidNetwork,
type Network,
} from "@dotli/config/network";

export const DOMAIN = process.env.DOMAIN ?? "host-playground";
export const PORT = process.env.PORT ?? "5173";
export const TIMEOUT_MS = parseInt(process.env.TIMEOUT_MS ?? "45000", 10);

/** Network under test. Must match the first entry of the build's VITE_NETWORKS. */
export const NETWORK: Network = (() => {
const raw = process.env.NETWORK ?? "paseo-next-v2";
if (!isValidNetwork(raw)) {
throw new Error(`NETWORK is not a known network: ${JSON.stringify(raw)}`);
}
return raw;
})();

/** dotNS suffix of the network under test, e.g. `.paseo`. Read from the same config the app uses. */
export const TLD_SUFFIX = `.${NETWORK_NAME_TO_SERVICES_CONFIG[NETWORK].dotns.TLD}`;

/** The full dotNS name under test, e.g. `host-playground.paseo`. */
export const DOTNS_NAME = `${DOMAIN}${TLD_SUFFIX}`;
6 changes: 5 additions & 1 deletion apps/host/tests/functional/loading.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { test } from "./helpers/shared-mode-reset";
import { findAppFrame } from "../product-frame";
import { seedBackend, type Backend } from "./fixtures/settings";

import { TLD_SUFFIX } from "../env";

const DOMAIN = process.env.COMBO_DOMAIN ?? "host-playground";
const PORT = process.env.COMBO_PORT ?? "5173";
const HOST_URL = `http://${DOMAIN}.localhost:${PORT}/`;
Expand Down Expand Up @@ -462,7 +464,9 @@ test("As a user, when I visit a domain that has no content set, I see the approp
"This app can't be reached",
{ timeout: 10_000 },
);
await expect(page.locator(".error-page-domain")).toHaveText(`${DOMAIN}.dot`);
await expect(page.locator(".error-page-domain")).toHaveText(
`${DOMAIN}${TLD_SUFFIX}`,
);
await expect(page.locator(".error-page-detail")).toContainText(
"Check if there is a typo",
);
Expand Down
4 changes: 2 additions & 2 deletions apps/host/tests/functional/resolution.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* Env overrides: DOMAIN, PORT, TIMEOUT_MS
*/

import { DOMAIN, PORT, TIMEOUT_MS } from "../env";
import { DOMAIN, DOTNS_NAME, PORT, TIMEOUT_MS } from "../env";
import { setupTest } from "./helpers/context";
import { waitForResolutionOutcome } from "../product-frame";
import { BACKENDS } from "./fixtures/settings";
Expand All @@ -19,7 +19,7 @@ test.setTimeout(BACKENDS.length * TIMEOUT_MS * 2);

test.describe("Resolution across chain backends", () => {
for (const backend of BACKENDS) {
test(`As a user opening ${DOMAIN}.dot via ${backend}, the shell loads the app`, async ({
test(`As a user opening ${DOTNS_NAME} via ${backend}, the shell loads the app`, async ({
browser,
}) => {
// Given
Expand Down
13 changes: 7 additions & 6 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,12 @@
"smoldot": "3.3.2"
},
"overrides": {
"@parity/truapi": "0.7.0",
"@parity/truapi": "0.9.0",
"fast-uri": "3.1.5",
"smoldot": "3.3.2",
"esbuild": "^0.28.1",
"brace-expansion": "^5.0.9",
"postcss": "^8.5.24"
"postcss": "^8.5.24",
"nanoid": "^3.3.18"
}
}
41 changes: 31 additions & 10 deletions packages/config/src/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ export interface DotnsStorageSlots {
export interface DotnsContracts {
readonly DOTNS_REGISTRY: `0x${string}`;
readonly DOTNS_CONTENT_RESOLVER: `0x${string}`;
readonly storageSlots: DotnsStorageSlots;
readonly STORAGE_SLOTS: DotnsStorageSlots;
/** Bare TLD label this network registers names under, e.g. `dot` or `paseo`. */
readonly TLD: string;
}

export interface ChainService {
Expand Down Expand Up @@ -50,7 +52,7 @@ const BUILTIN_NETWORK_SERVICES: Record<NetworkName, ServicesConfig> = {
description: "Legacy Paseo Next system chains",
relay: {
genesis:
"0x77afd6190f1554ad45fd0d31aee62aacc33c6db0ea801129acb813f913e0764f",
"0x374057be67b355151f271ff70c3db98308c62c8adc48dc6724b6a009a1a014fd",
rpcs: [
"wss://paseo-rpc.n.dwellir.com",
"wss://paseo.dotters.network",
Expand Down Expand Up @@ -82,15 +84,16 @@ const BUILTIN_NETWORK_SERVICES: Record<NetworkName, ServicesConfig> = {
dotns: {
DOTNS_REGISTRY: "0x4Da0d37aBe96C06ab19963F31ca2DC0412057a6f",
DOTNS_CONTENT_RESOLVER: "0x7756DF72CBc7f062e7403cD59e45fBc78bed1cD7",
storageSlots: { REGISTRY_RECORDS: 0, CONTENTHASH: 1 },
STORAGE_SLOTS: { REGISTRY_RECORDS: 0, CONTENTHASH: 1 },
TLD: "dot",
},
},
[NetworkName.PASEO_NEXT_V2]: {
label: "Paseo Next V2",
description: "Upgraded Paseo Next system chains",
relay: {
genesis:
"0x77afd6190f1554ad45fd0d31aee62aacc33c6db0ea801129acb813f913e0764f",
"0x374057be67b355151f271ff70c3db98308c62c8adc48dc6724b6a009a1a014fd",
rpcs: [
"wss://paseo-rpc.n.dwellir.com",
"wss://paseo.dotters.network",
Expand All @@ -100,7 +103,7 @@ const BUILTIN_NETWORK_SERVICES: Record<NetworkName, ServicesConfig> = {
},
assethub: {
genesis:
"0xbf0488dbe9daa1de1c08c5f743e26fdc2a4ecd74cf87dd1b4b1eeb99ae4ef19f",
"0x23e730eb1c6fecae09c917439a5038cb6122d0d48980e8b9bbf0ff56f94a2ca6",
rpcs: ["wss://paseo-asset-hub-next-rpc.polkadot.io"],
},
bulletin: {
Expand All @@ -111,13 +114,14 @@ const BUILTIN_NETWORK_SERVICES: Record<NetworkName, ServicesConfig> = {
},
people: {
genesis:
"0xc5af1826b31493f08b7e2a823842f98575b806a784126f28da9608c68665afa5",
"0x89a63b11fef2c0273fc72c0d864da0793a665dade5db153e0cab995348c5440f",
rpcs: ["wss://paseo-people-next-system-rpc.polkadot.io"],
},
dotns: {
DOTNS_REGISTRY: "0xa1b2b939E82b2ecE55Bd8a0E283818BfC1CA6CDc",
DOTNS_CONTENT_RESOLVER: "0x8A26480b0B5Df3d4D9b95adc24a5Ecb33A5b8F64",
storageSlots: { REGISTRY_RECORDS: 0, CONTENTHASH: 0, TEXT_RECORDS: 1 },
DOTNS_REGISTRY: "0xf34054fd76BbF85f216cf9908226D5f0A72E50CA",
DOTNS_CONTENT_RESOLVER: "0x7F74D7CD50f5a834270E2ad395a01b01891AB37d",
STORAGE_SLOTS: { REGISTRY_RECORDS: 0, CONTENTHASH: 0, TEXT_RECORDS: 1 },
TLD: "paseo",
},
},
[NetworkName.PREVIEW_NET]: {
Expand Down Expand Up @@ -150,7 +154,8 @@ const BUILTIN_NETWORK_SERVICES: Record<NetworkName, ServicesConfig> = {
dotns: {
DOTNS_REGISTRY: "0xf34054fd76BbF85f216cf9908226D5f0A72E50CA",
DOTNS_CONTENT_RESOLVER: "0x7F74D7CD50f5a834270E2ad395a01b01891AB37d",
storageSlots: { REGISTRY_RECORDS: 0, CONTENTHASH: 0, TEXT_RECORDS: 1 },
STORAGE_SLOTS: { REGISTRY_RECORDS: 0, CONTENTHASH: 0, TEXT_RECORDS: 1 },
TLD: "dot",
},
},
};
Expand Down Expand Up @@ -492,6 +497,22 @@ export function setNetwork(network: Network): void {
}
}

/**
* The active TLD with its leading dot, e.g. `".paseo"`.
*
* Each TLD must also be in `DOTNS_TLDS` in truapi's `truapi-platform`, which
* gates `productId` with its own hardcoded list. A network whose TLD the core
* does not know cannot load a product until the core ships it.
*/
export function getActiveTldSuffix(): string {
return `.${getActiveServicesConfig().dotns.TLD}`;
}

/** Appends the active TLD to a bare label, giving `myapp.paseo`. */
export function withActiveTld(label: string): string {
return `${label}${getActiveTldSuffix()}`;
}

/** Full service config for the active network. */
export function getActiveServicesConfig(): ServicesConfig {
return NETWORK_NAME_TO_SERVICES_CONFIG[getNetwork()];
Expand Down
24 changes: 23 additions & 1 deletion packages/config/tests/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe("config constants", () => {
describe("dotns storage slots", () => {
it("every network exposes non-negative REGISTRY_RECORDS and CONTENTHASH slots", () => {
for (const cfg of Object.values(NETWORK_NAME_TO_SERVICES_CONFIG)) {
const slots = cfg.dotns.storageSlots;
const slots = cfg.dotns.STORAGE_SLOTS;
expect(Number.isInteger(slots.REGISTRY_RECORDS)).toBe(true);
expect(slots.REGISTRY_RECORDS).toBeGreaterThanOrEqual(0);
expect(Number.isInteger(slots.CONTENTHASH)).toBe(true);
Expand All @@ -52,6 +52,28 @@ describe("config constants", () => {
});
});

describe("dotns TLD", () => {
// Mirrors `DOTNS_TLDS` in truapi's `truapi-platform`.
const CORE_ACCEPTED_TLDS = ["dot", "paseo"];

it("every network declares a bare lowercase TLD the truapi core accepts", () => {
for (const [name, cfg] of Object.entries(
NETWORK_NAME_TO_SERVICES_CONFIG,
)) {
const tld = cfg.dotns.TLD;
expect(tld, `${name} TLD must be non-empty`).not.toBe("");
expect(tld, `${name} TLD must be lowercase`).toBe(tld.toLowerCase());
expect(tld, `${name} TLD must not carry a leading dot`).not.toMatch(
/^\./,
);
expect(
CORE_ACCEPTED_TLDS,
`${name} TLD ${tld} is not in the core's DOTNS_TLDS`,
).toContain(tld);
}
});
});

describe("IPFS gateways", () => {
it("V1 first gateway is a valid HTTPS URL", () => {
expect(
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"name":"Paseo Asset Hub Next","id":"next-asset-hub-paseo","chainType":"Live","bootNodes":["/dns/paseo-asset-hub-next-collator-node-0.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWKT5DcVLoBbVDAM6N5ujDVknPfQWHk8SGJqJPyAM8Z4Y4","/dns/paseo-asset-hub-next-collator-node-1.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWFjpVdBnfJFntogWhinn15WW2n8Fd2DiAcqU6i9rg47Yg","/ip4/34.7.179.66/udp/32208/webrtc-direct/certhash/uEiBsqkcr8pOaNjl6px_v1nBatWMfXB9C_sU8fDat3mZWfQ/p2p/12D3KooWK8TtC31jkC9u5EfHPx7MGRCdE1kFSFXW43qdDWqdKMum","/ip4/34.91.159.79/udp/32039/webrtc-direct/certhash/uEiB5itUMGibt92QYT7-bsTz7zMTR8v7GZlJJ0RUIbNd0HA/p2p/12D3KooWK2X3GytbTgeBwFJ3mHMkpkB3u7JPu7zyTuWiuqp99UHK"],"telemetryEndpoints":null,"protocolId":null,"properties":{"tokenDecimals":10,"tokenSymbol":"PAS"},"relay_chain":"paseo","para_id":1500,"codeSubstitutes":{},"genesis":{"stateRootHash":"0x9202fc20d789aecfb88faf17dca27d42f8b90cf0a0b02c96d7024585864b7271"}}
{"name":"Paseo Asset Hub Next","id":"next-asset-hub-paseo","chainType":"Live","bootNodes":["/dns/paseo-asset-hub-next-collator-node-0.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWKT5DcVLoBbVDAM6N5ujDVknPfQWHk8SGJqJPyAM8Z4Y4","/dns/paseo-asset-hub-next-collator-node-1.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWFjpVdBnfJFntogWhinn15WW2n8Fd2DiAcqU6i9rg47Yg","/ip4/34.7.179.66/udp/32208/webrtc-direct/certhash/uEiBsqkcr8pOaNjl6px_v1nBatWMfXB9C_sU8fDat3mZWfQ/p2p/12D3KooWK8TtC31jkC9u5EfHPx7MGRCdE1kFSFXW43qdDWqdKMum","/ip4/34.91.159.79/udp/32039/webrtc-direct/certhash/uEiB5itUMGibt92QYT7-bsTz7zMTR8v7GZlJJ0RUIbNd0HA/p2p/12D3KooWK2X3GytbTgeBwFJ3mHMkpkB3u7JPu7zyTuWiuqp99UHK"],"telemetryEndpoints":null,"protocolId":null,"properties":{"tokenDecimals":10,"tokenSymbol":"PAS"},"relay_chain":"paseo","para_id":1500,"codeSubstitutes":{},"genesis":{"stateRootHash":"0x2b1f5dafccbaeb2e52c553a57003ef77bf35920d86c2c932920e4a60c7f68a50"}}
Loading
Loading