From 1f0fc0cc66ddcab679f8ac3837a270225b195f5a Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Wed, 18 Sep 2024 21:01:53 +0100 Subject: [PATCH 01/11] fix(explorer): default opts to empty object --- packages/explorer/src/observer/decorator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/explorer/src/observer/decorator.ts b/packages/explorer/src/observer/decorator.ts index cee3a22c2b..ee7e4a7e58 100644 --- a/packages/explorer/src/observer/decorator.ts +++ b/packages/explorer/src/observer/decorator.ts @@ -14,7 +14,7 @@ export type ObserverOptions = { export function observer({ explorerUrl = "http://localhost:13690", waitForStateChange, -}: ObserverOptions): ( +}: ObserverOptions = {}): ( client: Client, ) => Pick, "writeContract"> { const emit = createBridge({ url: `${explorerUrl}/internal/observer-relay` }); From 3c5e5e0e3a97b116133a364cea73da4ea5525289 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Wed, 18 Sep 2024 13:05:15 -0700 Subject: [PATCH 02/11] Create unlucky-icons-wonder.md --- .changeset/unlucky-icons-wonder.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/unlucky-icons-wonder.md diff --git a/.changeset/unlucky-icons-wonder.md b/.changeset/unlucky-icons-wonder.md new file mode 100644 index 0000000000..b3468ab8de --- /dev/null +++ b/.changeset/unlucky-icons-wonder.md @@ -0,0 +1,10 @@ +--- +"@latticexyz/explorer": patch +--- + +Fixed an issue where the `observer` Viem client decorator required an empty object arg when no options were used. + +```diff +-client.extend(observer({})); ++client.extend(observer()); +``` From 61890f6a80434d6449d376a460e0946a0cd5761c Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Wed, 18 Sep 2024 13:06:01 -0700 Subject: [PATCH 03/11] Update unlucky-icons-wonder.md --- .changeset/unlucky-icons-wonder.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/unlucky-icons-wonder.md b/.changeset/unlucky-icons-wonder.md index b3468ab8de..653142c8e4 100644 --- a/.changeset/unlucky-icons-wonder.md +++ b/.changeset/unlucky-icons-wonder.md @@ -2,7 +2,7 @@ "@latticexyz/explorer": patch --- -Fixed an issue where the `observer` Viem client decorator required an empty object arg when no options were used. +Fixed an issue where the `observer` Viem client decorator required an empty object arg when no options are used. ```diff -client.extend(observer({})); From 6835f28168f751a4667af8a26e023097733f4cfc Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Wed, 18 Sep 2024 22:13:45 +0100 Subject: [PATCH 04/11] various fixes --- packages/explorer/next.config.mjs | 7 +++++- .../src/app/{ => (explorer)}/error.tsx | 6 ++--- .../src/app/{ => (explorer)}/globals.css | 0 .../src/app/{ => (explorer)}/layout.tsx | 3 --- .../src/app/{ => (explorer)}/not-found.tsx | 4 ++-- .../src/app/{ => (explorer)}/page.tsx | 4 ++-- packages/explorer/src/app/api/world/route.ts | 8 +++---- packages/explorer/src/common.ts | 24 ++++++++++--------- 8 files changed, 30 insertions(+), 26 deletions(-) rename packages/explorer/src/app/{ => (explorer)}/error.tsx (88%) rename packages/explorer/src/app/{ => (explorer)}/globals.css (100%) rename packages/explorer/src/app/{ => (explorer)}/layout.tsx (96%) rename packages/explorer/src/app/{ => (explorer)}/not-found.tsx (90%) rename packages/explorer/src/app/{ => (explorer)}/page.tsx (67%) diff --git a/packages/explorer/next.config.mjs b/packages/explorer/next.config.mjs index cffb1b98ba..7811d5e71d 100644 --- a/packages/explorer/next.config.mjs +++ b/packages/explorer/next.config.mjs @@ -10,10 +10,15 @@ export default function config() { }, redirects: async () => { return [ + { + source: "/worlds/:path*", + destination: "/anvil/worlds/:path*", + permanent: false, + }, { source: "/:chainName/worlds/:worldAddress/explorer", destination: "/:chainName/worlds/:worldAddress/explore", - permanent: true, + permanent: false, }, ]; }, diff --git a/packages/explorer/src/app/error.tsx b/packages/explorer/src/app/(explorer)/error.tsx similarity index 88% rename from packages/explorer/src/app/error.tsx rename to packages/explorer/src/app/(explorer)/error.tsx index 844d3e5723..c599bd6ab8 100644 --- a/packages/explorer/src/app/error.tsx +++ b/packages/explorer/src/app/(explorer)/error.tsx @@ -2,8 +2,8 @@ import { ExternalLink, RefreshCwIcon } from "lucide-react"; import Link from "next/link"; -import { Button } from "../components/ui/Button"; -import { useWorldUrl } from "../hooks/useWorldUrl"; +import { Button } from "../../components/ui/Button"; +import { useWorldUrl } from "../../hooks/useWorldUrl"; type Props = { error: Error & { digest?: string }; @@ -14,7 +14,7 @@ export default function Error({ reset, error }: Props) { const getUrl = useWorldUrl(); return (
-

400

+

500

Something went wrong :(

{error.message && ( diff --git a/packages/explorer/src/app/globals.css b/packages/explorer/src/app/(explorer)/globals.css similarity index 100% rename from packages/explorer/src/app/globals.css rename to packages/explorer/src/app/(explorer)/globals.css diff --git a/packages/explorer/src/app/layout.tsx b/packages/explorer/src/app/(explorer)/layout.tsx similarity index 96% rename from packages/explorer/src/app/layout.tsx rename to packages/explorer/src/app/(explorer)/layout.tsx index 1dd8395953..533d78abcf 100644 --- a/packages/explorer/src/app/layout.tsx +++ b/packages/explorer/src/app/(explorer)/layout.tsx @@ -19,9 +19,6 @@ const jetbrains = JetBrains_Mono({ export const metadata: Metadata = { title: "World Explorer", description: "World Explorer is a tool for visually exploring and manipulating the state of worlds", - icons: { - icon: "/favicon.svg", - }, }; export default function RootLayout({ diff --git a/packages/explorer/src/app/not-found.tsx b/packages/explorer/src/app/(explorer)/not-found.tsx similarity index 90% rename from packages/explorer/src/app/not-found.tsx rename to packages/explorer/src/app/(explorer)/not-found.tsx index a4cddf5996..44059b412c 100644 --- a/packages/explorer/src/app/not-found.tsx +++ b/packages/explorer/src/app/(explorer)/not-found.tsx @@ -2,8 +2,8 @@ import { ExternalLink } from "lucide-react"; import Link from "next/link"; -import { Button } from "../components/ui/Button"; -import { useWorldUrl } from "../hooks/useWorldUrl"; +import { Button } from "../../components/ui/Button"; +import { useWorldUrl } from "../../hooks/useWorldUrl"; export default function NotFound() { const getUrl = useWorldUrl(); diff --git a/packages/explorer/src/app/page.tsx b/packages/explorer/src/app/(explorer)/page.tsx similarity index 67% rename from packages/explorer/src/app/page.tsx rename to packages/explorer/src/app/(explorer)/page.tsx index cfa56011be..99c90345a5 100644 --- a/packages/explorer/src/app/page.tsx +++ b/packages/explorer/src/app/(explorer)/page.tsx @@ -1,5 +1,5 @@ import { redirect } from "next/navigation"; -import { supportedChainsById, validateChainId } from "../common"; +import { chainIdToName, validateChainId } from "../../common"; export const dynamic = "force-dynamic"; @@ -7,6 +7,6 @@ export default function IndexPage() { const chainId = Number(process.env.CHAIN_ID); validateChainId(chainId); - const chainName = supportedChainsById[chainId]; + const chainName = chainIdToName[chainId] ?? "anvil"; return redirect(`/${chainName}/worlds`); } diff --git a/packages/explorer/src/app/api/world/route.ts b/packages/explorer/src/app/api/world/route.ts index 650dee9bad..4e4aa12ce9 100644 --- a/packages/explorer/src/app/api/world/route.ts +++ b/packages/explorer/src/app/api/world/route.ts @@ -3,12 +3,12 @@ import { getBlockNumber, getLogs } from "viem/actions"; import { helloStoreEvent } from "@latticexyz/store"; import { helloWorldEvent } from "@latticexyz/world"; import { getWorldAbi } from "@latticexyz/world/internal"; -import { SupportedChainIds, supportedChainsById, validateChainId } from "../../../common"; +import { supportedChainId, supportedChains, validateChainId } from "../../../common"; export const dynamic = "force-dynamic"; -async function getClient(chainId: SupportedChainIds) { - const chain = supportedChainsById[chainId]; +async function getClient(chainId: supportedChainId) { + const chain = Object.values(supportedChains).find((c) => c.id === chainId); const client = createWalletClient({ chain, transport: http(), @@ -17,7 +17,7 @@ async function getClient(chainId: SupportedChainIds) { return client; } -async function getParameters(chainId: SupportedChainIds, worldAddress: Address) { +async function getParameters(chainId: supportedChainId, worldAddress: Address) { const client = await getClient(chainId); const toBlock = await getBlockNumber(client); const logs = await getLogs(client, { diff --git a/packages/explorer/src/common.ts b/packages/explorer/src/common.ts index de77ede488..f139856721 100644 --- a/packages/explorer/src/common.ts +++ b/packages/explorer/src/common.ts @@ -1,21 +1,23 @@ import { anvil, garnet, redstone } from "viem/chains"; export const supportedChains = { anvil, garnet, redstone } as const; -export const supportedChainsById = Object.fromEntries( - Object.entries(supportedChains).map(([, chain]) => [chain.id, chain]), -); +export type supportedChains = typeof supportedChains; + +export type supportedChainName = keyof supportedChains; +export type supportedChainId = supportedChains[supportedChainName]["id"]; -export type SupportedChainIds = (typeof supportedChains)[keyof typeof supportedChains]["id"]; -export type SupportedChainNames = keyof typeof supportedChains; +export const chainIdToName = Object.fromEntries( + Object.entries(supportedChains).map(([chainName, chain]) => [chain.id, chainName]), +); -export function validateChainId(chainId: number): asserts chainId is SupportedChainIds { - if (!(chainId in supportedChainsById)) { - throw new Error(`Invalid chain id. Supported chains are: ${Object.keys(supportedChainsById).join(", ")}.`); +export function validateChainId(chainId: unknown): asserts chainId is supportedChainId { + if (!(typeof chainId === "number" && chainId in chainIdToName)) { + throw new Error(`Invalid chain ID. Supported chains are: ${Object.keys(chainIdToName).join(", ")}.`); } } -export function validateChainName(name: string | string[] | undefined): asserts name is SupportedChainNames { - if (Array.isArray(name) || typeof name !== "string" || !(name in supportedChains)) { - throw new Error(`Invalid chain name. Supported chains are: ${Object.keys(supportedChainsById).join(", ")}.`); +export function validateChainName(name: unknown): asserts name is supportedChainName { + if (!(typeof name === "string" && name in supportedChains)) { + throw new Error(`Invalid chain name. Supported chains are: ${Object.keys(supportedChains).join(", ")}.`); } } From 45b4d6639718efeef795059b37d5ecba5f3ff282 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Wed, 18 Sep 2024 22:17:58 +0100 Subject: [PATCH 05/11] fix icon issue --- .../explorer/{public/favicon.svg => src/app/(explorer)/icon.svg} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/explorer/{public/favicon.svg => src/app/(explorer)/icon.svg} (100%) diff --git a/packages/explorer/public/favicon.svg b/packages/explorer/src/app/(explorer)/icon.svg similarity index 100% rename from packages/explorer/public/favicon.svg rename to packages/explorer/src/app/(explorer)/icon.svg From 49d3d6762dc12af147879a278eb867036ee3bcc4 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Wed, 18 Sep 2024 14:20:47 -0700 Subject: [PATCH 06/11] Update unlucky-icons-wonder.md --- .changeset/unlucky-icons-wonder.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.changeset/unlucky-icons-wonder.md b/.changeset/unlucky-icons-wonder.md index 653142c8e4..b27c39e971 100644 --- a/.changeset/unlucky-icons-wonder.md +++ b/.changeset/unlucky-icons-wonder.md @@ -8,3 +8,5 @@ Fixed an issue where the `observer` Viem client decorator required an empty obje -client.extend(observer({})); +client.extend(observer()); ``` + +Also fixed a few issues where we were incorrectly redirecting based on the chain name or ID. From 4600a0e3f05d955338fb88cc9c0b0cdd49901025 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Wed, 18 Sep 2024 22:30:12 +0100 Subject: [PATCH 07/11] cache bridges so we don't create a bunch --- packages/explorer/src/observer/bridge.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/explorer/src/observer/bridge.ts b/packages/explorer/src/observer/bridge.ts index 7be2ebb926..1f493b733c 100644 --- a/packages/explorer/src/observer/bridge.ts +++ b/packages/explorer/src/observer/bridge.ts @@ -20,13 +20,18 @@ export function wrapMessage(data: unknown): BridgeEnvelope { return { mud: "explorer/observer", data }; } +const emits = new Map(); + export type CreateBridgeOpts = { url: string; timeout?: number; }; export function createBridge({ url, timeout = 10_000 }: CreateBridgeOpts): EmitMessage { - const emit = Promise.withResolvers(); + const priorEmit = emits.get(url); + if (priorEmit) return priorEmit; + + const deferredEmit = Promise.withResolvers(); const iframe = document.createElement("iframe"); iframe.tabIndex = -1; iframe.ariaHidden = "true"; @@ -40,7 +45,7 @@ export function createBridge({ url, timeout = 10_000 }: CreateBridgeOpts): EmitM () => { debug("observer iframe ready", iframe.src); // TODO: throw if `iframe.contentWindow` is `null`? - emit.resolve((type, data) => { + deferredEmit.resolve((type, data) => { const message = wrapMessage({ ...data, type, time: Date.now() }); debug("posting message to bridge", message); iframe.contentWindow!.postMessage(message, "*"); @@ -53,29 +58,31 @@ export function createBridge({ url, timeout = 10_000 }: CreateBridgeOpts): EmitM "error", (error) => { debug("observer iframe error", error); - emit.reject(error); + deferredEmit.reject(error); }, { once: true }, ); // TODO: should we let the caller handle this with their own promise timeout or race? wait(timeout).then(() => { - emit.reject(new Error("Timed out waiting for observer iframe to load.")); + deferredEmit.reject(new Error("Timed out waiting for observer iframe to load.")); }); debug("mounting observer iframe", url); iframe.src = url; parent.document.body.appendChild(iframe); - emit.promise.catch(() => { + deferredEmit.promise.catch(() => { iframe.remove(); }); - return (messageType, message) => { + const emit: EmitMessage = (messageType, message) => { debug("got message for bridge", messageType, message); - emit.promise.then( + deferredEmit.promise.then( (fn) => fn(messageType, message), (error) => debug("could not deliver message", message, error), ); }; + emits.set(url, emit); + return emit; } From 2c91293933e5532b5fc07f652346ed06e7b6aeb8 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 19 Sep 2024 00:05:54 +0100 Subject: [PATCH 08/11] refactor --- packages/explorer/src/observer/bridge.ts | 102 ++++++++++---------- packages/explorer/src/observer/decorator.ts | 75 +++++++------- 2 files changed, 85 insertions(+), 92 deletions(-) diff --git a/packages/explorer/src/observer/bridge.ts b/packages/explorer/src/observer/bridge.ts index 1f493b733c..d29cfa4956 100644 --- a/packages/explorer/src/observer/bridge.ts +++ b/packages/explorer/src/observer/bridge.ts @@ -20,69 +20,67 @@ export function wrapMessage(data: unknown): BridgeEnvelope { return { mud: "explorer/observer", data }; } -const emits = new Map(); - export type CreateBridgeOpts = { url: string; timeout?: number; }; export function createBridge({ url, timeout = 10_000 }: CreateBridgeOpts): EmitMessage { - const priorEmit = emits.get(url); - if (priorEmit) return priorEmit; - - const deferredEmit = Promise.withResolvers(); - const iframe = document.createElement("iframe"); - iframe.tabIndex = -1; - iframe.ariaHidden = "true"; - iframe.style.position = "absolute"; - iframe.style.border = "0"; - iframe.style.width = "0"; - iframe.style.height = "0"; - - iframe.addEventListener( - "load", - () => { - debug("observer iframe ready", iframe.src); - // TODO: throw if `iframe.contentWindow` is `null`? - deferredEmit.resolve((type, data) => { - const message = wrapMessage({ ...data, type, time: Date.now() }); - debug("posting message to bridge", message); - iframe.contentWindow!.postMessage(message, "*"); - }); - }, - { once: true }, - ); + const bridge = new Promise((resolve, reject) => { + const iframe = + Array.from(document.querySelectorAll("iframe[data-bridge][src]")) + .filter((el): el is HTMLIFrameElement => true) + .find((el) => el.src === url) ?? document.createElement("iframe"); - iframe.addEventListener( - "error", - (error) => { - debug("observer iframe error", error); - deferredEmit.reject(error); - }, - { once: true }, - ); - - // TODO: should we let the caller handle this with their own promise timeout or race? - wait(timeout).then(() => { - deferredEmit.reject(new Error("Timed out waiting for observer iframe to load.")); - }); + if (iframe.dataset.bridge === "ready") { + debug("reusing observer iframe", iframe.src); + return resolve(iframe); + } - debug("mounting observer iframe", url); - iframe.src = url; - parent.document.body.appendChild(iframe); + Promise.race([ + new Promise((resolve, reject) => { + iframe.addEventListener("load", () => resolve(), { once: true }); + iframe.addEventListener("error", (error) => reject(error), { once: true }); + }), + wait(timeout).then(() => { + throw new Error("Timed out waiting for observer iframe to load."); + }), + ]).then( + () => { + debug("observer iframe ready", iframe.src); + iframe.dataset.bridge = "ready"; + resolve(iframe); + }, + (error) => { + debug("observer iframe error", error); + iframe.remove(); + reject(error); + }, + ); - deferredEmit.promise.catch(() => { - iframe.remove(); + if (iframe.dataset.bridge !== "loading") { + iframe.tabIndex = -1; + iframe.ariaHidden = "true"; + iframe.style.position = "absolute"; + iframe.style.border = "0"; + iframe.style.width = "0"; + iframe.style.height = "0"; + iframe.dataset.bridge = "loading"; + iframe.src = url; + debug("mounting observer iframe", url); + parent.document.body.appendChild(iframe); + } }); - const emit: EmitMessage = (messageType, message) => { - debug("got message for bridge", messageType, message); - deferredEmit.promise.then( - (fn) => fn(messageType, message), - (error) => debug("could not deliver message", message, error), + return (type, data) => { + debug("got message for bridge", type, data); + bridge.then( + (iframe) => { + debug("posting message to bridge", type, data); + const message = wrapMessage({ type, time: Date.now(), ...data }); + iframe.contentWindow!.postMessage(message, "*"); + }, + (error) => debug("could not deliver message", type, data, error), ); }; - emits.set(url, emit); - return emit; } diff --git a/packages/explorer/src/observer/decorator.ts b/packages/explorer/src/observer/decorator.ts index ee7e4a7e58..f23c15c203 100644 --- a/packages/explorer/src/observer/decorator.ts +++ b/packages/explorer/src/observer/decorator.ts @@ -11,6 +11,8 @@ export type ObserverOptions = { waitForStateChange?: WaitForStateChange; }; +let writeCounter = 0; + export function observer({ explorerUrl = "http://localhost:13690", waitForStateChange, @@ -19,54 +21,47 @@ export function observer Pick, "writeContract"> { const emit = createBridge({ url: `${explorerUrl}/internal/observer-relay` }); - setInterval(() => { - emit("ping", {}); - }, 2000); + return (client) => ({ + async writeContract(args) { + const writeId = `${client.uid}-${++writeCounter}`; + const write = getAction(client, writeContract, "writeContract")(args); - return (client) => { - let counter = 0; - return { - async writeContract(args) { - const writeId = `${client.uid}-${++counter}`; - const write = getAction(client, writeContract, "writeContract")(args); + // `writeContract` above will throw if this isn't present + const functionAbiItem = getAbiItem({ + abi: args.abi, + name: args.functionName, + args: args.args, + } as never)!; - // `writeContract` above will throw if this isn't present - const functionAbiItem = getAbiItem({ - abi: args.abi, - name: args.functionName, - args: args.args, - } as never)!; + emit("write", { + writeId, + address: args.address, + functionSignature: formatAbiItem(functionAbiItem), + args: (args.args ?? []) as never, + }); + Promise.allSettled([write]).then(([result]) => { + emit("write:result", { ...result, writeId }); + }); - emit("write", { - writeId, - address: args.address, - functionSignature: formatAbiItem(functionAbiItem), - args: (args.args ?? []) as never, - }); - Promise.allSettled([write]).then(([result]) => { - emit("write:result", { ...result, writeId }); + write.then((hash) => { + const receipt = getAction(client, waitForTransactionReceipt, "waitForTransactionReceipt")({ hash }); + emit("waitForTransactionReceipt", { writeId }); + Promise.allSettled([receipt]).then(([result]) => { + emit("waitForTransactionReceipt:result", { ...result, writeId }); }); + }); + if (waitForStateChange) { write.then((hash) => { - const receipt = getAction(client, waitForTransactionReceipt, "waitForTransactionReceipt")({ hash }); - emit("waitForTransactionReceipt", { writeId }); + const receipt = waitForStateChange(hash); + emit("waitForStateChange", { writeId }); Promise.allSettled([receipt]).then(([result]) => { - emit("waitForTransactionReceipt:result", { ...result, writeId }); + emit("waitForStateChange:result", { ...result, writeId }); }); }); + } - if (waitForStateChange) { - write.then((hash) => { - const receipt = waitForStateChange(hash); - emit("waitForStateChange", { writeId }); - Promise.allSettled([receipt]).then(([result]) => { - emit("waitForStateChange:result", { ...result, writeId }); - }); - }); - } - - return write; - }, - }; - }; + return write; + }, + }); } From f985eb2d2cb4d5dd9d66b7f42c3a9fc858544c88 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 19 Sep 2024 02:30:58 -0700 Subject: [PATCH 09/11] Create chilly-readers-heal.md --- .changeset/chilly-readers-heal.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/chilly-readers-heal.md diff --git a/.changeset/chilly-readers-heal.md b/.changeset/chilly-readers-heal.md new file mode 100644 index 0000000000..d5470a3ada --- /dev/null +++ b/.changeset/chilly-readers-heal.md @@ -0,0 +1,5 @@ +--- +"@latticexyz/explorer": patch +--- + +Refactored `observer` initialization to reuse bridge iframes with the same `url`. From b4f5d61639681570815ef8cefcf270286279e7d1 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 19 Sep 2024 02:31:12 -0700 Subject: [PATCH 10/11] Update unlucky-icons-wonder.md --- .changeset/unlucky-icons-wonder.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/.changeset/unlucky-icons-wonder.md b/.changeset/unlucky-icons-wonder.md index b27c39e971..653142c8e4 100644 --- a/.changeset/unlucky-icons-wonder.md +++ b/.changeset/unlucky-icons-wonder.md @@ -8,5 +8,3 @@ Fixed an issue where the `observer` Viem client decorator required an empty obje -client.extend(observer({})); +client.extend(observer()); ``` - -Also fixed a few issues where we were incorrectly redirecting based on the chain name or ID. From 077930197c8cb482a4ded0f54025b88402835279 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 19 Sep 2024 02:31:54 -0700 Subject: [PATCH 11/11] Create mean-radios-search.md --- .changeset/mean-radios-search.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/mean-radios-search.md diff --git a/.changeset/mean-radios-search.md b/.changeset/mean-radios-search.md new file mode 100644 index 0000000000..146888fb1f --- /dev/null +++ b/.changeset/mean-radios-search.md @@ -0,0 +1,5 @@ +--- +"@latticexyz/explorer": patch +--- + +Fixed favicon paths and fixed a few issues where we were incorrectly redirecting based on the chain name or ID.