Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Dashboard] Feature: UI for crosschain modules #5399

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
fe50d84
UI for crosschain modules
GWSzeto Nov 8, 2024
d14ec4b
Merge remote-tracking branch 'origin/main' into crosschain-ui
GWSzeto Nov 25, 2024
d442dfe
Merge remote-tracking branch 'origin/main' into crosschain-ui
GWSzeto Nov 26, 2024
c4acff6
Merge remote-tracking branch 'origin/main' into crosschain-ui
GWSzeto Nov 27, 2024
9fedac3
WIP: hardcoded modified version of TWCloneFactory to be used
GWSzeto Nov 27, 2024
4595d58
WIP: TODO's created to fetch for ContractInitialized event and update…
GWSzeto Nov 30, 2024
d08b33c
WIP: Testing out deploying bare core contract with modules installed …
GWSzeto Dec 4, 2024
9f5289b
Merge remote-tracking branch 'origin/main' into crosschain-ui
GWSzeto Dec 4, 2024
1f74bf6
WIP: staging to get help
GWSzeto Dec 5, 2024
3248212
successfully deploys using the initialize data from the event
GWSzeto Dec 6, 2024
6b9dd33
able to install modules successfully when crosschain deploying
GWSzeto Dec 6, 2024
64ca579
updated to be promise.allSettled when installing modules to not block…
GWSzeto Dec 6, 2024
9143857
removing console logs
GWSzeto Dec 6, 2024
051028b
fixing liniting issues
GWSzeto Dec 6, 2024
97e6b99
fixed linting issues
GWSzeto Dec 6, 2024
55158b4
created isSuperchain and isCrosschain flag in favour of case switch s…
GWSzeto Dec 9, 2024
67db04d
god bless greg it works
GWSzeto Dec 10, 2024
f6ee6a2
Merge remote-tracking branch 'origin/main' into crosschain-ui
GWSzeto Dec 10, 2024
828c198
addressed comments in PR
GWSzeto Dec 11, 2024
429d10c
updated wording
GWSzeto Dec 11, 2024
71b8a97
updated superchain bridge address
GWSzeto Dec 11, 2024
3cc27b6
Merge remote-tracking branch 'origin/main' into crosschain-ui
GWSzeto Dec 13, 2024
1a7f7cb
updated to show links
GWSzeto Dec 14, 2024
ec1a0a1
implemented cross chain transfers
GWSzeto Dec 17, 2024
52c3f44
tweaked input + button on crosschain transfer for OP Interop
GWSzeto Dec 17, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ export function getContractPageSidebarLinks(data: {
hide: !data.metadata.isModularCore,
exactMatch: true,
},
{
label: "Cross Chain",
href: `${layoutPrefix}/cross-chain`,
hide: !data.metadata.isModularCore,
exactMatch: true,
},
{
label: "Code Snippets",
href: `${layoutPrefix}/code`,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
"use client";

import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { getThirdwebClient } from "@/constants/thirdweb.server";
import {
type ColumnDef,
flexRender,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table";
import { verifyContract } from "app/(dashboard)/(chain)/[chain_id]/[contractAddress]/sources/ContractSourcesPage";
import {
type DeployModalStep,
DeployStatusModal,
useDeployStatusModal,
} from "components/contract-components/contract-deploy-form/deploy-context-modal";
import { useTxNotifications } from "hooks/useTxNotifications";
import { defineChain } from "thirdweb";
import type { FetchDeployMetadataResult } from "thirdweb/contract";
import { deployContractfromDeployMetadata } from "thirdweb/deploys";
import { useActiveAccount, useSwitchActiveWalletChain } from "thirdweb/react";
import { concatHex, padHex } from "thirdweb/utils";

export type CrossChain = {
id: number;
network: string;
chainId: number;
status: "DEPLOYED" | "NOT_DEPLOYED";
};

export function DataTable({
data,
coreMetadata,
modulesMetadata,
initializerCalldata,
}: {
data: CrossChain[];
coreMetadata: FetchDeployMetadataResult;
modulesMetadata: FetchDeployMetadataResult[];
initializerCalldata: `0x${string}`;
}) {
const activeAccount = useActiveAccount();
const switchChain = useSwitchActiveWalletChain();
const deployStatusModal = useDeployStatusModal();
const { onError } = useTxNotifications(
"Successfully deployed contract",
"Failed to deploy contract",
);

const columns: ColumnDef<CrossChain>[] = [
{
accessorKey: "network",
header: "Network",
},
{
accessorKey: "chainId",
header: "Chain ID",
},
{
accessorKey: "status",
header: "Status",
cell: ({ row }) => {
if (row.getValue("status") === "DEPLOYED") {
return <Badge variant="success">Deployed</Badge>;
}
return (
<Button onClick={() => deployContract(row.getValue("chainId"))}>
Deploy
</Button>
);
},
},
];

const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
});

const deployContract = async (chainId: number) => {
try {
if (!activeAccount) {
throw new Error("No active account");
}

// TODO: deploy the core contract directly with the initializer calldata

const chain = defineChain(chainId);
const client = getThirdwebClient();
const salt = concatHex(["0x0101", padHex("0x", { size: 30 })]).toString();

await switchChain(chain);

const steps: DeployModalStep[] = [
{
type: "deploy",
signatureCount: 1,
},
];

deployStatusModal.setViewContractLink("");
deployStatusModal.open(steps);

const crosschainContractAddress = await deployContractfromDeployMetadata({
account: activeAccount,
chain,
client,
deployMetadata: coreMetadata,
initializeParams: coreInitializeParams,
salt,
modules: modulesMetadata.map((m) => ({
deployMetadata: m,
initializeParams: moduleInitializeParams[m.name],
})),
});

await verifyContract({
address: crosschainContractAddress,
chain,
client,
});

deployStatusModal.nextStep();
deployStatusModal.setViewContractLink(
`/${chain.id}/${crosschainContractAddress}`,
);
} catch (e) {
onError(e);
console.error("failed to deploy contract", e);
deployStatusModal.close();
}
};

return (
<TableContainer>
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
<DeployStatusModal deployStatusModal={deployStatusModal} />
</TableContainer>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { fetchPublishedContractsFromDeploy } from "components/contract-components/fetchPublishedContractsFromDeploy";
import { notFound, redirect } from "next/navigation";
import { readContract } from "thirdweb";
import { getContractEvents, prepareEvent } from "thirdweb";
import { defineChain, getChainMetadata, localhost } from "thirdweb/chains";
import { type FetchDeployMetadataResult, getContract } from "thirdweb/contract";
import { getInstalledModules } from "thirdweb/modules";
import { eth_getCode, getRpcClient } from "thirdweb/rpc";
import { getContractPageParamsInfo } from "../_utils/getContractFromParams";
import { getContractPageMetadata } from "../_utils/getContractPageMetadata";
import { DataTable } from "./data-table";

export function getModuleInstallParams(mod: FetchDeployMetadataResult) {
return (
mod.abi
.filter((a) => a.type === "function")
.find((f) => f.name === "encodeBytesOnInstall")?.inputs || []
);
}

export default async function Page(props: {
params: Promise<{
contractAddress: string;
chain_id: string;
}>;
}) {
const params = await props.params;
const info = await getContractPageParamsInfo(params);

if (!info) {
notFound();
}

const { contract } = info;

if (contract.chain.id === localhost.id) {
return <div>asd</div>;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace the placeholder asd with a descriptive message that communicates the localhost limitation to users, such as Cross-chain deployment is not available on localhost networks

Spotted by Graphite Reviewer

Is this helpful? React 👍 or 👎 to let us know.

}

const { isModularCore } = await getContractPageMetadata(contract);

if (!isModularCore) {
redirect(`/${params.chain_id}/${params.contractAddress}`);
}

const originalCode = await eth_getCode(
getRpcClient({
client: contract.client,
chain: contract.chain,
}),
{
address: contract.address,
},
);

const topOPStackTestnetChainIds = [
84532, // Base
11155420, // OP testnet
919, // Mode Network
111557560, // Cyber
999999999, // Zora
];

const chainsDeployedOn = await Promise.all(
topOPStackTestnetChainIds.map(async (chainId) => {
const chain = defineChain(chainId);
const chainMetadata = await getChainMetadata(chain);

const rpcRequest = getRpcClient({
client: contract.client,
chain,
});
const code = await eth_getCode(rpcRequest, {
address: params.contractAddress,
});

return {
id: chainId,
network: chainMetadata.name,
chainId: chain.id,
status:
code === originalCode
? ("DEPLOYED" as const)
: ("NOT_DEPLOYED" as const),
};
}),
);

const modules = await getInstalledModules({ contract });

const coreMetadata = (
await fetchPublishedContractsFromDeploy({
contract,
client: contract.client,
})
).at(-1) as FetchDeployMetadataResult;
const modulesMetadata = (await Promise.all(
modules.map(async (m) =>
(
await fetchPublishedContractsFromDeploy({
contract: getContract({
chain: contract.chain,
client: contract.client,
address: m.implementation,
}),
client: contract.client,
})
).at(-1),
),
)) as FetchDeployMetadataResult[];

const _erc20InitialData = await Promise.all([
readContract({
contract: contract,
method: "function name() view returns (string)",
}),
readContract({
contract: contract,
method: "function symbol() view returns (string)",
}),
readContract({
contract: contract,
method: "function contractURI() view returns (string)",
}),
readContract({
contract: contract,
method: "function owner() view returns (address)",
}),
]);

const ProxyDeployedEvent = prepareEvent({
signature:
"event ProxyDeployed(address indexed implementation, address proxy, address indexed deployer, bytes data)",
});

// TODO: figure out how to fetch the events properly
const [event] = await getContractEvents({
contract,
events: [ProxyDeployedEvent],
blockRange: 123456n,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decide on what an appropriate way is to parse for this event

});

return (
<DataTable
coreMetadata={coreMetadata}
modulesMetadata={modulesMetadata}
initializerCalldata={event?.args.data}
data={chainsDeployedOn}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ export const InstallModuleForm = (props: InstallModuleFormProps) => {
module: selectedModuleMeta,
isQueryEnabled: !!selectedModule && !!isModuleCompatibleQuery.data,
});
console.log("moduleInstallParams", moduleInstallParams.data);

return (
<FormProvider {...form}>
Expand Down
Loading
Loading