Skip to content

Commit 77029a2

Browse files
authored
Interop module support (#6325)
1 parent b0b3b7f commit 77029a2

File tree

7 files changed

+205
-34
lines changed

7 files changed

+205
-34
lines changed

apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx

Lines changed: 138 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
"use client";
22

33
import { Button } from "@/components/ui/button";
4-
import { Form } from "@/components/ui/form";
4+
import {
5+
Form,
6+
FormControl,
7+
FormField,
8+
FormItem,
9+
FormMessage,
10+
} from "@/components/ui/form";
11+
import { Input } from "@/components/ui/input";
512
import {
613
Table,
714
TableBody,
@@ -11,6 +18,7 @@ import {
1118
TableHeader,
1219
TableRow,
1320
} from "@/components/ui/table";
21+
import { ToolTipLabel } from "@/components/ui/tooltip";
1422
import { getThirdwebClient } from "@/constants/thirdweb.server";
1523
import { zodResolver } from "@hookform/resolvers/zod";
1624
import { useMutation } from "@tanstack/react-query";
@@ -33,7 +41,9 @@ import { useForm } from "react-hook-form";
3341
import {
3442
defineChain,
3543
eth_getCode,
44+
getContract,
3645
getRpcClient,
46+
prepareContractCall,
3747
prepareTransaction,
3848
sendAndConfirmTransaction,
3949
} from "thirdweb";
@@ -57,19 +67,21 @@ type CrossChain = {
5767
status: "DEPLOYED" | "NOT_DEPLOYED";
5868
};
5969

70+
const interopChains = ["420120000", "420120001"];
71+
72+
type ChainId = "420120000" | "420120001";
73+
6074
const formSchema = z.object({
6175
amounts: z.object({
62-
"84532": z.string(),
63-
"11155420": z.string(),
64-
"919": z.string(),
65-
"111557560": z.string(),
66-
"999999999": z.string(),
67-
"11155111": z.string(),
68-
"421614": z.string(),
76+
"420120000": z.string(),
77+
"420120001": z.string(),
6978
}),
7079
});
7180
type FormSchema = z.output<typeof formSchema>;
7281

82+
const positiveIntegerRegex = /^[0-9]\d*$/;
83+
const superchainBridgeAddress = "0x4200000000000000000000000000000000000028";
84+
7385
export function DataTable({
7486
data,
7587
coreMetadata,
@@ -97,13 +109,21 @@ export function DataTable({
97109
"Failed to deploy contract",
98110
);
99111

112+
const isCrosschain = !!modulesMetadata?.find(
113+
(m) => m.name === "SuperChainInterop",
114+
);
115+
100116
const addRowMutation = useMutation({
101117
mutationFn: async (chain: { chainId: number; name: string }) => {
118+
if (coreContract.chain.id === chain.chainId) {
119+
return;
120+
}
121+
// eslint-disable-next-line no-restricted-syntax
122+
const c = defineChain(chain.chainId);
102123
const code = await eth_getCode(
103124
getRpcClient({
104125
client: getThirdwebClient(),
105-
// eslint-disable-next-line no-restricted-syntax
106-
chain: defineChain(chain.chainId),
126+
chain: c,
107127
}),
108128
{ address: coreContract.address },
109129
);
@@ -155,19 +175,59 @@ export function DataTable({
155175

156176
const form = useForm<FormSchema>({
157177
resolver: zodResolver(formSchema),
158-
values: {
178+
defaultValues: {
159179
amounts: {
160-
"84532": "", // Base
161-
"11155420": "", // OP testnet
162-
"919": "", // Mode Network
163-
"111557560": "", // Cyber
164-
"999999999": "", // Zora
165-
"11155111": "", // Sepolia
166-
"421614": "",
180+
"420120000": "",
181+
"420120001": "",
167182
},
168183
},
169184
});
170185

186+
const crossChainTransfer = async (chainId: ChainId) => {
187+
if (!activeAccount) {
188+
throw new Error("Account not connected");
189+
}
190+
const amount = form.getValues().amounts[chainId];
191+
if (!positiveIntegerRegex.test(amount)) {
192+
form.setError(`amounts.${chainId}`, { message: "Invalid Amount" });
193+
return;
194+
}
195+
196+
const superChainBridge = getContract({
197+
address: superchainBridgeAddress,
198+
chain: coreContract.chain,
199+
client: coreContract.client,
200+
});
201+
202+
const sendErc20Tx = prepareContractCall({
203+
contract: superChainBridge,
204+
method:
205+
"function sendERC20(address _token, address _to, uint256 _amount, uint256 _chainId)",
206+
params: [
207+
coreContract.address,
208+
activeAccount.address,
209+
BigInt(amount),
210+
BigInt(chainId),
211+
],
212+
});
213+
214+
await sendAndConfirmTransaction({
215+
account: activeAccount,
216+
transaction: sendErc20Tx,
217+
});
218+
};
219+
220+
const crossChainTransferNotifications = useTxNotifications(
221+
"Successfully submitted cross chain transfer",
222+
"Failed to submit cross chain transfer",
223+
);
224+
225+
const crossChainTransferMutation = useMutation({
226+
mutationFn: crossChainTransfer,
227+
onSuccess: crossChainTransferNotifications.onSuccess,
228+
onError: crossChainTransferNotifications.onError,
229+
});
230+
171231
const columns: ColumnDef<CrossChain>[] = [
172232
{
173233
accessorKey: "network",
@@ -208,6 +268,54 @@ export function DataTable({
208268
);
209269
},
210270
},
271+
{
272+
accessorKey: "transfer",
273+
header: "",
274+
cell: ({ row }) => {
275+
const chain = row.getValue("chainId");
276+
if (
277+
row.getValue("status") === "DEPLOYED" &&
278+
interopChains.includes(String(chain)) &&
279+
isCrosschain
280+
) {
281+
return (
282+
<FormField
283+
disabled={false}
284+
control={form.control}
285+
name={`amounts.${row.getValue("chainId") as ChainId}`}
286+
render={({ field }) => (
287+
<FormItem>
288+
<FormControl>
289+
<ToolTipLabel label="Bridge tokens">
290+
<div className="flex">
291+
<Input
292+
className="w-22 rounded-r-none border-r-0"
293+
placeholder="amount"
294+
{...field}
295+
/>
296+
<Button
297+
type="button"
298+
disabled={false}
299+
onClick={() =>
300+
crossChainTransferMutation.mutate(
301+
row.getValue("chainId"),
302+
)
303+
}
304+
className="rounded-lg rounded-l-none border border-l-0"
305+
>
306+
Bridge
307+
</Button>
308+
</div>
309+
</ToolTipLabel>
310+
</FormControl>
311+
<FormMessage />
312+
</FormItem>
313+
)}
314+
/>
315+
);
316+
}
317+
},
318+
},
211319
];
212320

213321
const table = useReactTable({
@@ -268,6 +376,18 @@ export function DataTable({
268376
crosschainContractAddress = coreContract.address;
269377
}
270378
} else {
379+
if (modulesMetadata) {
380+
for (const m of modulesMetadata) {
381+
await getOrDeployInfraForPublishedContract({
382+
chain,
383+
client,
384+
account: activeAccount,
385+
contractId: m.name,
386+
publisher: m.publisher,
387+
});
388+
}
389+
}
390+
271391
crosschainContractAddress = await deployContractfromDeployMetadata({
272392
account: activeAccount,
273393
chain,
@@ -283,17 +403,6 @@ export function DataTable({
283403
chain,
284404
client,
285405
});
286-
if (modulesMetadata) {
287-
for (const m of modulesMetadata) {
288-
await getOrDeployInfraForPublishedContract({
289-
chain,
290-
client,
291-
account: activeAccount,
292-
contractId: m.name,
293-
publisher: m.publisher,
294-
});
295-
}
296-
}
297406
}
298407
deployStatusModal.nextStep();
299408
deployStatusModal.setViewContractLink(

apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { fetchPublishedContractsFromDeploy } from "components/contract-component
22
import { notFound } from "next/navigation";
33
import {
44
type ContractOptions,
5+
eth_blockNumber,
56
eth_getTransactionByHash,
67
eth_getTransactionReceipt,
78
getContractEvents,
@@ -110,10 +111,16 @@ export default async function Page(props: {
110111
let creationBlockNumber: bigint | undefined;
111112

112113
if (twCloneFactoryContract) {
114+
const latestBlockNumber = await eth_blockNumber(
115+
getRpcClient({
116+
client: contract.client,
117+
chain: contract.chain,
118+
}),
119+
);
113120
const events = await getContractEvents({
114121
contract: twCloneFactoryContract,
115122
events: [ProxyDeployedEvent],
116-
blockRange: 123456n,
123+
blockRange: latestBlockNumber < 2000000n ? latestBlockNumber : 2000000n,
117124
});
118125
const event = events.find(
119126
(e) =>
@@ -165,7 +172,7 @@ export default async function Page(props: {
165172
}
166173
}),
167174
)
168-
).filter((c) => c.status === "DEPLOYED");
175+
).filter((c) => c.chainId !== contract.chain.id);
169176

170177
let coreMetadata: FetchDeployMetadataResult | undefined;
171178
try {
@@ -260,10 +267,11 @@ export default async function Page(props: {
260267
<>
261268
<div>
262269
<h2 className="mb-1 font-bold text-2xl tracking-tight">
263-
Deploy Cross-chain
270+
Cross-chain contracts
264271
</h2>
265272
<p className="text-muted-foreground">
266-
Deterministically deploy your contracts on multiple networks.
273+
Deterministically deploy and interact with your contracts on multiple
274+
networks.
267275
</p>
268276
</div>
269277
<div className="h-10" />

apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import {
6262
getModuleInstallParams,
6363
showPrimarySaleFieldset,
6464
showRoyaltyFieldset,
65+
showSuperchainBridgeFieldset,
6566
} from "./modular-contract-default-modules-fieldset";
6667
import { Param } from "./param";
6768
import { PlatformFeeFieldset } from "./platform-fee-fieldset";
@@ -294,6 +295,9 @@ export const CustomContractForm: React.FC<CustomContractFormProps> = ({
294295
// set connected wallet address as default "primarySaleRecipient"
295296
else if (showPrimarySaleFieldset(paramNames)) {
296297
returnVal.primarySaleRecipient = activeAccount.address;
298+
} else if (showSuperchainBridgeFieldset(paramNames)) {
299+
returnVal.superchainBridge =
300+
"0x4200000000000000000000000000000000000028";
297301
}
298302

299303
acc[mod.name] = returnVal;

apps/dashboard/src/components/contract-components/contract-deploy-form/modular-contract-default-modules-fieldset.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,7 @@ export function showPrimarySaleFieldset(paramNames: string[]) {
226226
function showSequentialTokenIdFieldset(paramNames: string[]) {
227227
return paramNames.length === 1 && paramNames.includes("startTokenId");
228228
}
229+
230+
export function showSuperchainBridgeFieldset(paramNames: string[]) {
231+
return paramNames.length === 1 && paramNames.includes("superchainBridge");
232+
}

apps/dashboard/src/components/explore/contract-card/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ export async function ContractCard({
114114
<div className="flex justify-between">
115115
<div className="flex items-center gap-1.5">
116116
{/* Audited */}
117-
{auditLink && (
117+
{auditLink && !isBeta && (
118118
<>
119119
<Link
120120
target="_blank"

apps/dashboard/src/data/explore.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,43 @@ const MODULAR_CONTRACTS = {
192192
],
193193
} satisfies ExploreCategory;
194194

195+
const SUPERCHAIN = {
196+
id: "modular-superchain-contracts",
197+
name: "Modular Superchain Contracts",
198+
displayName: "Modular Superchain Contracts",
199+
description: "Modular contracts with OP Superchain support",
200+
contracts: [
201+
// erc20 token + superchain
202+
[
203+
"thirdweb.eth/ERC20CoreInitializable",
204+
[
205+
"deployer.thirdweb.eth/MintableERC20",
206+
"deployer.thirdweb.eth/TransferableERC20",
207+
"deployer.thirdweb.eth/SuperChainInterop",
208+
],
209+
{
210+
title: "Modular Superchain Token",
211+
description: "ERC20 Tokens that only owners can mint.",
212+
},
213+
],
214+
// erc20 drop + superchain
215+
[
216+
"thirdweb.eth/ERC20CoreInitializable",
217+
[
218+
"deployer.thirdweb.eth/ClaimableERC20",
219+
"deployer.thirdweb.eth/TransferableERC20",
220+
"deployer.thirdweb.eth/SuperChainInterop",
221+
],
222+
{
223+
title: "Modular Superchain Token Drop",
224+
description: "ERC20 Tokens that others can mint.",
225+
},
226+
],
227+
],
228+
showInExplore: true,
229+
isBeta: true,
230+
} satisfies ExploreCategory;
231+
195232
const AIRDROP = {
196233
id: "airdrop",
197234
name: "Airdrop",
@@ -281,6 +318,7 @@ const SMART_WALLET = {
281318
const CATEGORIES: Record<string, ExploreCategory> = {
282319
[POPULAR.id]: POPULAR,
283320
[MODULAR_CONTRACTS.id]: MODULAR_CONTRACTS,
321+
[SUPERCHAIN.id]: SUPERCHAIN,
284322
[NFTS.id]: NFTS,
285323
[MARKETS.id]: MARKETS,
286324
[DROPS.id]: DROPS,

0 commit comments

Comments
 (0)