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 15 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,294 @@
"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 {
getModuleInstallParams,
showPrimarySaleFiedset,
showRoyaltyFieldset,
showSuperchainBridgeFieldset,
} from "components/contract-components/contract-deploy-form/modular-contract-default-modules-fieldset";
import { useTxNotifications } from "hooks/useTxNotifications";
import {
ZERO_ADDRESS,
defineChain,
getContract,
readContract,
sendTransaction,
waitForReceipt,
} from "thirdweb";
import type {
FetchDeployMetadataResult,
ThirdwebContract,
} from "thirdweb/contract";
import { deployContractfromDeployMetadata } from "thirdweb/deploys";
import { installPublishedModule } from "thirdweb/modules";
import { useActiveAccount, useSwitchActiveWalletChain } from "thirdweb/react";
import {
type AbiFunction,
concatHex,
encodeAbiParameters,
padHex,
} from "thirdweb/utils";

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

export function DataTable({
data,
coreMetadata,
coreContract,
modulesMetadata,
initializeData,
}: {
data: CrossChain[];
coreMetadata: FetchDeployMetadataResult;
coreContract: ThirdwebContract;
modulesMetadata: FetchDeployMetadataResult[];
initializeData?: `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");
}

// eslint-disable-next-line no-restricted-syntax
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,
deployType: "crosschain" as const,
},
initializeData,
salt,
});

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

const owner = await readContract({
contract: coreContract,
method: "function owner() view returns (address)",
params: [],
});

const moduleInitializeParams = modulesMetadata.reduce(
(acc, mod) => {
const params = getModuleInstallParams(mod);
const paramNames = params
.map((param) => param.name)
.filter((p) => p !== undefined);
const returnVal: Record<string, string> = {};

// set connected wallet address as default "royaltyRecipient"
if (showRoyaltyFieldset(paramNames)) {
returnVal.royaltyRecipient = owner || "";
returnVal.royaltyBps = "0";
returnVal.transferValidator = ZERO_ADDRESS;
}

// set connected wallet address as default "primarySaleRecipient"
else if (showPrimarySaleFiedset(paramNames)) {
returnVal.primarySaleRecipient = owner || "";
}

// set superchain bridge address
else if (showSuperchainBridgeFieldset(paramNames)) {
returnVal.superchainBridge =
"0x4200000000000000000000000000000000000010"; // OP Superchain Bridge
}

acc[mod.name] = returnVal;
return acc;
},
{} as Record<string, Record<string, string>>,
);

const moduleDeployData = modulesMetadata.map((m) => ({
deployMetadata: m,
initializeParams: moduleInitializeParams[m.name],
}));

const contract = getContract({
address: crosschainContractAddress,
chain,
client,
});

await Promise.all(
moduleDeployData.map(async (m) => {
let moduleData: `0x${string}` | undefined;

const moduleInstallParams = m.deployMetadata.abi.find(
(abiType) =>
(abiType as AbiFunction).name === "encodeBytesOnInstall",
) as AbiFunction | undefined;

if (m.initializeParams && moduleInstallParams) {
moduleData = encodeAbiParameters(
(
moduleInstallParams.inputs as { name: string; type: string }[]
).map((p) => ({
name: p.name,
type: p.type,
})),
Object.values(m.initializeParams),
);
}

const installTransaction = installPublishedModule({
contract,
account: activeAccount,
moduleName: m.deployMetadata.name,
publisher: m.deployMetadata.publisher,
version: m.deployMetadata.version,
moduleData,
});

const txResult = await sendTransaction({
transaction: installTransaction,
account: activeAccount,
});

return await waitForReceipt(txResult);
}),
);

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>
);
}
Loading
Loading