Skip to content

Commit

Permalink
Deploy modular core with default extensions (#3524)
Browse files Browse the repository at this point in the history
## Problem solved

Short description of the bug fixed or feature added

## Changes made

- [ ] Public API changes: list the public API changes made if any
- [ ] Internal API changes: explain the internal logic changes

## How to test

- [ ] Automated tests: link to unit test file
- [ ] Manual tests: step by step instructions on how to test

## Contributor NFT

Paste in your wallet address below and we will airdrop you a special NFT when your pull request is merged.

```Address: ```

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces support for modular extensions in contract publishing forms.

### Detailed summary
- Added support for modular extensions in contract publishing forms
- Updated extension input fields to handle modular extensions
- Implemented extension installation parameters for modular extensions

> The following files were skipped due to too many changes: `apps/dashboard/src/components/contract-components/contract-publish-form/index.tsx`, `legacy_packages/sdk/src/evm/common/any-evm-utils/getModularDeploymentInfo.ts`, `legacy_packages/sdk/src/evm/core/sdk.ts`

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
  • Loading branch information
kumaryash90 committed Jul 25, 2024
1 parent 5c9af85 commit 8599fbf
Show file tree
Hide file tree
Showing 206 changed files with 23,344 additions and 730 deletions.
5 changes: 5 additions & 0 deletions .changeset/early-mirrors-divide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"thirdweb": patch
---

Modular contracts deployment setup
3 changes: 2 additions & 1 deletion apps/dashboard/src/@/components/ui/CopyAddressButton.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import { cn } from "../../lib/utils";
import { CopyTextButton } from "./CopyTextButton";

export function CopyAddressButton(props: {
Expand All @@ -22,7 +23,7 @@ export function CopyAddressButton(props: {
textToCopy={props.address}
textToShow={shortenedAddress}
tooltip="Copy Address"
className="font-mono text-sm"
className={cn("font-mono text-sm", props.className)}
variant={props.variant}
copyIconPosition={props.copyIconPosition}
/>
Expand Down
7 changes: 5 additions & 2 deletions apps/dashboard/src/@/components/ui/alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const AlertTitle = React.forwardRef<
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
className={cn("mb-1.5 font-medium leading-none tracking-tight", className)}
{...props}
/>
));
Expand All @@ -53,7 +53,10 @@ const AlertDescription = React.forwardRef<
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm [&_p]:leading-relaxed", className)}
className={cn(
"text-sm [&_p]:leading-relaxed text-secondary-foreground",
className,
)}
{...props}
/>
));
Expand Down
4 changes: 2 additions & 2 deletions apps/dashboard/src/@/components/ui/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const DialogOverlay = React.forwardRef<
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
"fixed inset-0 z-[10000] bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className,
)}
{...props}
Expand All @@ -38,7 +38,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
"fixed left-[50%] top-[50%] z-[10001] grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className,
)}
{...props}
Expand Down
2 changes: 1 addition & 1 deletion apps/dashboard/src/@/components/ui/sonner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const Toaster = ({ ...props }: ToasterProps) => {
toastOptions={{
classNames: {
toast:
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg group-[.toaster]:border",
description: "group-[.toast]:text-muted-foreground",
actionButton:
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
Expand Down
2 changes: 1 addition & 1 deletion apps/dashboard/src/@/components/ui/tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const TooltipContent = React.forwardRef<
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
"z-50 overflow-hidden rounded-md border border-border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className,
)}
{...props}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { Flex, FormControl, Input, Textarea } from "@chakra-ui/react";
import { FileInput } from "components/shared/FileInput";
import { useImageFileOrUrl } from "hooks/useImageFileOrUrl";
import type { UseFormReturn } from "react-hook-form";
import { FormErrorMessage, FormLabel, Heading, Text } from "tw-components";
import type { useContractPublishMetadataFromURI } from "../hooks";
import type { CustomContractDeploymentForm } from "./custom-contract";

interface ContractMetadataFieldsetProps {
// biome-ignore lint/suspicious/noExplicitAny: FIXME
form: UseFormReturn<any, any>;
form: CustomContractDeploymentForm;
metadata: ReturnType<typeof useContractPublishMetadataFromURI>;
}

Expand Down Expand Up @@ -74,7 +73,7 @@ export const ContractMetadataFieldset: React.FC<
/>
<FormErrorMessage>
{
form.getFieldState("contractMetadata..name", form.formState)
form.getFieldState("contractMetadata.name", form.formState)
.error?.message
}
</FormErrorMessage>
Expand Down Expand Up @@ -105,7 +104,10 @@ export const ContractMetadataFieldset: React.FC<
<FormControl
isDisabled={!metadata.isSuccess}
isInvalid={
!!form.getFieldState("description", form.formState).error
!!form.getFieldState(
"contractMetadata.description",
form.formState,
).error
}
>
<FormLabel>Description</FormLabel>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ import { useTxNotifications } from "hooks/useTxNotifications";
import { replaceTemplateValues } from "lib/deployment/template-values";
import { useRouter } from "next/router";
import { useMemo } from "react";
import { FormProvider, useForm } from "react-hook-form";
import { FormProvider, type UseFormReturn, useForm } from "react-hook-form";
import { FiHelpCircle } from "react-icons/fi";
import { encodeAbiParameters } from "thirdweb/utils";
import invariant from "tiny-invariant";
import {
Card,
Expand All @@ -34,6 +35,7 @@ import {
Text,
TrackedLink,
} from "tw-components";
import { Spinner } from "../../../@/components/ui/Spinner/Spinner";
import {
useConstructorParamsFromABI,
useContractEnabledExtensions,
Expand All @@ -46,6 +48,10 @@ import {
useTransactionsForDeploy,
} from "../hooks";
import { ContractMetadataFieldset } from "./contract-metadata-fieldset";
import {
ModularContractDefaultExtensionsFieldset,
useModularContractsDefaultExtensionsInstallParams,
} from "./modular-contract-default-extensions-fieldset";
import { Param } from "./param";
import { PlatformFeeFieldset } from "./platform-fee-fieldset";
import { PrimarySaleFieldset } from "./primary-sale-fieldset";
Expand All @@ -63,6 +69,25 @@ interface CustomContractFormProps {
walletAddress: string | undefined;
}

export type CustomContractDeploymentFormData = {
addToDashboard: boolean;
deployDeterministic: boolean;
saltForCreate2: string;
signerAsSalt: boolean;
deployParams: Record<string, string>;
modularContractDefaultExtensionsInstallParams: Record<string, string>[];
contractMetadata?: {
name: string;
description: string;
symbol: string;
image: File;
};
recipients?: Recipient[];
};

export type CustomContractDeploymentForm =
UseFormReturn<CustomContractDeploymentFormData>;

const CustomContractForm: React.FC<CustomContractFormProps> = ({
ipfsHash,
version,
Expand Down Expand Up @@ -113,6 +138,15 @@ const CustomContractForm: React.FC<CustomContractFormProps> = ({
fullPublishMetadata.data?.deployType === "autoFactory" ||
fullPublishMetadata.data?.deployType === "customFactory";

const isModular = fullPublishMetadata.data?.routerType === "modular";
const defaultExtensions = fullPublishMetadata.data?.defaultExtensions;

const modularContractDefaultExtensionsInstallParams =
useModularContractsDefaultExtensionsInstallParams({
defaultExtensions,
isQueryEnabled: isModular,
});

const deployParams = isFactoryDeployment
? initializerParams
: constructorParams;
Expand Down Expand Up @@ -164,32 +198,19 @@ const CustomContractForm: React.FC<CustomContractFormProps> = ({
signerAsSalt: true,
deployParams: parsedDeployParams,
recipients: [{ address: connectedWallet || "", sharesBps: 10000 }],
modularContractDefaultExtensionsInstallParams: [],
}),
[parsedDeployParams, isAccountFactory, connectedWallet],
);

const form = useForm<{
addToDashboard: boolean;
deployDeterministic: boolean;
saltForCreate2: string;
signerAsSalt: boolean;
deployParams: Record<string, string>;
contractMetadata?: {
name: string;
description: string;
symbol: string;
image: string;
};
recipients?: Recipient[];
}>({
const form = useForm<CustomContractDeploymentFormData>({
defaultValues: transformedQueryData,
values: transformedQueryData,
resetOptions: {
keepDirty: true,
keepDirtyValues: true,
},
});

const formDeployParams = form.watch("deployParams");

const anyHiddenParams = Object.keys(formDeployParams).some((paramKey) => {
Expand Down Expand Up @@ -282,33 +303,58 @@ const CustomContractForm: React.FC<CustomContractFormProps> = ({
direction="column"
id="custom-contract-form"
as="form"
onSubmit={form.handleSubmit(async (d) => {
onSubmit={form.handleSubmit(async (formData) => {
if (!selectedChain) {
return;
}
const deployData = {
ipfsHash,
constructorParams: d.deployParams,
contractMetadata: d,
constructorParams: formData.deployParams,
contractMetadata: formData,
publishMetadata: compilerMetadata.data,
chainId: selectedChain,
is_proxy: fullPublishMetadata.data?.isDeployableViaProxy,
is_factory: fullPublishMetadata.data?.isDeployableViaProxy,
};
// always respect this since even factory deployments cannot auto-add to registry anymore
const addToDashboard = d.addToDashboard;
const addToDashboard = formData.addToDashboard;
trackEvent({
category: "custom-contract",
action: "deploy",
label: "attempt",
deployData,
});

const deployParams = { ...formData.deployParams };

// if Modular contract has extensions
if (isModular && modularContractDefaultExtensionsInstallParams.data) {
const extensionInstallData: string[] =
modularContractDefaultExtensionsInstallParams.data.map(
(ext, extIndex) => {
return encodeAbiParameters(
// param name+type []
ext.params.map((p) => ({ name: p.name, type: p.type })),
// value []
Object.values(
formData.modularContractDefaultExtensionsInstallParams[
extIndex
] || {},
),
);
},
);

deployParams._extensionInstallData =
JSON.stringify(extensionInstallData);
}

deploy.mutate(
{
...d,
...formData,
address: walletAddress,
addToDashboard,
deployParams,
},
{
onSuccess: (deployedContractAddress) => {
Expand Down Expand Up @@ -377,13 +423,15 @@ const CustomContractForm: React.FC<CustomContractFormProps> = ({
>
{Object.keys(formDeployParams).length > 0 && (
<>
{/* Info */}
<Flex direction="column">
<Heading size="subtitle.md">Contract Parameters</Heading>
<Text size="body.md">
Parameters the contract specifies to be passed in during
deployment.
</Text>
</Flex>

<Flex gap={4} flexDir="column">
{hasContractURI && (
<ContractMetadataFieldset
Expand All @@ -397,28 +445,58 @@ const CustomContractForm: React.FC<CustomContractFormProps> = ({
{hasTrustedForwarders && (
<TrustedForwardersFieldset form={form} />
)}
{Object.keys(formDeployParams).map((paramKey) => {
const deployParam = deployParams.find(
// biome-ignore lint/suspicious/noExplicitAny: FIXME
(p: any) => p.name === paramKey,
);
const contructorParams =
fullPublishMetadata.data?.constructorParams || {};
const extraMetadataParam = contructorParams[paramKey];

if (shouldHide(paramKey) || extraMetadataParam?.hidden) {
return null;
}

return (
<Param
key={paramKey}
paramKey={paramKey}
deployParam={deployParam}
extraMetadataParam={extraMetadataParam}
/>
);
})}
{Object.keys(formDeployParams)
.filter((paramName) => {
if (
isModular &&
(paramName === "_extensionInstallData" ||
paramName === "_extensions")
) {
return false;
}

return true;
})
.map((paramKey) => {
const deployParam = deployParams.find(
(p) => p.name === paramKey,
);
const contructorParams =
fullPublishMetadata.data?.constructorParams || {};
const extraMetadataParam = contructorParams[paramKey];

if (shouldHide(paramKey) || extraMetadataParam?.hidden) {
return null;
}

return (
<Param
key={paramKey}
paramKey={paramKey}
deployParam={deployParam}
extraMetadataParam={extraMetadataParam}
/>
);
})}

{isModular && (
<>
{modularContractDefaultExtensionsInstallParams.data ? (
<ModularContractDefaultExtensionsFieldset
form={form}
installParams={
modularContractDefaultExtensionsInstallParams.data
}
/>
) : (
<div className="min-h-[250px] flex justify-center items-center">
<Spinner className="size-8" />
</div>
)}
</>
)}

{(anyHiddenParams || hasPlatformFee) && (
<Accordion allowToggle>
<AccordionItem borderColor="borderColor" borderBottom="none">
Expand All @@ -440,8 +518,7 @@ const CustomContractForm: React.FC<CustomContractFormProps> = ({
{hasPlatformFee && <PlatformFeeFieldset form={form} />}
{Object.keys(formDeployParams).map((paramKey) => {
const deployParam = deployParams.find(
// biome-ignore lint/suspicious/noExplicitAny: FIXME
(p: any) => p.name === paramKey,
(p) => p.name === paramKey,
);
const contructorParams =
fullPublishMetadata.data?.constructorParams || {};
Expand Down
Loading

0 comments on commit 8599fbf

Please sign in to comment.