From fe50d84996f25c84709359770e8d237b67db8642 Mon Sep 17 00:00:00 2001 From: Stanley Date: Thu, 7 Nov 2024 20:07:42 -0500 Subject: [PATCH 01/19] UI for crosschain modules --- .../_utils/getContractPageSidebarLinks.ts | 6 + .../cross-chain/data-table.tsx | 256 ++++++++++++++++++ .../[contractAddress]/cross-chain/page.tsx | 144 ++++++++++ .../contract-deploy-form/custom-contract.tsx | 17 +- ...ular-contract-default-modules-fieldset.tsx | 4 + apps/dashboard/src/data/explore.ts | 23 ++ 6 files changed, 445 insertions(+), 5 deletions(-) create mode 100644 apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx create mode 100644 apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/getContractPageSidebarLinks.ts b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/getContractPageSidebarLinks.ts index 474b31d404b..2281ed405a4 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/getContractPageSidebarLinks.ts +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/getContractPageSidebarLinks.ts @@ -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`, diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx new file mode 100644 index 00000000000..6258913067c --- /dev/null +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx @@ -0,0 +1,256 @@ +"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 { replaceTemplateValues } from "lib/deployment/template-values"; +import { ZERO_ADDRESS, defineChain } from "thirdweb"; +import type { FetchDeployMetadataResult } from "thirdweb/contract"; +import { deployContractfromDeployMetadata } from "thirdweb/deploys"; +import { useActiveAccount, useSwitchActiveWalletChain } from "thirdweb/react"; + +export type CrossChain = { + id: number; + network: string; + chainId: number; + status: "DEPLOYED" | "NOT_DEPLOYED"; +}; + +export function DataTable({ + data, + coreMetadata, + modulesMetadata, + erc20InitialData: [_name, _symbol, _contractURI, _owner], +}: { + data: CrossChain[]; + coreMetadata: FetchDeployMetadataResult; + modulesMetadata: FetchDeployMetadataResult[]; + erc20InitialData: string[]; +}) { + const activeAccount = useActiveAccount(); + const switchChain = useSwitchActiveWalletChain(); + const deployStatusModal = useDeployStatusModal(); + const { onError } = useTxNotifications( + "Successfully deployed contract", + "Failed to deploy contract", + ); + + const columns: ColumnDef[] = [ + { + accessorKey: "network", + header: "Network", + }, + { + accessorKey: "chainId", + header: "Chain ID", + }, + { + accessorKey: "status", + header: "Status", + cell: ({ row }) => { + if (row.getValue("status") === "DEPLOYED") { + return Deployed; + } + return ( + + ); + }, + }, + ]; + + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + }); + + const deployContract = async (chainId: number) => { + try { + if (!activeAccount) { + throw new Error("No active account"); + } + + const coreInitializeParams = { + ...( + coreMetadata.abi + .filter((a) => a.type === "function") + .find((a) => a.name === "initialize")?.inputs || [] + ).reduce( + (acc, param) => { + if (!param.name) { + param.name = "*"; + } + + acc[param.name] = replaceTemplateValues( + coreMetadata?.constructorParams?.[param.name]?.defaultValue + ? coreMetadata?.constructorParams?.[param.name]?.defaultValue || + "" + : param.name === "_royaltyBps" || + param.name === "_platformFeeBps" + ? "0" + : "", + param.type, + { + connectedWallet: "0x0000000000000000000000000000000000000000", + chainId: 1, + }, + ); + + return acc; + }, + {} as Record, + ), + _name, + _symbol, + _contractURI, + _owner, + }; + + const moduleInitializeParams = modulesMetadata.reduce( + (acc, mod) => { + const params = getModuleInstallParams(mod); + const paramNames = params + .map((param) => param.name) + .filter((p) => p !== undefined); + const returnVal: Record = {}; + + // 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>, + ); + + const chain = defineChain(chainId); + const client = getThirdwebClient(); + const salt = "thirdweb"; + + 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 ( + + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ); + })} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ))} + +
+ +
+ ); +} diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx new file mode 100644 index 00000000000..f5dcd1c55a6 --- /dev/null +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx @@ -0,0 +1,144 @@ +import { fetchPublishedContractsFromDeploy } from "components/contract-components/fetchPublishedContractsFromDeploy"; +import { notFound, redirect } from "next/navigation"; +import { readContract } 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
asd
; + } + + 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 _topOPStackChainIds = [ + 8453, // Base + 10, // OP Mainnet + 34443, // Mode Network + 7560, // Cyber + 7777777, // Zora + ]; + + 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" : "NOT_DEPLOYED", + }; + }), + ); + + 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)", + }), + ]); + + return ( + + ); +} diff --git a/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx b/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx index 2b3683b8389..1e835aebeec 100644 --- a/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx +++ b/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx @@ -174,6 +174,10 @@ export const CustomContractForm: React.FC = ({ !isFactoryDeployment && (metadata?.name.includes("AccountFactory") || false); + const isSuperchainInterop = !!modules?.find( + (m) => m.name === "SuperChainInterop", + ); + const parsedDeployParams = useMemo( () => ({ ...deployParams.reduce( @@ -430,11 +434,14 @@ export const CustomContractForm: React.FC = ({ _contractURI, }; - const salt = params.deployDeterministic - ? params.signerAsSalt - ? activeAccount.address.concat(params.saltForCreate2) - : params.saltForCreate2 - : undefined; + // TODO: discuss how to handle the salt properly for crosschain contracts + const salt = isSuperchainInterop + ? "thirdweb" + : params.deployDeterministic + ? params.signerAsSalt + ? activeAccount.address.concat(params.saltForCreate2) + : params.saltForCreate2 + : undefined; return await deployContractfromDeployMetadata({ account: activeAccount, diff --git a/apps/dashboard/src/components/contract-components/contract-deploy-form/modular-contract-default-modules-fieldset.tsx b/apps/dashboard/src/components/contract-components/contract-deploy-form/modular-contract-default-modules-fieldset.tsx index 46c4c9ff09e..0404cc5f59e 100644 --- a/apps/dashboard/src/components/contract-components/contract-deploy-form/modular-contract-default-modules-fieldset.tsx +++ b/apps/dashboard/src/components/contract-components/contract-deploy-form/modular-contract-default-modules-fieldset.tsx @@ -194,3 +194,7 @@ export function showRoyaltyFieldset(paramNames: string[]) { export function showPrimarySaleFiedset(paramNames: string[]) { return paramNames.length === 1 && paramNames.includes("primarySaleRecipient"); } + +export function showSuperchainBridgeFieldset(paramNames: string[]) { + return paramNames.length === 1 && paramNames.includes("superchainBridge"); +} diff --git a/apps/dashboard/src/data/explore.ts b/apps/dashboard/src/data/explore.ts index b0261b4034d..494d259aa12 100644 --- a/apps/dashboard/src/data/explore.ts +++ b/apps/dashboard/src/data/explore.ts @@ -192,6 +192,28 @@ const MODULAR_CONTRACTS = { ], } satisfies ExploreCategory; +const CROSS_CHAIN = { + id: "cross-chain", + name: "cross-chain", + displayName: "Cross Chain", + description: + "Collection of contracts that are popular for building cross-chain applications.", + contracts: [ + // erc20 drop + [ + "thirdweb.eth/ERC20CoreInitializable", + [ + "deployer.thirdweb.eth/ClaimableERC20", + "0xf2d22310905EaD92C19c7ef0003C1AD38e129cb1/SuperChainInterop", // TODO: replace this with the OP published contract + ], + { + title: "Modular Token Drop", + description: "ERC20 Tokens that others can mint.", + }, + ], + ], +} satisfies ExploreCategory; + const AIRDROP = { id: "airdrop", name: "Airdrop", @@ -281,6 +303,7 @@ const SMART_WALLET = { const CATEGORIES: Record = { [POPULAR.id]: POPULAR, [MODULAR_CONTRACTS.id]: MODULAR_CONTRACTS, + [CROSS_CHAIN.id]: CROSS_CHAIN, [NFTS.id]: NFTS, [MARKETS.id]: MARKETS, [DROPS.id]: DROPS, From 9fedac3624dc7003c0e41348acd765959c6e7583 Mon Sep 17 00:00:00 2001 From: Stanley Date: Wed, 27 Nov 2024 12:01:00 -0500 Subject: [PATCH 02/19] WIP: hardcoded modified version of TWCloneFactory to be used --- .../[contractAddress]/cross-chain/data-table.tsx | 3 ++- .../[contractAddress]/cross-chain/page.tsx | 8 -------- .../contract-deploy-form/custom-contract.tsx | 3 ++- .../contract/deployment/deploy-via-autofactory.ts | 4 ++-- .../src/extensions/prebuilts/deploy-published.ts | 14 +++++++++++--- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx index 6258913067c..67bded7da7b 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx @@ -36,6 +36,7 @@ import { ZERO_ADDRESS, 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; @@ -170,7 +171,7 @@ export function DataTable({ const chain = defineChain(chainId); const client = getThirdwebClient(); - const salt = "thirdweb"; + const salt = concatHex(["0x0101", padHex("0x", { size: 30 })]).toString(); await switchChain(chain); diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx index f5dcd1c55a6..67df2509ece 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx @@ -52,14 +52,6 @@ export default async function Page(props: { }, ); - const _topOPStackChainIds = [ - 8453, // Base - 10, // OP Mainnet - 34443, // Mode Network - 7560, // Cyber - 7777777, // Zora - ]; - const topOPStackTestnetChainIds = [ 84532, // Base 11155420, // OP testnet diff --git a/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx b/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx index 1e835aebeec..74ad59bec63 100644 --- a/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx +++ b/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx @@ -35,6 +35,7 @@ import { } from "thirdweb/deploys"; import { useActiveAccount, useActiveWalletChain } from "thirdweb/react"; import { upload } from "thirdweb/storage"; +import { concatHex, padHex } from "thirdweb/utils"; import { FormHelperText, FormLabel, Heading, Text } from "tw-components"; import { useCustomFactoryAbi, useFunctionParamsFromABI } from "../hooks"; import { addContractToMultiChainRegistry } from "../utils"; @@ -436,7 +437,7 @@ export const CustomContractForm: React.FC = ({ // TODO: discuss how to handle the salt properly for crosschain contracts const salt = isSuperchainInterop - ? "thirdweb" + ? concatHex(["0x0101", padHex("0x", { size: 30 })]).toString() : params.deployDeterministic ? params.signerAsSalt ? activeAccount.address.concat(params.saltForCreate2) diff --git a/packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts b/packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts index 3c4410fe8a9..97dda7418ed 100644 --- a/packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts +++ b/packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts @@ -6,7 +6,6 @@ import { getRpcClient } from "../../rpc/rpc.js"; import { encode } from "../../transaction/actions/encode.js"; import { sendAndConfirmTransaction } from "../../transaction/actions/send-and-confirm-transaction.js"; import type { PreparedTransaction } from "../../transaction/prepare-transaction.js"; -import { keccakId } from "../../utils/any-evm/keccak-id.js"; import { isZkSyncChain } from "../../utils/any-evm/zksync/isZkSyncChain.js"; import { toHex } from "../../utils/encoding/hex.js"; import { resolvePromisedValue } from "../../utils/promise/resolve-promised-value.js"; @@ -35,7 +34,8 @@ export function prepareAutoFactoryDeployTransaction( }); const blockNumber = await eth_blockNumber(rpcRequest); const salt = args.salt - ? keccakId(args.salt) + ? //? keccakId(args.salt) + (args.salt as `0x${string}`) : toHex(blockNumber, { size: 32, }); diff --git a/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts b/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts index 1d72dab99fd..150547ecaa5 100644 --- a/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts +++ b/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts @@ -163,7 +163,7 @@ export async function deployContractfromDeployMetadata( import("../../contract/deployment/deploy-via-autofactory.js"), import("../../contract/deployment/utils/bootstrap.js"), ]); - const { cloneFactoryContract, implementationContract } = + const { cloneFactoryContract: _, implementationContract } = await getOrDeployInfraForPublishedContract({ chain, client, @@ -181,18 +181,26 @@ export async function deployContractfromDeployMetadata( const initializeTransaction = await getInitializeTransaction({ client, chain, - deployMetadata: deployMetadata, + deployMetadata, implementationContract, initializeParams, account, modules, }); + // TODO: remove this once the modified version of TWCloneFactory + // has been published under the thirdweb wallet + const modifiedCloneFactoryContract = getContract({ + client, + address: "0x7756D8a084e55d9872BD5bBDf6867543D15866A4", // only deployed on OP and zora testnets + chain, + }); + return deployViaAutoFactory({ client, chain, account, - cloneFactoryContract, + cloneFactoryContract: modifiedCloneFactoryContract, initializeTransaction, salt, }); From 4595d58991826c8407b4d61b807f05d68fb01a59 Mon Sep 17 00:00:00 2001 From: Stanley Date: Fri, 29 Nov 2024 21:16:26 -0500 Subject: [PATCH 03/19] WIP: TODO's created to fetch for ContractInitialized event and updated salt transforming --- .../[chain_id]/[contractAddress]/cross-chain/data-table.tsx | 5 +++++ .../[chain_id]/[contractAddress]/cross-chain/page.tsx | 6 ++++-- apps/dashboard/src/data/explore.ts | 2 +- .../src/contract/deployment/deploy-via-autofactory.ts | 6 ++++-- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx index 67bded7da7b..bbb252435d3 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx @@ -101,6 +101,11 @@ export function DataTable({ throw new Error("No active account"); } + // TODO: fetch the ContractIntialized event + // Guessing it has to fetch the transaction hash based on the contract address + // Then fetch the block number from the transaction hash + // Then fetch the event by limiting the block number + const coreInitializeParams = { ...( coreMetadata.abi diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx index 67df2509ece..7f00ebab171 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx @@ -77,7 +77,10 @@ export default async function Page(props: { id: chainId, network: chainMetadata.name, chainId: chain.id, - status: code === _originalCode ? "DEPLOYED" : "NOT_DEPLOYED", + status: + code === _originalCode + ? ("DEPLOYED" as const) + : ("NOT_DEPLOYED" as const), }; }), ); @@ -129,7 +132,6 @@ export default async function Page(props: { coreMetadata={coreMetadata} modulesMetadata={modulesMetadata} erc20InitialData={erc20InitialData} - contract={contract} data={chainsDeployedOn} /> ); diff --git a/apps/dashboard/src/data/explore.ts b/apps/dashboard/src/data/explore.ts index 494d259aa12..dd3c3e577f3 100644 --- a/apps/dashboard/src/data/explore.ts +++ b/apps/dashboard/src/data/explore.ts @@ -201,7 +201,7 @@ const CROSS_CHAIN = { contracts: [ // erc20 drop [ - "thirdweb.eth/ERC20CoreInitializable", + "0x5bb2610C42280674d6f70682f76311B44D1c07FB/ERC20CoreInitializable", // TODO: replace this with the thirdweb published contract [ "deployer.thirdweb.eth/ClaimableERC20", "0xf2d22310905EaD92C19c7ef0003C1AD38e129cb1/SuperChainInterop", // TODO: replace this with the OP published contract diff --git a/packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts b/packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts index 97dda7418ed..5d5026eaa8d 100644 --- a/packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts +++ b/packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts @@ -1,3 +1,4 @@ +import { keccakId } from "src/exports/utils.js"; import { parseEventLogs } from "../../event/actions/parse-logs.js"; import { proxyDeployedEvent } from "../../extensions/thirdweb/__generated__/IContractFactory/events/ProxyDeployed.js"; import { deployProxyByImplementation } from "../../extensions/thirdweb/__generated__/IContractFactory/write/deployProxyByImplementation.js"; @@ -34,8 +35,9 @@ export function prepareAutoFactoryDeployTransaction( }); const blockNumber = await eth_blockNumber(rpcRequest); const salt = args.salt - ? //? keccakId(args.salt) - (args.salt as `0x${string}`) + ? args.salt.startsWith("0x") && args.salt.length === 66 + ? (args.salt as `0x${string}`) + : keccakId(args.salt) : toHex(blockNumber, { size: 32, }); From d08b33c882660c062481c0d4bc0cb2bff802ebf5 Mon Sep 17 00:00:00 2001 From: Stanley Date: Wed, 4 Dec 2024 18:36:13 -0500 Subject: [PATCH 04/19] WIP: Testing out deploying bare core contract with modules installed afterwards --- .../cross-chain/data-table.tsx | 86 +------------------ .../[contractAddress]/cross-chain/page.tsx | 21 ++++- .../modules/components/ModuleForm.tsx | 1 + .../contract-deploy-form/custom-contract.tsx | 85 ++++++++++++++++-- apps/dashboard/src/data/explore.ts | 2 +- .../extensions/prebuilts/deploy-published.ts | 2 +- 6 files changed, 102 insertions(+), 95 deletions(-) diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx index bbb252435d3..e51f18d3482 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx @@ -24,15 +24,8 @@ import { 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 { replaceTemplateValues } from "lib/deployment/template-values"; -import { ZERO_ADDRESS, defineChain } from "thirdweb"; +import { defineChain } from "thirdweb"; import type { FetchDeployMetadataResult } from "thirdweb/contract"; import { deployContractfromDeployMetadata } from "thirdweb/deploys"; import { useActiveAccount, useSwitchActiveWalletChain } from "thirdweb/react"; @@ -49,12 +42,12 @@ export function DataTable({ data, coreMetadata, modulesMetadata, - erc20InitialData: [_name, _symbol, _contractURI, _owner], + initializerCalldata, }: { data: CrossChain[]; coreMetadata: FetchDeployMetadataResult; modulesMetadata: FetchDeployMetadataResult[]; - erc20InitialData: string[]; + initializerCalldata: `0x${string}`; }) { const activeAccount = useActiveAccount(); const switchChain = useSwitchActiveWalletChain(); @@ -101,78 +94,7 @@ export function DataTable({ throw new Error("No active account"); } - // TODO: fetch the ContractIntialized event - // Guessing it has to fetch the transaction hash based on the contract address - // Then fetch the block number from the transaction hash - // Then fetch the event by limiting the block number - - const coreInitializeParams = { - ...( - coreMetadata.abi - .filter((a) => a.type === "function") - .find((a) => a.name === "initialize")?.inputs || [] - ).reduce( - (acc, param) => { - if (!param.name) { - param.name = "*"; - } - - acc[param.name] = replaceTemplateValues( - coreMetadata?.constructorParams?.[param.name]?.defaultValue - ? coreMetadata?.constructorParams?.[param.name]?.defaultValue || - "" - : param.name === "_royaltyBps" || - param.name === "_platformFeeBps" - ? "0" - : "", - param.type, - { - connectedWallet: "0x0000000000000000000000000000000000000000", - chainId: 1, - }, - ); - - return acc; - }, - {} as Record, - ), - _name, - _symbol, - _contractURI, - _owner, - }; - - const moduleInitializeParams = modulesMetadata.reduce( - (acc, mod) => { - const params = getModuleInstallParams(mod); - const paramNames = params - .map((param) => param.name) - .filter((p) => p !== undefined); - const returnVal: Record = {}; - - // 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>, - ); + // TODO: deploy the core contract directly with the initializer calldata const chain = defineChain(chainId); const client = getThirdwebClient(); diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx index 7f00ebab171..0df58424a7f 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx @@ -1,6 +1,7 @@ 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"; @@ -42,7 +43,7 @@ export default async function Page(props: { redirect(`/${params.chain_id}/${params.contractAddress}`); } - const _originalCode = await eth_getCode( + const originalCode = await eth_getCode( getRpcClient({ client: contract.client, chain: contract.chain, @@ -78,7 +79,7 @@ export default async function Page(props: { network: chainMetadata.name, chainId: chain.id, status: - code === _originalCode + code === originalCode ? ("DEPLOYED" as const) : ("NOT_DEPLOYED" as const), }; @@ -108,7 +109,7 @@ export default async function Page(props: { ), )) as FetchDeployMetadataResult[]; - const erc20InitialData = await Promise.all([ + const _erc20InitialData = await Promise.all([ readContract({ contract: contract, method: "function name() view returns (string)", @@ -127,11 +128,23 @@ export default async function Page(props: { }), ]); + 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, + }); + return ( ); diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/modules/components/ModuleForm.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/modules/components/ModuleForm.tsx index f0a15b15879..9c520b8569a 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/modules/components/ModuleForm.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/modules/components/ModuleForm.tsx @@ -252,6 +252,7 @@ export const InstallModuleForm = (props: InstallModuleFormProps) => { module: selectedModuleMeta, isQueryEnabled: !!selectedModule && !!isModuleCompatibleQuery.data, }); + console.log("moduleInstallParams", moduleInstallParams.data); return ( diff --git a/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx b/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx index 74ad59bec63..687366c2369 100644 --- a/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx +++ b/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx @@ -26,16 +26,27 @@ import { CircleAlertIcon, ExternalLinkIcon, InfoIcon } from "lucide-react"; import Link from "next/link"; import { useCallback, useMemo } from "react"; import { FormProvider, type UseFormReturn, useForm } from "react-hook-form"; -import { ZERO_ADDRESS } from "thirdweb"; +import { + ZERO_ADDRESS, + getContract, + sendTransaction, + waitForReceipt, +} from "thirdweb"; import type { FetchDeployMetadataResult } from "thirdweb/contract"; import { deployContractfromDeployMetadata, deployMarketplaceContract, getRequiredTransactions, } from "thirdweb/deploys"; +import { installPublishedModule } from "thirdweb/modules"; import { useActiveAccount, useActiveWalletChain } from "thirdweb/react"; import { upload } from "thirdweb/storage"; -import { concatHex, padHex } from "thirdweb/utils"; +import { + type AbiFunction, + concatHex, + encodeAbiParameters, + padHex, +} from "thirdweb/utils"; import { FormHelperText, FormLabel, Heading, Text } from "tw-components"; import { useCustomFactoryAbi, useFunctionParamsFromABI } from "../hooks"; import { addContractToMultiChainRegistry } from "../utils"; @@ -444,18 +455,78 @@ export const CustomContractForm: React.FC = ({ : params.saltForCreate2 : undefined; - return await deployContractfromDeployMetadata({ + const moduleDeployData = modules?.map((m) => ({ + deployMetadata: m, + initializeParams: params.moduleData[m.name], + })); + console.log("module deploy data", moduleDeployData); + + const deployData = { account: activeAccount, chain: walletChain, client: thirdwebClient, deployMetadata: metadata, initializeParams, salt, - modules: modules?.map((m) => ({ - deployMetadata: m, - initializeParams: params.moduleData[m.name], - })), + modules: isSuperchainInterop + ? // remove modules for superchain interop in order to deploy deterministically deploy just the core contract + [] + : moduleDeployData, + }; + + console.log("deploy data", deployData); + const coreContractAddress = + await deployContractfromDeployMetadata(deployData); + const coreContract = getContract({ + client: thirdwebClient, + address: coreContractAddress, + chain: walletChain, }); + console.log("core contract", coreContract); + + if (isSuperchainInterop && moduleDeployData) { + 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), + ); + } + console.log("module install params", moduleInstallParams); + + const installTransaction = installPublishedModule({ + contract: coreContract, + account: activeAccount, + moduleName: m.deployMetadata.name, + publisher: m.deployMetadata.publisher, + version: m.deployMetadata.version, + moduleData, + }); + console.log("install transaction", installTransaction); + + const txResult = await sendTransaction({ + transaction: installTransaction, + account: activeAccount, + }); + console.log("tx result", txResult); + + return await waitForReceipt(txResult); + }), + ); + } }, }); diff --git a/apps/dashboard/src/data/explore.ts b/apps/dashboard/src/data/explore.ts index dd3c3e577f3..46eaa168a7a 100644 --- a/apps/dashboard/src/data/explore.ts +++ b/apps/dashboard/src/data/explore.ts @@ -201,7 +201,7 @@ const CROSS_CHAIN = { contracts: [ // erc20 drop [ - "0x5bb2610C42280674d6f70682f76311B44D1c07FB/ERC20CoreInitializable", // TODO: replace this with the thirdweb published contract + "deployer.thirdweb.eth/ERC20CoreInitializable", // TODO: replace this with the thirdweb published contract [ "deployer.thirdweb.eth/ClaimableERC20", "0xf2d22310905EaD92C19c7ef0003C1AD38e129cb1/SuperChainInterop", // TODO: replace this with the OP published contract diff --git a/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts b/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts index 150547ecaa5..95b5c93d726 100644 --- a/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts +++ b/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts @@ -192,7 +192,7 @@ export async function deployContractfromDeployMetadata( // has been published under the thirdweb wallet const modifiedCloneFactoryContract = getContract({ client, - address: "0x7756D8a084e55d9872BD5bBDf6867543D15866A4", // only deployed on OP and zora testnets + address: "0xB83db4b940e4796aA1f53DBFC824B9B1865835D5", // only deployed on OP and zora testnets chain, }); From 1f74bf686c0cba9913c5444992e40201eb93b639 Mon Sep 17 00:00:00 2001 From: Stanley Date: Thu, 5 Dec 2024 12:05:50 -0500 Subject: [PATCH 05/19] WIP: staging to get help --- .../contract-deploy-form/custom-contract.tsx | 111 ++++++++---------- apps/dashboard/src/data/explore.ts | 2 +- .../deployment/deploy-via-autofactory.ts | 2 +- .../extensions/prebuilts/deploy-published.ts | 2 +- .../IContractFactory/events/ProxyDeployed.ts | 2 +- .../actions/send-and-confirm-transaction.ts | 4 +- packages/thirdweb/src/utils/ens/namehash.ts | 5 +- 7 files changed, 60 insertions(+), 68 deletions(-) diff --git a/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx b/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx index 687366c2369..e455dbc7796 100644 --- a/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx +++ b/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx @@ -26,27 +26,16 @@ import { CircleAlertIcon, ExternalLinkIcon, InfoIcon } from "lucide-react"; import Link from "next/link"; import { useCallback, useMemo } from "react"; import { FormProvider, type UseFormReturn, useForm } from "react-hook-form"; -import { - ZERO_ADDRESS, - getContract, - sendTransaction, - waitForReceipt, -} from "thirdweb"; +import { ZERO_ADDRESS, getContract } from "thirdweb"; import type { FetchDeployMetadataResult } from "thirdweb/contract"; import { deployContractfromDeployMetadata, deployMarketplaceContract, getRequiredTransactions, } from "thirdweb/deploys"; -import { installPublishedModule } from "thirdweb/modules"; import { useActiveAccount, useActiveWalletChain } from "thirdweb/react"; import { upload } from "thirdweb/storage"; -import { - type AbiFunction, - concatHex, - encodeAbiParameters, - padHex, -} from "thirdweb/utils"; +import { concatHex, padHex } from "thirdweb/utils"; import { FormHelperText, FormLabel, Heading, Text } from "tw-components"; import { useCustomFactoryAbi, useFunctionParamsFromABI } from "../hooks"; import { addContractToMultiChainRegistry } from "../utils"; @@ -461,7 +450,7 @@ export const CustomContractForm: React.FC = ({ })); console.log("module deploy data", moduleDeployData); - const deployData = { + const coreContractAddress = await deployContractfromDeployMetadata({ account: activeAccount, chain: walletChain, client: thirdwebClient, @@ -472,11 +461,7 @@ export const CustomContractForm: React.FC = ({ ? // remove modules for superchain interop in order to deploy deterministically deploy just the core contract [] : moduleDeployData, - }; - - console.log("deploy data", deployData); - const coreContractAddress = - await deployContractfromDeployMetadata(deployData); + }); const coreContract = getContract({ client: thirdwebClient, address: coreContractAddress, @@ -484,49 +469,51 @@ export const CustomContractForm: React.FC = ({ }); console.log("core contract", coreContract); - if (isSuperchainInterop && moduleDeployData) { - 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), - ); - } - console.log("module install params", moduleInstallParams); - - const installTransaction = installPublishedModule({ - contract: coreContract, - account: activeAccount, - moduleName: m.deployMetadata.name, - publisher: m.deployMetadata.publisher, - version: m.deployMetadata.version, - moduleData, - }); - console.log("install transaction", installTransaction); - - const txResult = await sendTransaction({ - transaction: installTransaction, - account: activeAccount, - }); - console.log("tx result", txResult); - - return await waitForReceipt(txResult); - }), - ); - } + //if (isSuperchainInterop && moduleDeployData) { + // 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), + // ); + // } + // console.log("module install params", moduleInstallParams); + // + // const installTransaction = installPublishedModule({ + // contract: coreContract, + // account: activeAccount, + // moduleName: m.deployMetadata.name, + // publisher: m.deployMetadata.publisher, + // version: m.deployMetadata.version, + // moduleData, + // }); + // console.log("install transaction", installTransaction); + // + // const txResult = await sendTransaction({ + // transaction: installTransaction, + // account: activeAccount, + // }); + // console.log("tx result", txResult); + // + // return await waitForReceipt(txResult); + // }), + // ); + //} + + return coreContractAddress; }, }); diff --git a/apps/dashboard/src/data/explore.ts b/apps/dashboard/src/data/explore.ts index 46eaa168a7a..a75809aefd8 100644 --- a/apps/dashboard/src/data/explore.ts +++ b/apps/dashboard/src/data/explore.ts @@ -203,8 +203,8 @@ const CROSS_CHAIN = { [ "deployer.thirdweb.eth/ERC20CoreInitializable", // TODO: replace this with the thirdweb published contract [ - "deployer.thirdweb.eth/ClaimableERC20", "0xf2d22310905EaD92C19c7ef0003C1AD38e129cb1/SuperChainInterop", // TODO: replace this with the OP published contract + "deployer.thirdweb.eth/ClaimableERC20", ], { title: "Modular Token Drop", diff --git a/packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts b/packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts index 5d5026eaa8d..a4a8fc925a0 100644 --- a/packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts +++ b/packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts @@ -1,4 +1,3 @@ -import { keccakId } from "src/exports/utils.js"; import { parseEventLogs } from "../../event/actions/parse-logs.js"; import { proxyDeployedEvent } from "../../extensions/thirdweb/__generated__/IContractFactory/events/ProxyDeployed.js"; import { deployProxyByImplementation } from "../../extensions/thirdweb/__generated__/IContractFactory/write/deployProxyByImplementation.js"; @@ -7,6 +6,7 @@ import { getRpcClient } from "../../rpc/rpc.js"; import { encode } from "../../transaction/actions/encode.js"; import { sendAndConfirmTransaction } from "../../transaction/actions/send-and-confirm-transaction.js"; import type { PreparedTransaction } from "../../transaction/prepare-transaction.js"; +import { keccakId } from "../../utils/any-evm/keccak-id.js"; import { isZkSyncChain } from "../../utils/any-evm/zksync/isZkSyncChain.js"; import { toHex } from "../../utils/encoding/hex.js"; import { resolvePromisedValue } from "../../utils/promise/resolve-promised-value.js"; diff --git a/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts b/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts index 95b5c93d726..3e4cb6f8052 100644 --- a/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts +++ b/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts @@ -196,7 +196,7 @@ export async function deployContractfromDeployMetadata( chain, }); - return deployViaAutoFactory({ + return await deployViaAutoFactory({ client, chain, account, diff --git a/packages/thirdweb/src/extensions/thirdweb/__generated__/IContractFactory/events/ProxyDeployed.ts b/packages/thirdweb/src/extensions/thirdweb/__generated__/IContractFactory/events/ProxyDeployed.ts index bc0db42d912..d966b2afc25 100644 --- a/packages/thirdweb/src/extensions/thirdweb/__generated__/IContractFactory/events/ProxyDeployed.ts +++ b/packages/thirdweb/src/extensions/thirdweb/__generated__/IContractFactory/events/ProxyDeployed.ts @@ -41,7 +41,7 @@ export type ProxyDeployedEventFilters = Partial<{ export function proxyDeployedEvent(filters: ProxyDeployedEventFilters = {}) { return prepareEvent({ signature: - "event ProxyDeployed(address indexed implementation, address proxy, address indexed deployer)", + "event ProxyDeployed(address indexed implementation, address proxy, address indexed deployer, bytes data)", filters, }); } diff --git a/packages/thirdweb/src/transaction/actions/send-and-confirm-transaction.ts b/packages/thirdweb/src/transaction/actions/send-and-confirm-transaction.ts index 68d635afd53..69368820d72 100644 --- a/packages/thirdweb/src/transaction/actions/send-and-confirm-transaction.ts +++ b/packages/thirdweb/src/transaction/actions/send-and-confirm-transaction.ts @@ -53,5 +53,7 @@ export async function sendAndConfirmTransaction( options: SendTransactionOptions, ): Promise { const submittedTx = await sendTransaction(options); - return waitForReceipt(submittedTx); + const receipt = await waitForReceipt(submittedTx); + + return receipt; } diff --git a/packages/thirdweb/src/utils/ens/namehash.ts b/packages/thirdweb/src/utils/ens/namehash.ts index 87eb6fae7e4..fa450a83f30 100644 --- a/packages/thirdweb/src/utils/ens/namehash.ts +++ b/packages/thirdweb/src/utils/ens/namehash.ts @@ -20,7 +20,10 @@ export function namehash(name: string) { const hashed = hashFromEncodedLabel ? toBytes(hashFromEncodedLabel) : keccak256(stringToBytes(item), "bytes"); - result = keccak256(concat([result, hashed]), "bytes"); + result = keccak256( + concat([result, hashed]), + "bytes", + ) as Uint8Array; } return bytesToHex(result); From 3248212daf4a3414bfdb5f4afc61935f877c97d8 Mon Sep 17 00:00:00 2001 From: Stanley Date: Fri, 6 Dec 2024 13:20:47 -0500 Subject: [PATCH 06/19] successfully deploys using the initialize data from the event --- .../cross-chain/data-table.tsx | 16 ++--- .../[contractAddress]/cross-chain/page.tsx | 33 +++------- .../modules/components/ModuleForm.tsx | 1 - .../deployment/deploy-via-autofactory.ts | 62 ++++++++++++++++++- .../extensions/prebuilts/deploy-published.ts | 40 ++++++++++++ .../src/utils/any-evm/deploy-metadata.ts | 2 +- 6 files changed, 120 insertions(+), 34 deletions(-) diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx index e51f18d3482..1738d6a5cd0 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx @@ -42,12 +42,12 @@ export function DataTable({ data, coreMetadata, modulesMetadata, - initializerCalldata, + initializeData, }: { data: CrossChain[]; coreMetadata: FetchDeployMetadataResult; modulesMetadata: FetchDeployMetadataResult[]; - initializerCalldata: `0x${string}`; + initializeData?: `0x${string}`; }) { const activeAccount = useActiveAccount(); const switchChain = useSwitchActiveWalletChain(); @@ -116,14 +116,14 @@ export function DataTable({ account: activeAccount, chain, client, - deployMetadata: coreMetadata, - initializeParams: coreInitializeParams, + deployMetadata: { + ...coreMetadata, + deployType: "crosschain" as const, + }, + initializeData, salt, - modules: modulesMetadata.map((m) => ({ - deployMetadata: m, - initializeParams: moduleInitializeParams[m.name], - })), }); + console.log("crosschain contract address", crosschainContractAddress); await verifyContract({ address: crosschainContractAddress, diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx index 0df58424a7f..bef7d5df69c 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx @@ -1,6 +1,5 @@ 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"; @@ -109,42 +108,30 @@ export default async function Page(props: { ), )) 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)", }); + const twCloneFactoryContract = getContract({ + address: "0xB83db4b940e4796aA1f53DBFC824B9B1865835D5", + chain: contract.chain, + client: contract.client, + }); + // TODO: figure out how to fetch the events properly - const [event] = await getContractEvents({ - contract, + const events = await getContractEvents({ + contract: twCloneFactoryContract, events: [ProxyDeployedEvent], blockRange: 123456n, }); + const event = events.find((e) => e.args.proxy === params.contractAddress); return ( ); diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/modules/components/ModuleForm.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/modules/components/ModuleForm.tsx index b8fda468cc6..8ae9666803f 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/modules/components/ModuleForm.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/modules/components/ModuleForm.tsx @@ -252,7 +252,6 @@ export const InstallModuleForm = (props: InstallModuleFormProps) => { module: selectedModuleMeta, isQueryEnabled: !!selectedModule && !!isModuleCompatibleQuery.data, }); - console.log("moduleInstallParams", moduleInstallParams.data); return ( diff --git a/packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts b/packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts index a4a8fc925a0..a22643b4229 100644 --- a/packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts +++ b/packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts @@ -47,11 +47,13 @@ export function prepareAutoFactoryDeployTransaction( if (!implementation) { throw new Error("initializeTransaction must have a 'to' field set"); } - return { + const asd = { data: await encode(args.initializeTransaction), implementation, salt, } as const; + console.error("core contract deploy params: ", asd); + return asd; }, }); } @@ -108,3 +110,61 @@ export async function deployViaAutoFactory( } return decodedEvent[0]?.args.proxy; } + +/** + * @internal + */ +export async function deployViaAutoFactoryWithImplementationParams( + options: ClientAndChainAndAccount & { + cloneFactoryContract: ThirdwebContract; + initializeData?: `0x${string}`; + implementationAddress: string; + salt?: string; + }, +): Promise { + const { + client, + chain, + account, + cloneFactoryContract, + initializeData, + implementationAddress, + salt, + } = options; + + const rpcRequest = getRpcClient({ + client, + chain, + }); + const blockNumber = await eth_blockNumber(rpcRequest); + const parsedSalt = salt + ? salt.startsWith("0x") && salt.length === 66 + ? (salt as `0x${string}`) + : keccakId(salt) + : toHex(blockNumber, { + size: 32, + }); + + const asd = { + contract: cloneFactoryContract, + data: initializeData || "0x", + implementation: implementationAddress, + salt: parsedSalt, + }; + console.error("cross chain contract deploy params: ", asd); + const tx = deployProxyByImplementation(asd); + const receipt = await sendAndConfirmTransaction({ + transaction: tx, + account, + }); + const decodedEvent = parseEventLogs({ + events: [proxyDeployedEvent()], + logs: receipt.logs, + }); + if (decodedEvent.length === 0 || !decodedEvent[0]) { + throw new Error( + `No ProxyDeployed event found in transaction: ${receipt.transactionHash}`, + ); + } + return decodedEvent[0]?.args.proxy; +} diff --git a/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts b/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts index 3e4cb6f8052..8410b6ceec5 100644 --- a/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts +++ b/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts @@ -2,6 +2,7 @@ import type { AbiFunction } from "abitype"; import type { Chain } from "../../chains/types.js"; import type { ThirdwebClient } from "../../client/client.js"; import { type ThirdwebContract, getContract } from "../../contract/contract.js"; +import { deployViaAutoFactoryWithImplementationParams } from "../../contract/deployment/deploy-via-autofactory.js"; import { fetchPublishedContractMetadata } from "../../contract/deployment/publisher.js"; import { getOrDeployInfraContractFromMetadata } from "../../contract/deployment/utils/bootstrap.js"; import { sendAndConfirmTransaction } from "../../transaction/actions/send-and-confirm-transaction.js"; @@ -120,6 +121,7 @@ export type DeployContractfromDeployMetadataOptions = { account: Account; deployMetadata: FetchDeployMetadataResult; initializeParams?: Record; + initializeData?: `0x${string}`; implementationConstructorParams?: Record; modules?: { deployMetadata: FetchDeployMetadataResult; @@ -139,6 +141,7 @@ export async function deployContractfromDeployMetadata( account, chain, initializeParams, + initializeData, deployMetadata, implementationConstructorParams, modules, @@ -205,6 +208,43 @@ export async function deployContractfromDeployMetadata( salt, }); } + case "crosschain": { + const { getOrDeployInfraForPublishedContract } = await import( + "../../contract/deployment/utils/bootstrap.js" + ); + const { cloneFactoryContract: _, implementationContract } = + await getOrDeployInfraForPublishedContract({ + chain, + client, + account, + contractId: deployMetadata.name, + constructorParams: + implementationConstructorParams || + (await getAllDefaultConstructorParamsForImplementation({ + chain, + client, + })), + publisher: deployMetadata.publisher, + }); + + // TODO: remove this once the modified version of TWCloneFactory + // has been published under the thirdweb wallet + const modifiedCloneFactoryContract = getContract({ + client, + address: "0xB83db4b940e4796aA1f53DBFC824B9B1865835D5", // only deployed on OP and zora testnets + chain, + }); + + return await deployViaAutoFactoryWithImplementationParams({ + client, + chain, + account, + cloneFactoryContract: modifiedCloneFactoryContract, + implementationAddress: implementationContract.address, + initializeData, + salt, + }); + } case "customFactory": { if (!deployMetadata?.factoryDeploymentData?.customFactoryInput) { throw new Error("No custom factory info found"); diff --git a/packages/thirdweb/src/utils/any-evm/deploy-metadata.ts b/packages/thirdweb/src/utils/any-evm/deploy-metadata.ts index 365d9b882e0..af30d8c9c1a 100644 --- a/packages/thirdweb/src/utils/any-evm/deploy-metadata.ts +++ b/packages/thirdweb/src/utils/any-evm/deploy-metadata.ts @@ -207,7 +207,7 @@ export type ExtendedMetadata = { factoryAddresses?: Record; } | undefined; - deployType?: "standard" | "autoFactory" | "customFactory"; + deployType?: "standard" | "autoFactory" | "customFactory" | "crosschain"; routerType?: "none" | "plugin" | "dynamic" | "modular"; networksForDeployment?: { allNetworks?: boolean; From 6b9dd3374efb017b99ff332b0140e5943705e8fe Mon Sep 17 00:00:00 2001 From: Stanley Date: Fri, 6 Dec 2024 16:41:12 -0500 Subject: [PATCH 07/19] able to install modules successfully when crosschain deploying --- .../cross-chain/data-table.tsx | 124 +++++++++++++++++- .../[contractAddress]/cross-chain/page.tsx | 2 + 2 files changed, 123 insertions(+), 3 deletions(-) diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx index 1738d6a5cd0..8fd4cd2cd8e 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx @@ -24,12 +24,34 @@ import { 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 { defineChain } from "thirdweb"; -import type { FetchDeployMetadataResult } from "thirdweb/contract"; +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 { concatHex, padHex } from "thirdweb/utils"; +import { + type AbiFunction, + concatHex, + encodeAbiParameters, + padHex, +} from "thirdweb/utils"; export type CrossChain = { id: number; @@ -41,11 +63,13 @@ export type CrossChain = { export function DataTable({ data, coreMetadata, + coreContract, modulesMetadata, initializeData, }: { data: CrossChain[]; coreMetadata: FetchDeployMetadataResult; + coreContract: ThirdwebContract; modulesMetadata: FetchDeployMetadataResult[]; initializeData?: `0x${string}`; }) { @@ -131,6 +155,100 @@ export function DataTable({ client, }); + const owner = await readContract({ + contract: coreContract, + method: "function owner() view returns (address)", + params: [], + }); + console.log("owner", owner); + + const moduleInitializeParams = modulesMetadata.reduce( + (acc, mod) => { + const params = getModuleInstallParams(mod); + const paramNames = params + .map((param) => param.name) + .filter((p) => p !== undefined); + const returnVal: Record = {}; + + // 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>, + ); + console.log("moduleInitializeParams", moduleInitializeParams); + + const moduleDeployData = modulesMetadata.map((m) => ({ + deployMetadata: m, + initializeParams: moduleInitializeParams[m.name], + })); + console.log("module deploy data", moduleDeployData); + + 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), + ); + } + console.log("module install params", moduleInstallParams); + + const installTransaction = installPublishedModule({ + contract, + account: activeAccount, + moduleName: m.deployMetadata.name, + publisher: m.deployMetadata.publisher, + version: m.deployMetadata.version, + moduleData, + }); + console.log("install transaction", installTransaction); + + const txResult = await sendTransaction({ + transaction: installTransaction, + account: activeAccount, + }); + console.log("tx result", txResult); + + return await waitForReceipt(txResult); + }), + ); + deployStatusModal.nextStep(); deployStatusModal.setViewContractLink( `/${chain.id}/${crosschainContractAddress}`, diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx index bef7d5df69c..a95120a9b12 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx @@ -107,6 +107,7 @@ export default async function Page(props: { ).at(-1), ), )) as FetchDeployMetadataResult[]; + console.log("modulesMetadata", modulesMetadata); const ProxyDeployedEvent = prepareEvent({ signature: @@ -133,6 +134,7 @@ export default async function Page(props: { modulesMetadata={modulesMetadata} initializeData={event?.args.data} data={chainsDeployedOn} + coreContract={contract} /> ); } From 64ca5797d7238220ece643a38ceead1c2bd343d1 Mon Sep 17 00:00:00 2001 From: Stanley Date: Fri, 6 Dec 2024 16:51:20 -0500 Subject: [PATCH 08/19] updated to be promise.allSettled when installing modules to not block deployment of the contract --- .../contract-deploy-form/custom-contract.tsx | 101 ++++++++++-------- 1 file changed, 56 insertions(+), 45 deletions(-) diff --git a/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx b/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx index e455dbc7796..5940cac52e6 100644 --- a/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx +++ b/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx @@ -26,16 +26,27 @@ import { CircleAlertIcon, ExternalLinkIcon, InfoIcon } from "lucide-react"; import Link from "next/link"; import { useCallback, useMemo } from "react"; import { FormProvider, type UseFormReturn, useForm } from "react-hook-form"; -import { ZERO_ADDRESS, getContract } from "thirdweb"; +import { + ZERO_ADDRESS, + getContract, + sendTransaction, + waitForReceipt, +} from "thirdweb"; import type { FetchDeployMetadataResult } from "thirdweb/contract"; import { deployContractfromDeployMetadata, deployMarketplaceContract, getRequiredTransactions, } from "thirdweb/deploys"; +import { installPublishedModule } from "thirdweb/modules"; import { useActiveAccount, useActiveWalletChain } from "thirdweb/react"; import { upload } from "thirdweb/storage"; -import { concatHex, padHex } from "thirdweb/utils"; +import { + type AbiFunction, + concatHex, + encodeAbiParameters, + padHex, +} from "thirdweb/utils"; import { FormHelperText, FormLabel, Heading, Text } from "tw-components"; import { useCustomFactoryAbi, useFunctionParamsFromABI } from "../hooks"; import { addContractToMultiChainRegistry } from "../utils"; @@ -469,49 +480,49 @@ export const CustomContractForm: React.FC = ({ }); console.log("core contract", coreContract); - //if (isSuperchainInterop && moduleDeployData) { - // 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), - // ); - // } - // console.log("module install params", moduleInstallParams); - // - // const installTransaction = installPublishedModule({ - // contract: coreContract, - // account: activeAccount, - // moduleName: m.deployMetadata.name, - // publisher: m.deployMetadata.publisher, - // version: m.deployMetadata.version, - // moduleData, - // }); - // console.log("install transaction", installTransaction); - // - // const txResult = await sendTransaction({ - // transaction: installTransaction, - // account: activeAccount, - // }); - // console.log("tx result", txResult); - // - // return await waitForReceipt(txResult); - // }), - // ); - //} + if (isSuperchainInterop && moduleDeployData) { + await Promise.allSettled( + 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), + ); + } + console.log("module install params", moduleInstallParams); + + const installTransaction = installPublishedModule({ + contract: coreContract, + account: activeAccount, + moduleName: m.deployMetadata.name, + publisher: m.deployMetadata.publisher, + version: m.deployMetadata.version, + moduleData, + }); + console.log("install transaction", installTransaction); + + const txResult = await sendTransaction({ + transaction: installTransaction, + account: activeAccount, + }); + console.log("tx result", txResult); + + return await waitForReceipt(txResult); + }), + ); + } return coreContractAddress; }, From 91438571dfdaa762610ca90aae70b46a64427237 Mon Sep 17 00:00:00 2001 From: Stanley Date: Fri, 6 Dec 2024 16:58:01 -0500 Subject: [PATCH 09/19] removing console logs --- .../[contractAddress]/cross-chain/data-table.tsx | 7 ------- .../[chain_id]/[contractAddress]/cross-chain/page.tsx | 2 -- .../contract-deploy-form/custom-contract.tsx | 6 ------ .../src/contract/deployment/deploy-via-autofactory.ts | 5 +---- 4 files changed, 1 insertion(+), 19 deletions(-) diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx index 8fd4cd2cd8e..86befcbc4e6 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx @@ -147,7 +147,6 @@ export function DataTable({ initializeData, salt, }); - console.log("crosschain contract address", crosschainContractAddress); await verifyContract({ address: crosschainContractAddress, @@ -160,7 +159,6 @@ export function DataTable({ method: "function owner() view returns (address)", params: [], }); - console.log("owner", owner); const moduleInitializeParams = modulesMetadata.reduce( (acc, mod) => { @@ -193,13 +191,11 @@ export function DataTable({ }, {} as Record>, ); - console.log("moduleInitializeParams", moduleInitializeParams); const moduleDeployData = modulesMetadata.map((m) => ({ deployMetadata: m, initializeParams: moduleInitializeParams[m.name], })); - console.log("module deploy data", moduleDeployData); const contract = getContract({ address: crosschainContractAddress, @@ -227,7 +223,6 @@ export function DataTable({ Object.values(m.initializeParams), ); } - console.log("module install params", moduleInstallParams); const installTransaction = installPublishedModule({ contract, @@ -237,13 +232,11 @@ export function DataTable({ version: m.deployMetadata.version, moduleData, }); - console.log("install transaction", installTransaction); const txResult = await sendTransaction({ transaction: installTransaction, account: activeAccount, }); - console.log("tx result", txResult); return await waitForReceipt(txResult); }), diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx index a95120a9b12..215be1e839d 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx @@ -107,7 +107,6 @@ export default async function Page(props: { ).at(-1), ), )) as FetchDeployMetadataResult[]; - console.log("modulesMetadata", modulesMetadata); const ProxyDeployedEvent = prepareEvent({ signature: @@ -120,7 +119,6 @@ export default async function Page(props: { client: contract.client, }); - // TODO: figure out how to fetch the events properly const events = await getContractEvents({ contract: twCloneFactoryContract, events: [ProxyDeployedEvent], diff --git a/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx b/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx index 5940cac52e6..47898655363 100644 --- a/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx +++ b/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx @@ -446,7 +446,6 @@ export const CustomContractForm: React.FC = ({ _contractURI, }; - // TODO: discuss how to handle the salt properly for crosschain contracts const salt = isSuperchainInterop ? concatHex(["0x0101", padHex("0x", { size: 30 })]).toString() : params.deployDeterministic @@ -459,7 +458,6 @@ export const CustomContractForm: React.FC = ({ deployMetadata: m, initializeParams: params.moduleData[m.name], })); - console.log("module deploy data", moduleDeployData); const coreContractAddress = await deployContractfromDeployMetadata({ account: activeAccount, @@ -478,7 +476,6 @@ export const CustomContractForm: React.FC = ({ address: coreContractAddress, chain: walletChain, }); - console.log("core contract", coreContract); if (isSuperchainInterop && moduleDeployData) { await Promise.allSettled( @@ -501,7 +498,6 @@ export const CustomContractForm: React.FC = ({ Object.values(m.initializeParams), ); } - console.log("module install params", moduleInstallParams); const installTransaction = installPublishedModule({ contract: coreContract, @@ -511,13 +507,11 @@ export const CustomContractForm: React.FC = ({ version: m.deployMetadata.version, moduleData, }); - console.log("install transaction", installTransaction); const txResult = await sendTransaction({ transaction: installTransaction, account: activeAccount, }); - console.log("tx result", txResult); return await waitForReceipt(txResult); }), diff --git a/packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts b/packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts index a22643b4229..90cb042874b 100644 --- a/packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts +++ b/packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts @@ -47,13 +47,11 @@ export function prepareAutoFactoryDeployTransaction( if (!implementation) { throw new Error("initializeTransaction must have a 'to' field set"); } - const asd = { + return { data: await encode(args.initializeTransaction), implementation, salt, } as const; - console.error("core contract deploy params: ", asd); - return asd; }, }); } @@ -151,7 +149,6 @@ export async function deployViaAutoFactoryWithImplementationParams( implementation: implementationAddress, salt: parsedSalt, }; - console.error("cross chain contract deploy params: ", asd); const tx = deployProxyByImplementation(asd); const receipt = await sendAndConfirmTransaction({ transaction: tx, From 051028bca25a7ad264a99eb7ef5d81957b8b7f88 Mon Sep 17 00:00:00 2001 From: Stanley Date: Fri, 6 Dec 2024 17:24:19 -0500 Subject: [PATCH 10/19] fixing liniting issues --- .../[chain_id]/[contractAddress]/cross-chain/data-table.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx index 86befcbc4e6..45306bc64a4 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx @@ -53,7 +53,7 @@ import { padHex, } from "thirdweb/utils"; -export type CrossChain = { +type CrossChain = { id: number; network: string; chainId: number; @@ -118,8 +118,6 @@ export function DataTable({ 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(); From 97e6b994db7ba94aebb794efb52710c3438344ea Mon Sep 17 00:00:00 2001 From: Stanley Date: Fri, 6 Dec 2024 17:35:26 -0500 Subject: [PATCH 11/19] fixed linting issues --- .../[chain_id]/[contractAddress]/cross-chain/data-table.tsx | 1 + .../(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx index 45306bc64a4..91df19b6930 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx @@ -118,6 +118,7 @@ export function DataTable({ 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(); diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx index 215be1e839d..adfbac86b64 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx @@ -62,6 +62,7 @@ export default async function Page(props: { const chainsDeployedOn = await Promise.all( topOPStackTestnetChainIds.map(async (chainId) => { + // eslint-disable-next-line no-restricted-syntax const chain = defineChain(chainId); const chainMetadata = await getChainMetadata(chain); From 55158b4330ffb3782f47bb04463b7e2aa8909e00 Mon Sep 17 00:00:00 2001 From: Stanley Date: Mon, 9 Dec 2024 14:42:40 -0500 Subject: [PATCH 12/19] created isSuperchain and isCrosschain flag in favour of case switch statement --- .../cross-chain/data-table.tsx | 160 +++++++++--------- .../contract-deploy-form/custom-contract.tsx | 1 + .../extensions/prebuilts/deploy-published.ts | 86 ++++------ 3 files changed, 118 insertions(+), 129 deletions(-) diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx index 91df19b6930..10aa67890a7 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx @@ -135,14 +135,16 @@ export function DataTable({ deployStatusModal.setViewContractLink(""); deployStatusModal.open(steps); + const isCrosschain = !!modulesMetadata?.find( + (m) => m.name === "SuperChainInterop", + ); + const crosschainContractAddress = await deployContractfromDeployMetadata({ account: activeAccount, chain, client, - deployMetadata: { - ...coreMetadata, - deployType: "crosschain" as const, - }, + deployMetadata: coreMetadata, + isCrosschain, initializeData, salt, }); @@ -153,93 +155,95 @@ export function DataTable({ client, }); - const owner = await readContract({ - contract: coreContract, - method: "function owner() view returns (address)", - params: [], - }); + if (isCrosschain) { + 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 = {}; + const moduleInitializeParams = modulesMetadata.reduce( + (acc, mod) => { + const params = getModuleInstallParams(mod); + const paramNames = params + .map((param) => param.name) + .filter((p) => p !== undefined); + const returnVal: Record = {}; - // 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 "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 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 - } + // set superchain bridge address + else if (showSuperchainBridgeFieldset(paramNames)) { + returnVal.superchainBridge = + "0x4200000000000000000000000000000000000010"; // OP Superchain Bridge + } - acc[mod.name] = returnVal; - return acc; - }, - {} as Record>, - ); + acc[mod.name] = returnVal; + return acc; + }, + {} as Record>, + ); - const moduleDeployData = modulesMetadata.map((m) => ({ - deployMetadata: m, - initializeParams: moduleInitializeParams[m.name], - })); + const moduleDeployData = modulesMetadata.map((m) => ({ + deployMetadata: m, + initializeParams: moduleInitializeParams[m.name], + })); - const contract = getContract({ - address: crosschainContractAddress, - chain, - client, - }); + const contract = getContract({ + address: crosschainContractAddress, + chain, + client, + }); - await Promise.all( - moduleDeployData.map(async (m) => { - let moduleData: `0x${string}` | undefined; + 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; + 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), - ); - } + 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 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, - }); + const txResult = await sendTransaction({ + transaction: installTransaction, + account: activeAccount, + }); - return await waitForReceipt(txResult); - }), - ); + return await waitForReceipt(txResult); + }), + ); + } deployStatusModal.nextStep(); deployStatusModal.setViewContractLink( diff --git a/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx b/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx index 47898655363..23a742e6d52 100644 --- a/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx +++ b/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx @@ -466,6 +466,7 @@ export const CustomContractForm: React.FC = ({ deployMetadata: metadata, initializeParams, salt, + isSuperchainInterop, modules: isSuperchainInterop ? // remove modules for superchain interop in order to deploy deterministically deploy just the core contract [] diff --git a/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts b/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts index 8410b6ceec5..a0b03269ad9 100644 --- a/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts +++ b/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts @@ -123,6 +123,8 @@ export type DeployContractfromDeployMetadataOptions = { initializeParams?: Record; initializeData?: `0x${string}`; implementationConstructorParams?: Record; + isSuperchainInterop?: boolean; + isCrosschain?: boolean; modules?: { deployMetadata: FetchDeployMetadataResult; initializeParams?: Record; @@ -143,6 +145,8 @@ export async function deployContractfromDeployMetadata( initializeParams, initializeData, deployMetadata, + isSuperchainInterop, // TODO: Remove this once the updated Clone Factory has been published + isCrosschain, implementationConstructorParams, modules, salt, @@ -166,7 +170,7 @@ export async function deployContractfromDeployMetadata( import("../../contract/deployment/deploy-via-autofactory.js"), import("../../contract/deployment/utils/bootstrap.js"), ]); - const { cloneFactoryContract: _, implementationContract } = + const { cloneFactoryContract, implementationContract } = await getOrDeployInfraForPublishedContract({ chain, client, @@ -181,15 +185,9 @@ export async function deployContractfromDeployMetadata( publisher: deployMetadata.publisher, }); - const initializeTransaction = await getInitializeTransaction({ - client, - chain, - deployMetadata, - implementationContract, - initializeParams, - account, - modules, - }); + console.error("is superchain interop", isSuperchainInterop); + console.error("is crosschain: ", isCrosschain); + console.error("initialize data: ", initializeData); // TODO: remove this once the modified version of TWCloneFactory // has been published under the thirdweb wallet @@ -199,51 +197,37 @@ export async function deployContractfromDeployMetadata( chain, }); - return await deployViaAutoFactory({ - client, - chain, - account, - cloneFactoryContract: modifiedCloneFactoryContract, - initializeTransaction, - salt, - }); - } - case "crosschain": { - const { getOrDeployInfraForPublishedContract } = await import( - "../../contract/deployment/utils/bootstrap.js" - ); - const { cloneFactoryContract: _, implementationContract } = - await getOrDeployInfraForPublishedContract({ + if (isCrosschain) { + return await deployViaAutoFactoryWithImplementationParams({ + client, chain, + account, + cloneFactoryContract: modifiedCloneFactoryContract, + implementationAddress: implementationContract.address, + initializeData, + salt, + }); + } else { + const initializeTransaction = await getInitializeTransaction({ client, + chain, + deployMetadata, + implementationContract, + initializeParams, account, - contractId: deployMetadata.name, - constructorParams: - implementationConstructorParams || - (await getAllDefaultConstructorParamsForImplementation({ - chain, - client, - })), - publisher: deployMetadata.publisher, + modules, }); - - // TODO: remove this once the modified version of TWCloneFactory - // has been published under the thirdweb wallet - const modifiedCloneFactoryContract = getContract({ - client, - address: "0xB83db4b940e4796aA1f53DBFC824B9B1865835D5", // only deployed on OP and zora testnets - chain, - }); - - return await deployViaAutoFactoryWithImplementationParams({ - client, - chain, - account, - cloneFactoryContract: modifiedCloneFactoryContract, - implementationAddress: implementationContract.address, - initializeData, - salt, - }); + return await deployViaAutoFactory({ + client, + chain, + account, + cloneFactoryContract: isSuperchainInterop // TODO: remove this once the updated clone factory is publsihed + ? modifiedCloneFactoryContract + : cloneFactoryContract, + initializeTransaction, + salt, + }); + } } case "customFactory": { if (!deployMetadata?.factoryDeploymentData?.customFactoryInput) { From 67db04dd83b5f3dc77c50ff58ceacb41f7fddd8e Mon Sep 17 00:00:00 2001 From: Stanley Date: Mon, 9 Dec 2024 22:30:26 -0500 Subject: [PATCH 13/19] god bless greg it works --- .../cross-chain/data-table.tsx | 77 ++++++++++------- .../contract-deploy-form/custom-contract.tsx | 85 +++++++++++-------- .../modules/common/installPublishedModule.ts | 5 ++ .../extensions/prebuilts/deploy-published.ts | 2 +- 4 files changed, 98 insertions(+), 71 deletions(-) diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx index 10aa67890a7..2366c6850b6 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx @@ -34,7 +34,9 @@ import { useTxNotifications } from "hooks/useTxNotifications"; import { ZERO_ADDRESS, defineChain, + eth_getTransactionCount, getContract, + getRpcClient, readContract, sendTransaction, waitForReceipt, @@ -205,44 +207,53 @@ export function DataTable({ client, }); - await Promise.all( - moduleDeployData.map(async (m) => { - let moduleData: `0x${string}` | undefined; + const rpcRequest = getRpcClient({ + client, + chain, + }); + const currentNonce = await eth_getTransactionCount(rpcRequest, { + address: activeAccount.address, + }); - const moduleInstallParams = m.deployMetadata.abi.find( - (abiType) => - (abiType as AbiFunction).name === "encodeBytesOnInstall", - ) as AbiFunction | undefined; + for (const [i, m] of moduleDeployData.entries()) { + let moduleData: `0x${string}` | 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 moduleInstallParams = m.deployMetadata.abi.find( + (abiType) => + (abiType as AbiFunction).name === "encodeBytesOnInstall", + ) as AbiFunction | undefined; - const installTransaction = installPublishedModule({ - contract, - account: activeAccount, - moduleName: m.deployMetadata.name, - publisher: m.deployMetadata.publisher, - version: m.deployMetadata.version, - moduleData, - }); + 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 txResult = await sendTransaction({ - transaction: installTransaction, - account: activeAccount, - }); + const installTransaction = installPublishedModule({ + contract, + account: activeAccount, + moduleName: m.deployMetadata.name, + publisher: m.deployMetadata.publisher, + version: m.deployMetadata.version, + moduleData, + nonce: currentNonce + i, + }); - return await waitForReceipt(txResult); - }), - ); + const txResult = await sendTransaction({ + transaction: installTransaction, + account: activeAccount, + }); + + await waitForReceipt(txResult); + // can't handle parallel transactions, so wait a bit + await new Promise((resolve) => setTimeout(resolve, 1000)); + } } deployStatusModal.nextStep(); diff --git a/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx b/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx index 23a742e6d52..e574f2b6f08 100644 --- a/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx +++ b/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx @@ -28,7 +28,9 @@ import { useCallback, useMemo } from "react"; import { FormProvider, type UseFormReturn, useForm } from "react-hook-form"; import { ZERO_ADDRESS, + eth_getTransactionCount, getContract, + getRpcClient, sendTransaction, waitForReceipt, } from "thirdweb"; @@ -478,45 +480,54 @@ export const CustomContractForm: React.FC = ({ chain: walletChain, }); - if (isSuperchainInterop && moduleDeployData) { - await Promise.allSettled( - 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: coreContract, - account: activeAccount, - moduleName: m.deployMetadata.name, - publisher: m.deployMetadata.publisher, - version: m.deployMetadata.version, - moduleData, - }); + const rpcRequest = getRpcClient({ + client: thirdwebClient, + chain: walletChain, + }); + const currentNonce = await eth_getTransactionCount(rpcRequest, { + address: activeAccount.address, + }); - const txResult = await sendTransaction({ - transaction: installTransaction, - account: activeAccount, - }); + if (isSuperchainInterop && moduleDeployData) { + for (const [i, m] of moduleDeployData.entries()) { + 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), + ); + } - return await waitForReceipt(txResult); - }), - ); + const installTransaction = installPublishedModule({ + contract: coreContract, + account: activeAccount, + moduleName: m.deployMetadata.name, + publisher: m.deployMetadata.publisher, + version: m.deployMetadata.version, + moduleData, + nonce: currentNonce + i, + }); + + const txResult = await sendTransaction({ + transaction: installTransaction, + account: activeAccount, + }); + + await waitForReceipt(txResult); + // can't handle parallel transactions, so wait a bit + await new Promise((resolve) => setTimeout(resolve, 1000)); + } } return coreContractAddress; diff --git a/packages/thirdweb/src/extensions/modules/common/installPublishedModule.ts b/packages/thirdweb/src/extensions/modules/common/installPublishedModule.ts index 0d43f338e81..ff022357c1d 100644 --- a/packages/thirdweb/src/extensions/modules/common/installPublishedModule.ts +++ b/packages/thirdweb/src/extensions/modules/common/installPublishedModule.ts @@ -14,6 +14,7 @@ export type InstallPublishedModuleOptions = { version?: string; constructorParams?: Record; moduleData?: `0x${string}`; + nonce?: number; }; /** @@ -43,10 +44,14 @@ export function installPublishedModule(options: InstallPublishedModuleOptions) { constructorParams, publisher, moduleData, + nonce, } = options; return installModule({ contract, + overrides: { + nonce, + }, asyncParams: async () => { const deployedModule = await getOrDeployInfraForPublishedContract({ chain: contract.chain, diff --git a/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts b/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts index a0b03269ad9..ddcc0a8ee13 100644 --- a/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts +++ b/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts @@ -217,7 +217,7 @@ export async function deployContractfromDeployMetadata( account, modules, }); - return await deployViaAutoFactory({ + return deployViaAutoFactory({ client, chain, account, From 828c198443d75eaa60f40a396379aa9e497c9f09 Mon Sep 17 00:00:00 2001 From: Stanley Date: Tue, 10 Dec 2024 21:47:23 -0500 Subject: [PATCH 14/19] addressed comments in PR --- .../contract-deploy-form/custom-contract.tsx | 26 ++++++++++++------- .../deployment/deploy-via-autofactory.ts | 19 +++++++++----- .../extensions/prebuilts/deploy-published.ts | 4 --- .../IContractFactory/events/ProxyDeployed.ts | 10 +++++++ .../actions/send-and-confirm-transaction.ts | 4 +-- .../src/utils/any-evm/deploy-metadata.ts | 2 +- 6 files changed, 41 insertions(+), 24 deletions(-) diff --git a/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx b/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx index e574f2b6f08..a892c03862c 100644 --- a/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx +++ b/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx @@ -458,7 +458,10 @@ export const CustomContractForm: React.FC = ({ const moduleDeployData = modules?.map((m) => ({ deployMetadata: m, - initializeParams: params.moduleData[m.name], + initializeParams: + m.name === "SuperChainInterop" + ? { superchainBridge: "0x4200000000000000000000000000000000000010" } + : params.moduleData[m.name], })); const coreContractAddress = await deployContractfromDeployMetadata({ @@ -480,15 +483,15 @@ export const CustomContractForm: React.FC = ({ chain: walletChain, }); - const rpcRequest = getRpcClient({ - client: thirdwebClient, - chain: walletChain, - }); - const currentNonce = await eth_getTransactionCount(rpcRequest, { - address: activeAccount.address, - }); - if (isSuperchainInterop && moduleDeployData) { + const rpcRequest = getRpcClient({ + client: thirdwebClient, + chain: walletChain, + }); + const currentNonce = await eth_getTransactionCount(rpcRequest, { + address: activeAccount.address, + }); + for (const [i, m] of moduleDeployData.entries()) { let moduleData: `0x${string}` | undefined; @@ -849,7 +852,10 @@ export const CustomContractForm: React.FC = ({ {isModular && modules && modules.length > 0 && ( mod.name !== "SuperChainInterop", + )} isTWPublisher={isTWPublisher} /> )} diff --git a/packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts b/packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts index 90cb042874b..ce2a35405e2 100644 --- a/packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts +++ b/packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts @@ -1,5 +1,8 @@ import { parseEventLogs } from "../../event/actions/parse-logs.js"; -import { proxyDeployedEvent } from "../../extensions/thirdweb/__generated__/IContractFactory/events/ProxyDeployed.js"; +import { + modifiedProxyDeployedEvent, + proxyDeployedEvent, +} from "../../extensions/thirdweb/__generated__/IContractFactory/events/ProxyDeployed.js"; import { deployProxyByImplementation } from "../../extensions/thirdweb/__generated__/IContractFactory/write/deployProxyByImplementation.js"; import { eth_blockNumber } from "../../rpc/actions/eth_blockNumber.js"; import { getRpcClient } from "../../rpc/rpc.js"; @@ -97,8 +100,13 @@ export async function deployViaAutoFactory( transaction: tx, account, }); + + // TODO: remove this once the modified version of TWCloneFactory has been published + const proxyEvent = salt?.startsWith("0x0101") + ? modifiedProxyDeployedEvent() + : proxyDeployedEvent(); const decodedEvent = parseEventLogs({ - events: [proxyDeployedEvent()], + events: [proxyEvent], logs: receipt.logs, }); if (decodedEvent.length === 0 || !decodedEvent[0]) { @@ -143,19 +151,18 @@ export async function deployViaAutoFactoryWithImplementationParams( size: 32, }); - const asd = { + const tx = deployProxyByImplementation({ contract: cloneFactoryContract, data: initializeData || "0x", implementation: implementationAddress, salt: parsedSalt, - }; - const tx = deployProxyByImplementation(asd); + }); const receipt = await sendAndConfirmTransaction({ transaction: tx, account, }); const decodedEvent = parseEventLogs({ - events: [proxyDeployedEvent()], + events: [modifiedProxyDeployedEvent()], logs: receipt.logs, }); if (decodedEvent.length === 0 || !decodedEvent[0]) { diff --git a/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts b/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts index ddcc0a8ee13..51aa12fd861 100644 --- a/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts +++ b/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts @@ -185,10 +185,6 @@ export async function deployContractfromDeployMetadata( publisher: deployMetadata.publisher, }); - console.error("is superchain interop", isSuperchainInterop); - console.error("is crosschain: ", isCrosschain); - console.error("initialize data: ", initializeData); - // TODO: remove this once the modified version of TWCloneFactory // has been published under the thirdweb wallet const modifiedCloneFactoryContract = getContract({ diff --git a/packages/thirdweb/src/extensions/thirdweb/__generated__/IContractFactory/events/ProxyDeployed.ts b/packages/thirdweb/src/extensions/thirdweb/__generated__/IContractFactory/events/ProxyDeployed.ts index d966b2afc25..e9c7d7546fa 100644 --- a/packages/thirdweb/src/extensions/thirdweb/__generated__/IContractFactory/events/ProxyDeployed.ts +++ b/packages/thirdweb/src/extensions/thirdweb/__generated__/IContractFactory/events/ProxyDeployed.ts @@ -39,6 +39,16 @@ export type ProxyDeployedEventFilters = Partial<{ * ``` */ export function proxyDeployedEvent(filters: ProxyDeployedEventFilters = {}) { + return prepareEvent({ + signature: + "event ProxyDeployed(address indexed implementation, address proxy, address indexed deployer)", + filters, + }); +} + + +// TODO: remove this once the modified version of TWCloneFactory has been published +export function modifiedProxyDeployedEvent(filters: ProxyDeployedEventFilters = {}) { return prepareEvent({ signature: "event ProxyDeployed(address indexed implementation, address proxy, address indexed deployer, bytes data)", diff --git a/packages/thirdweb/src/transaction/actions/send-and-confirm-transaction.ts b/packages/thirdweb/src/transaction/actions/send-and-confirm-transaction.ts index 69368820d72..68d635afd53 100644 --- a/packages/thirdweb/src/transaction/actions/send-and-confirm-transaction.ts +++ b/packages/thirdweb/src/transaction/actions/send-and-confirm-transaction.ts @@ -53,7 +53,5 @@ export async function sendAndConfirmTransaction( options: SendTransactionOptions, ): Promise { const submittedTx = await sendTransaction(options); - const receipt = await waitForReceipt(submittedTx); - - return receipt; + return waitForReceipt(submittedTx); } diff --git a/packages/thirdweb/src/utils/any-evm/deploy-metadata.ts b/packages/thirdweb/src/utils/any-evm/deploy-metadata.ts index af30d8c9c1a..365d9b882e0 100644 --- a/packages/thirdweb/src/utils/any-evm/deploy-metadata.ts +++ b/packages/thirdweb/src/utils/any-evm/deploy-metadata.ts @@ -207,7 +207,7 @@ export type ExtendedMetadata = { factoryAddresses?: Record; } | undefined; - deployType?: "standard" | "autoFactory" | "customFactory" | "crosschain"; + deployType?: "standard" | "autoFactory" | "customFactory"; routerType?: "none" | "plugin" | "dynamic" | "modular"; networksForDeployment?: { allNetworks?: boolean; From 429d10c95a1911b2ad7914b8fd17ae82ee922e52 Mon Sep 17 00:00:00 2001 From: Stanley Date: Wed, 11 Dec 2024 12:20:18 -0500 Subject: [PATCH 15/19] updated wording --- .../[chain_id]/[contractAddress]/cross-chain/data-table.tsx | 2 ++ .../contract-deploy-form/custom-contract.tsx | 2 ++ apps/dashboard/src/data/explore.ts | 5 +++-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx index 2366c6850b6..74f594d8f6b 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx @@ -235,6 +235,8 @@ export function DataTable({ ); } + console.log("nonce used: ", currentNonce + i); + const installTransaction = installPublishedModule({ contract, account: activeAccount, diff --git a/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx b/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx index a892c03862c..e07056abfb3 100644 --- a/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx +++ b/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx @@ -512,6 +512,8 @@ export const CustomContractForm: React.FC = ({ ); } + console.log("nonce used: ", currentNonce + i); + const installTransaction = installPublishedModule({ contract: coreContract, account: activeAccount, diff --git a/apps/dashboard/src/data/explore.ts b/apps/dashboard/src/data/explore.ts index a75809aefd8..f2dcabbfa5d 100644 --- a/apps/dashboard/src/data/explore.ts +++ b/apps/dashboard/src/data/explore.ts @@ -207,8 +207,9 @@ const CROSS_CHAIN = { "deployer.thirdweb.eth/ClaimableERC20", ], { - title: "Modular Token Drop", - description: "ERC20 Tokens that others can mint.", + title: "OP Superchain Modular Token Drop", + description: + "ERC-20 Tokens crosschain compatible across OP Superchains", }, ], ], From 71b8a975b7faa3e3d7fd38dc2d5c96e3f1eb6391 Mon Sep 17 00:00:00 2001 From: Stanley Date: Wed, 11 Dec 2024 17:36:29 -0500 Subject: [PATCH 16/19] updated superchain bridge address --- .../[chain_id]/[contractAddress]/cross-chain/data-table.tsx | 2 +- .../contract-deploy-form/custom-contract.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx index 74f594d8f6b..cf814e944aa 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx @@ -187,7 +187,7 @@ export function DataTable({ // set superchain bridge address else if (showSuperchainBridgeFieldset(paramNames)) { returnVal.superchainBridge = - "0x4200000000000000000000000000000000000010"; // OP Superchain Bridge + "0x4200000000000000000000000000000000000028"; // OP Superchain Bridge } acc[mod.name] = returnVal; diff --git a/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx b/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx index e07056abfb3..4565da21471 100644 --- a/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx +++ b/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx @@ -460,7 +460,7 @@ export const CustomContractForm: React.FC = ({ deployMetadata: m, initializeParams: m.name === "SuperChainInterop" - ? { superchainBridge: "0x4200000000000000000000000000000000000010" } + ? { superchainBridge: "0x4200000000000000000000000000000000000028" } : params.moduleData[m.name], })); From 1a7f7cb0e4ca5ce962eb54a97113a1c52209ed9e Mon Sep 17 00:00:00 2001 From: Stanley Date: Sat, 14 Dec 2024 13:42:29 -0500 Subject: [PATCH 17/19] updated to show links --- apps/dashboard/next-env.d.ts | 2 +- .../[contractAddress]/cross-chain/data-table.tsx | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/dashboard/next-env.d.ts b/apps/dashboard/next-env.d.ts index 3cd7048ed94..725dd6f2451 100644 --- a/apps/dashboard/next-env.d.ts +++ b/apps/dashboard/next-env.d.ts @@ -3,4 +3,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx index cf814e944aa..fa30e208c24 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx @@ -31,6 +31,7 @@ import { showSuperchainBridgeFieldset, } from "components/contract-components/contract-deploy-form/modular-contract-default-modules-fieldset"; import { useTxNotifications } from "hooks/useTxNotifications"; +import Link from "next/link"; import { ZERO_ADDRESS, defineChain, @@ -87,6 +88,20 @@ export function DataTable({ { accessorKey: "network", header: "Network", + cell: ({ row }) => { + if (row.getValue("status") === "DEPLOYED") { + return ( + + {row.getValue("network")} + + ); + } + return row.getValue("network"); + }, }, { accessorKey: "chainId", From ec1a0a1bfa6e025baca350bcf8a38e59d2d1dc9d Mon Sep 17 00:00:00 2001 From: Stanley Date: Mon, 16 Dec 2024 20:08:12 -0700 Subject: [PATCH 18/19] implemented cross chain transfers --- .../cross-chain/data-table.tsx | 204 ++++++++++++++---- 1 file changed, 163 insertions(+), 41 deletions(-) diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx index fa30e208c24..c07e9ca6e7a 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx @@ -1,7 +1,14 @@ "use client"; -import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; import { Table, TableBody, @@ -12,6 +19,8 @@ import { TableRow, } from "@/components/ui/table"; import { getThirdwebClient } from "@/constants/thirdweb.server"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useMutation } from "@tanstack/react-query"; import { type ColumnDef, flexRender, @@ -32,13 +41,16 @@ import { } from "components/contract-components/contract-deploy-form/modular-contract-default-modules-fieldset"; import { useTxNotifications } from "hooks/useTxNotifications"; import Link from "next/link"; +import { useForm } from "react-hook-form"; import { ZERO_ADDRESS, defineChain, eth_getTransactionCount, getContract, getRpcClient, + prepareContractCall, readContract, + sendAndConfirmTransaction, sendTransaction, waitForReceipt, } from "thirdweb"; @@ -55,6 +67,7 @@ import { encodeAbiParameters, padHex, } from "thirdweb/utils"; +import { z } from "zod"; type CrossChain = { id: number; @@ -63,6 +76,22 @@ type CrossChain = { status: "DEPLOYED" | "NOT_DEPLOYED"; }; +type ChainId = "84532" | "11155420" | "919" | "111557560" | "999999999"; + +const formSchema = z.object({ + amounts: z.object({ + "84532": z.string(), + "11155420": z.string(), + "919": z.string(), + "111557560": z.string(), + "999999999": z.string(), + }), +}); +type FormSchema = z.output; + +const positiveIntegerRegex = /^[0-9]\d*$/; +const superchainBridgeAddress = "0x4200000000000000000000000000000000000028"; + export function DataTable({ data, coreMetadata, @@ -84,6 +113,64 @@ export function DataTable({ "Failed to deploy contract", ); + const form = useForm({ + resolver: zodResolver(formSchema), + values: { + amounts: { + "84532": "", // Base + "11155420": "", // OP testnet + "919": "", // Mode Network + "111557560": "", // Cyber + "999999999": "", // Zora + }, + }, + }); + + const crossChainTransfer = async (chainId: ChainId) => { + if (!activeAccount) { + throw new Error("Account not connected"); + } + const amount = form.getValues().amounts[chainId]; + if (!positiveIntegerRegex.test(amount)) { + form.setError(`amounts.${chainId}`, { message: "Invalid Amount" }); + return; + } + + const superChainBridge = getContract({ + address: superchainBridgeAddress, + chain: coreContract.chain, + client: coreContract.client, + }); + + const sendErc20Tx = prepareContractCall({ + contract: superChainBridge, + method: + "function sendERC20(address _token, address _to, uint256 _amount, uint256 _chainId)", + params: [ + coreContract.address, + activeAccount.address, + BigInt(amount), + BigInt(chainId), + ], + }); + + await sendAndConfirmTransaction({ + account: activeAccount, + transaction: sendErc20Tx, + }); + }; + + const crossChainTransferNotifications = useTxNotifications( + "Successfully submitted cross chain transfer", + "Failed to submit cross chain transfer", + ); + + const crossChainTransferMutation = useMutation({ + mutationFn: crossChainTransfer, + onSuccess: crossChainTransferNotifications.onSuccess, + onError: crossChainTransferNotifications.onError, + }); + const columns: ColumnDef[] = [ { accessorKey: "network", @@ -112,10 +199,40 @@ export function DataTable({ header: "Status", cell: ({ row }) => { if (row.getValue("status") === "DEPLOYED") { - return Deployed; + return ( + ( + + +
+ + +
+
+ +
+ )} + /> + ); } return ( - ); @@ -250,8 +367,6 @@ export function DataTable({ ); } - console.log("nonce used: ", currentNonce + i); - const installTransaction = installPublishedModule({ contract, account: activeAccount, @@ -285,42 +400,49 @@ export function DataTable({ }; return ( - - - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { - return ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext(), - )} - - ); - })} - - ))} - - - {table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - +
+ + +
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ); + })} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ))} + ))} - - ))} - -
- -
+ + + + + + ); } From 52c3f4453bae4642f0f48a659278e45aa7173797 Mon Sep 17 00:00:00 2001 From: Stanley Date: Wed, 18 Dec 2024 07:01:22 +0900 Subject: [PATCH 19/19] tweaked input + button on crosschain transfer for OP Interop --- .../cross-chain/data-table.tsx | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx index c07e9ca6e7a..ceddff9897a 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx @@ -18,6 +18,7 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; +import { ToolTipLabel } from "@/components/ui/tooltip"; import { getThirdwebClient } from "@/constants/thirdweb.server"; import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation } from "@tanstack/react-query"; @@ -207,20 +208,27 @@ export function DataTable({ render={({ field }) => ( -
- - -
+ +
+ + +
+