Skip to content

Commit

Permalink
feat(explorer): get observer url from chain config (#3366)
Browse files Browse the repository at this point in the history
Co-authored-by: alvarius <[email protected]>
  • Loading branch information
holic and alvrs authored Nov 7, 2024
1 parent 07b6be8 commit 88b9daf
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 89 deletions.
5 changes: 5 additions & 0 deletions .changeset/orange-houses-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@latticexyz/explorer": patch
---

Observer transport now uses the `blockExplorers.worldsExplorer.url` from the chain config if no `explorerUrl` is provided.
5 changes: 5 additions & 0 deletions .changeset/yellow-spoons-mate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@latticexyz/common": patch
---

Updated Rhodolite chain config.
27 changes: 25 additions & 2 deletions packages/common/src/chains/rhodolite.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { chainConfig } from "viem/op-stack";
import { MUDChain } from "./types";
import { Chain } from "viem";

const sourceId = 17001;

const defaultRpcUrls = {
http: ["https://rpc.rhodolitechain.com"],
webSocket: ["wss://rpc.rhodolitechain.com"],
} as const satisfies Chain["rpcUrls"]["default"];

export const rhodolite = {
...chainConfig,
name: "Rhodolite Devnet",
Expand All @@ -11,9 +17,26 @@ export const rhodolite = {
sourceId,
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
rpcUrls: {
default: defaultRpcUrls,
bundler: defaultRpcUrls,
quarryPassIssuer: defaultRpcUrls,
wiresaw: defaultRpcUrls,
},
contracts: {
...chainConfig.contracts,
quarryPaymaster: {
address: "0x61f22c3827d90c390e0e2aaf220971524ac0a68d",
blockCreated: 11262,
},
},
blockExplorers: {
default: {
http: ["https://rpc.rhodolitechain.com"],
webSocket: ["wss://rpc.rhodolitechain.com"],
name: "Blockscout",
url: "https://explorer.rhodolitechain.com",
},
worldsExplorer: {
name: "MUD Worlds Explorer",
url: "https://explorer.mud.dev/rhodolite/worlds",
},
},
iconUrls: ["https://redstone.xyz/chain-icons/rhodolite.png"],
Expand Down
181 changes: 94 additions & 87 deletions packages/explorer/src/observer/decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,106 +15,113 @@ export type ObserverOptions = {

let writeCounter = 0;

export function observer({ explorerUrl = "http://localhost:13690", waitForTransaction }: ObserverOptions = {}): <
export function observer({ explorerUrl: initialExplorerUrl, waitForTransaction }: ObserverOptions = {}): <
transport extends Transport,
chain extends Chain | undefined = Chain | undefined,
account extends Account | undefined = Account | undefined,
>(
client: Client<transport, chain, account>,
) => Pick<WalletActions<chain, account>, "writeContract"> & Pick<BundlerActions, "sendUserOperation"> {
const emit = createBridge({ url: `${explorerUrl}/internal/observer-relay` });

return (client) => ({
async sendUserOperation(args) {
const writeId = `${client.uid}-${++writeCounter}`;
const write = getAction(client, sendUserOperation, "sendUserOperation")(args);
if (!("calls" in args) || !args.calls) return write;

const calls = args.calls
.map((call) => {
if (!call || typeof call !== "object") return undefined;
if (!("to" in call) || typeof call.to !== "string") return undefined;
if (!("functionName" in call) || typeof call.functionName !== "string") return undefined;
if (!("args" in call) || !Array.isArray(call.args)) return undefined;

return {
to: call.to as Address,
functionName: call.functionName,
args: call.args,
...("value" in call && typeof call.value === "bigint" && { value: call.value }),
};
})
.filter(isDefined);
if (calls.length === 0) return write;

emit("write", {
writeId,
// TODO: type as SessionClient once available from entrykit
// eslint-disable-next-line @typescript-eslint/no-explicit-any
from: (client as any).userAddress ?? client.account!.address,
calls,
});
Promise.allSettled([write]).then(([result]) => {
emit("write:result", { ...result, writeId });
});

write.then((userOpHash) => {
const receipt = getAction(
client,
waitForUserOperationReceipt,
"waitForUserOperationReceipt",
)({ hash: userOpHash });

emit("waitForUserOperationReceipt", { writeId, userOpHash });
Promise.allSettled([receipt]).then(([result]) => {
emit("waitForUserOperationReceipt:result", { ...result, writeId });
return (client) => {
const explorerUrl = initialExplorerUrl ?? client.chain?.blockExplorers?.worldsExplorer?.url;
if (!explorerUrl) return {} as never;

const emit = createBridge({ url: new URL("/internal/observer-relay", explorerUrl).toString() });

return {
async sendUserOperation(args) {
const writeId = `${client.uid}-${++writeCounter}`;
const write = getAction(client, sendUserOperation, "sendUserOperation")(args);
if (!("calls" in args) || !args.calls) return write;

const calls = args.calls
.map((call) => {
if (!call || typeof call !== "object") return undefined;
if (!("to" in call) || typeof call.to !== "string") return undefined;
if (!("functionName" in call) || typeof call.functionName !== "string") return undefined;
if (!("args" in call) || !Array.isArray(call.args)) return undefined;

return {
to: call.to as Address,
functionName: call.functionName,
args: call.args,
...("value" in call && typeof call.value === "bigint" && { value: call.value }),
};
})
.filter(isDefined);
if (calls.length === 0) return write;

emit("write", {
writeId,
// TODO: type as SessionClient once available from entrykit
// eslint-disable-next-line @typescript-eslint/no-explicit-any
from: (client as any).userAddress ?? client.account!.address,
calls,
});
});

return write;
},

async writeContract(args) {
const writeId = `${client.uid}-${++writeCounter}`;
const write = getAction(client, writeContract, "writeContract")(args);

emit("write", {
writeId,
from: client.account!.address,
calls: [
{
to: args.address,
functionName: args.functionName,
args: (args.args ?? []) as never,
...(args.value && { value: args.value }),
},
],
});
Promise.allSettled([write]).then(([result]) => {
emit("write:result", { ...result, writeId });
});

write.then((hash) => {
const receipt = getAction(client, waitForTransactionReceipt, "waitForTransactionReceipt")({ hash });

emit("waitForTransactionReceipt", { writeId, hash });
Promise.allSettled([receipt]).then(([result]) => {
emit("waitForTransactionReceipt:result", { ...result, writeId });
Promise.allSettled([write]).then(([result]) => {
emit("write:result", { ...result, writeId });
});

write.then((userOpHash) => {
const receipt = getAction(
client,
waitForUserOperationReceipt,
"waitForUserOperationReceipt",
)({ hash: userOpHash });

emit("waitForUserOperationReceipt", { writeId, userOpHash });
Promise.allSettled([receipt]).then(([result]) => {
emit("waitForUserOperationReceipt:result", { ...result, writeId });
});
});

return write;
},

async writeContract(args) {
const writeId = `${client.uid}-${++writeCounter}`;
const write = getAction(client, writeContract, "writeContract")(args);

emit("write", {
writeId,
// TODO: type as SessionClient once available from entrykit
// eslint-disable-next-line @typescript-eslint/no-explicit-any
from: (client as any).userAddress ?? client.account!.address,
calls: [
{
to: args.address,
functionName: args.functionName,
args: (args.args ?? []) as never,
...(args.value && { value: args.value }),
},
],
});
Promise.allSettled([write]).then(([result]) => {
emit("write:result", { ...result, writeId });
});
});

if (waitForTransaction) {
write.then((hash) => {
const receipt = waitForTransaction(hash);
const receipt = getAction(client, waitForTransactionReceipt, "waitForTransactionReceipt")({ hash });

emit("waitForTransaction", { writeId });
emit("waitForTransactionReceipt", { writeId, hash });
Promise.allSettled([receipt]).then(([result]) => {
emit("waitForTransaction:result", { ...result, writeId });
emit("waitForTransactionReceipt:result", { ...result, writeId });
});
});
}

return write;
},
});
if (waitForTransaction) {
write.then((hash) => {
const receipt = waitForTransaction(hash);

emit("waitForTransaction", { writeId });
Promise.allSettled([receipt]).then(([result]) => {
emit("waitForTransaction:result", { ...result, writeId });
});
});
}

return write;
},
};
};
}

0 comments on commit 88b9daf

Please sign in to comment.