Skip to content

Commit fe50d84

Browse files
committed
UI for crosschain modules
1 parent a1707f6 commit fe50d84

File tree

6 files changed

+445
-5
lines changed

6 files changed

+445
-5
lines changed

apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/getContractPageSidebarLinks.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ export function getContractPageSidebarLinks(data: {
2424
hide: !data.metadata.isModularCore,
2525
exactMatch: true,
2626
},
27+
{
28+
label: "Cross Chain",
29+
href: `${layoutPrefix}/cross-chain`,
30+
hide: !data.metadata.isModularCore,
31+
exactMatch: true,
32+
},
2733
{
2834
label: "Code Snippets",
2935
href: `${layoutPrefix}/code`,
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
"use client";
2+
3+
import { Badge } from "@/components/ui/badge";
4+
import { Button } from "@/components/ui/button";
5+
import {
6+
Table,
7+
TableBody,
8+
TableCell,
9+
TableContainer,
10+
TableHead,
11+
TableHeader,
12+
TableRow,
13+
} from "@/components/ui/table";
14+
import { getThirdwebClient } from "@/constants/thirdweb.server";
15+
import {
16+
type ColumnDef,
17+
flexRender,
18+
getCoreRowModel,
19+
useReactTable,
20+
} from "@tanstack/react-table";
21+
import { verifyContract } from "app/(dashboard)/(chain)/[chain_id]/[contractAddress]/sources/ContractSourcesPage";
22+
import {
23+
type DeployModalStep,
24+
DeployStatusModal,
25+
useDeployStatusModal,
26+
} from "components/contract-components/contract-deploy-form/deploy-context-modal";
27+
import {
28+
getModuleInstallParams,
29+
showPrimarySaleFiedset,
30+
showRoyaltyFieldset,
31+
showSuperchainBridgeFieldset,
32+
} from "components/contract-components/contract-deploy-form/modular-contract-default-modules-fieldset";
33+
import { useTxNotifications } from "hooks/useTxNotifications";
34+
import { replaceTemplateValues } from "lib/deployment/template-values";
35+
import { ZERO_ADDRESS, defineChain } from "thirdweb";
36+
import type { FetchDeployMetadataResult } from "thirdweb/contract";
37+
import { deployContractfromDeployMetadata } from "thirdweb/deploys";
38+
import { useActiveAccount, useSwitchActiveWalletChain } from "thirdweb/react";
39+
40+
export type CrossChain = {
41+
id: number;
42+
network: string;
43+
chainId: number;
44+
status: "DEPLOYED" | "NOT_DEPLOYED";
45+
};
46+
47+
export function DataTable({
48+
data,
49+
coreMetadata,
50+
modulesMetadata,
51+
erc20InitialData: [_name, _symbol, _contractURI, _owner],
52+
}: {
53+
data: CrossChain[];
54+
coreMetadata: FetchDeployMetadataResult;
55+
modulesMetadata: FetchDeployMetadataResult[];
56+
erc20InitialData: string[];
57+
}) {
58+
const activeAccount = useActiveAccount();
59+
const switchChain = useSwitchActiveWalletChain();
60+
const deployStatusModal = useDeployStatusModal();
61+
const { onError } = useTxNotifications(
62+
"Successfully deployed contract",
63+
"Failed to deploy contract",
64+
);
65+
66+
const columns: ColumnDef<CrossChain>[] = [
67+
{
68+
accessorKey: "network",
69+
header: "Network",
70+
},
71+
{
72+
accessorKey: "chainId",
73+
header: "Chain ID",
74+
},
75+
{
76+
accessorKey: "status",
77+
header: "Status",
78+
cell: ({ row }) => {
79+
if (row.getValue("status") === "DEPLOYED") {
80+
return <Badge variant="success">Deployed</Badge>;
81+
}
82+
return (
83+
<Button onClick={() => deployContract(row.getValue("chainId"))}>
84+
Deploy
85+
</Button>
86+
);
87+
},
88+
},
89+
];
90+
91+
const table = useReactTable({
92+
data,
93+
columns,
94+
getCoreRowModel: getCoreRowModel(),
95+
});
96+
97+
const deployContract = async (chainId: number) => {
98+
try {
99+
if (!activeAccount) {
100+
throw new Error("No active account");
101+
}
102+
103+
const coreInitializeParams = {
104+
...(
105+
coreMetadata.abi
106+
.filter((a) => a.type === "function")
107+
.find((a) => a.name === "initialize")?.inputs || []
108+
).reduce(
109+
(acc, param) => {
110+
if (!param.name) {
111+
param.name = "*";
112+
}
113+
114+
acc[param.name] = replaceTemplateValues(
115+
coreMetadata?.constructorParams?.[param.name]?.defaultValue
116+
? coreMetadata?.constructorParams?.[param.name]?.defaultValue ||
117+
""
118+
: param.name === "_royaltyBps" ||
119+
param.name === "_platformFeeBps"
120+
? "0"
121+
: "",
122+
param.type,
123+
{
124+
connectedWallet: "0x0000000000000000000000000000000000000000",
125+
chainId: 1,
126+
},
127+
);
128+
129+
return acc;
130+
},
131+
{} as Record<string, string>,
132+
),
133+
_name,
134+
_symbol,
135+
_contractURI,
136+
_owner,
137+
};
138+
139+
const moduleInitializeParams = modulesMetadata.reduce(
140+
(acc, mod) => {
141+
const params = getModuleInstallParams(mod);
142+
const paramNames = params
143+
.map((param) => param.name)
144+
.filter((p) => p !== undefined);
145+
const returnVal: Record<string, string> = {};
146+
147+
// set connected wallet address as default "royaltyRecipient"
148+
if (showRoyaltyFieldset(paramNames)) {
149+
returnVal.royaltyRecipient = _owner || "";
150+
returnVal.royaltyBps = "0";
151+
returnVal.transferValidator = ZERO_ADDRESS;
152+
}
153+
154+
// set connected wallet address as default "primarySaleRecipient"
155+
else if (showPrimarySaleFiedset(paramNames)) {
156+
returnVal.primarySaleRecipient = _owner || "";
157+
}
158+
159+
// set superchain bridge address
160+
else if (showSuperchainBridgeFieldset(paramNames)) {
161+
returnVal.superchainBridge =
162+
"0x4200000000000000000000000000000000000010"; // OP Superchain Bridge
163+
}
164+
165+
acc[mod.name] = returnVal;
166+
return acc;
167+
},
168+
{} as Record<string, Record<string, string>>,
169+
);
170+
171+
const chain = defineChain(chainId);
172+
const client = getThirdwebClient();
173+
const salt = "thirdweb";
174+
175+
await switchChain(chain);
176+
177+
const steps: DeployModalStep[] = [
178+
{
179+
type: "deploy",
180+
signatureCount: 1,
181+
},
182+
];
183+
184+
deployStatusModal.setViewContractLink("");
185+
deployStatusModal.open(steps);
186+
187+
const crosschainContractAddress = await deployContractfromDeployMetadata({
188+
account: activeAccount,
189+
chain,
190+
client,
191+
deployMetadata: coreMetadata,
192+
initializeParams: coreInitializeParams,
193+
salt,
194+
modules: modulesMetadata.map((m) => ({
195+
deployMetadata: m,
196+
initializeParams: moduleInitializeParams[m.name],
197+
})),
198+
});
199+
200+
await verifyContract({
201+
address: crosschainContractAddress,
202+
chain,
203+
client,
204+
});
205+
206+
deployStatusModal.nextStep();
207+
deployStatusModal.setViewContractLink(
208+
`/${chain.id}/${crosschainContractAddress}`,
209+
);
210+
} catch (e) {
211+
onError(e);
212+
console.error("failed to deploy contract", e);
213+
deployStatusModal.close();
214+
}
215+
};
216+
217+
return (
218+
<TableContainer>
219+
<Table>
220+
<TableHeader>
221+
{table.getHeaderGroups().map((headerGroup) => (
222+
<TableRow key={headerGroup.id}>
223+
{headerGroup.headers.map((header) => {
224+
return (
225+
<TableHead key={header.id}>
226+
{header.isPlaceholder
227+
? null
228+
: flexRender(
229+
header.column.columnDef.header,
230+
header.getContext(),
231+
)}
232+
</TableHead>
233+
);
234+
})}
235+
</TableRow>
236+
))}
237+
</TableHeader>
238+
<TableBody>
239+
{table.getRowModel().rows.map((row) => (
240+
<TableRow
241+
key={row.id}
242+
data-state={row.getIsSelected() && "selected"}
243+
>
244+
{row.getVisibleCells().map((cell) => (
245+
<TableCell key={cell.id}>
246+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
247+
</TableCell>
248+
))}
249+
</TableRow>
250+
))}
251+
</TableBody>
252+
</Table>
253+
<DeployStatusModal deployStatusModal={deployStatusModal} />
254+
</TableContainer>
255+
);
256+
}

0 commit comments

Comments
 (0)