v5 deploy dashboard changes (#4498)
<!-- start pr-codex -->
## PR-Codex overview
This PR deprecates functions, updates types, refactors components, and adds modules for contract deployment.
### Detailed summary
- Deprecated functions in favor of new ones
- Updated types and added necessary type assertions
- Refactored components with new props and imports
- Added modules for contract deployment and handling metadata
> The following files were skipped due to too many changes: `apps/dashboard/src/app/(dashboard)/published-contract/utils/module-base-64.ts`, `apps/dashboard/src/app/(dashboard)/published-contract/[publisher]/[contract_id]/components/module-list.client.tsx`, `apps/dashboard/src/app/(dashboard)/published-contract/components/uri-based-deploy.tsx`, `pnpm-lock.yaml`, `apps/dashboard/src/app/(dashboard)/published-contract/components/contract-actions-deploy.client.tsx`, `apps/dashboard/src/app/(dashboard)/published-contract/components/contract-info.tsx`, `apps/dashboard/src/app/(dashboard)/published-contract/components/contract-actions-published.client.tsx`, `packages/thirdweb/src/utils/any-evm/deploy-metadata.ts`, `apps/dashboard/src/app/(dashboard)/published-contract/[publisher]/[contract_id]/utils/getPublishedContractsWithPublisherMapping.ts`, `packages/thirdweb/package.json`, `apps/dashboard/src/app/(dashboard)/contracts/deploy/[compiler_uri]/page.tsx`, `apps/dashboard/src/contract-ui/tabs/code/components/code-overview.tsx`, `apps/dashboard/src/components/contract-components/contract-deploy-form/trusted-forwarders-fieldset.tsx`, `apps/dashboard/src/app/(dashboard)/published-contract/[publisher]/[contract_id]/layout.tsx`, `apps/dashboard/src/components/contract-components/contract-deploy-form/contract-metadata-fieldset.tsx`, `apps/dashboard/src/app/(dashboard)/published-contract/components/contract-header.tsx`, `apps/dashboard/src/app/(dashboard)/published-contract/[publisher]/[contract_id]/components/breadcrumbs.client.tsx`, `apps/dashboard/src/app/(dashboard)/published-contract/[publisher]/[contract_id]/opengraph-image.tsx`, `apps/dashboard/src/app/(dashboard)/published-contract/[publisher]/[contract_id]/page.tsx`, `apps/dashboard/src/app/(dashboard)/published-contract/[publisher]/[contract_id]/[version]/opengraph-image.tsx`, `packages/thirdweb/src/utils/abi/normalizeFunctionParams.ts`, `apps/dashboard/src/app/(dashboard)/published-contract/components/version-selector.tsx`, `packages/thirdweb/src/contract/deployment/deploy-with-abi.ts`, `apps/dashboard/src/contract-ui/tabs/manage/components/getModuleInstalledParams.ts`, `packages/thirdweb/src/extensions/prebuilts/get-required-transactions.test.ts`, `apps/dashboard/src/app/(dashboard)/published-contract/[publisher]/[contract_id]/[version]/page.tsx`, `apps/dashboard/src/app/(dashboard)/published-contract/components/publish-based-deploy.tsx`, `apps/dashboard/src/components/contract-components/fetch-contracts-with-versions.ts`, `apps/dashboard/src/components/contract-functions/contract-function.tsx`, `apps/dashboard/src/components/contract-components/contract-deploy-form/modular-contract-default-modules-fieldset.tsx`, `packages/thirdweb/src/extensions/prebuilts/get-required-transactions.ts`, `apps/dashboard/src/pages/api/og/publishedContract.tsx`, `apps/dashboard/src/components/contract-components/published-contract/index.tsx`, `apps/dashboard/src/components/contract-components/hooks.ts`, `apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx`
> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`
<!-- end pr-codex -->
This commit is contained in:
@@ -6,8 +6,6 @@ const LOGGED_IN_ONLY_PATHS = [
|
||||
// anything that _starts_ with /cli is logged in only
|
||||
"/cli",
|
||||
"/support",
|
||||
|
||||
// TODO: add any other logged in only paths here
|
||||
];
|
||||
|
||||
export function isLoginRequired(pathname: string) {
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { setOverrides } from "lib/vercel-utils";
|
||||
import { fetchDeployMetadata } from "thirdweb/contract";
|
||||
import { thirdwebClient } from "../../../../../@/constants/client";
|
||||
import { DeployContractInfo } from "../../../published-contract/components/contract-info";
|
||||
import { DeployFormForUri } from "../../../published-contract/components/uri-based-deploy";
|
||||
|
||||
setOverrides();
|
||||
|
||||
type DirectDeployPageProps = {
|
||||
params: {
|
||||
compiler_uri: string;
|
||||
};
|
||||
};
|
||||
|
||||
export default async function DirectDeployPage(props: DirectDeployPageProps) {
|
||||
const metadata = await fetchDeployMetadata({
|
||||
client: thirdwebClient,
|
||||
// force `ipfs://` prefix
|
||||
uri: props.params.compiler_uri.startsWith("ipfs://")
|
||||
? props.params.compiler_uri
|
||||
: `ipfs://${props.params.compiler_uri}`,
|
||||
});
|
||||
return (
|
||||
<div className="container py-8 flex flex-col gap-4">
|
||||
<DeployContractInfo
|
||||
name={metadata.name}
|
||||
displayName={metadata.displayName}
|
||||
description={metadata.description}
|
||||
logo={metadata.logo}
|
||||
/>
|
||||
<DeployFormForUri contractMetadata={metadata} modules={null} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
import { AppFooter } from "@/components/blocks/app-footer";
|
||||
import { DashboardHeader } from "app/components/Header/DashboardHeader";
|
||||
import { ErrorProvider } from "../../contexts/error-handler";
|
||||
|
||||
export default function DashboardLayout(props: { children: React.ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<ErrorProvider>
|
||||
<div className="flex flex-col h-full bg-background">
|
||||
<DashboardHeader />
|
||||
<main className="grow">{props.children}</main>
|
||||
<AppFooter />
|
||||
</div>
|
||||
</>
|
||||
</ErrorProvider>
|
||||
);
|
||||
}
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { setOverrides } from "lib/vercel-utils";
|
||||
import { DeployFormForPublishInfo } from "../../../../components/publish-based-deploy";
|
||||
import { moduleFromBase64 } from "../../../../utils/module-base-64";
|
||||
|
||||
setOverrides();
|
||||
|
||||
export default function PublishedContractVersionDeployPage({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: {
|
||||
publisher: string;
|
||||
contract_id: string;
|
||||
version: string;
|
||||
};
|
||||
searchParams: {
|
||||
module?: string[];
|
||||
};
|
||||
}) {
|
||||
const modules = searchParams.module
|
||||
?.map((m) => moduleFromBase64(m))
|
||||
.filter((m) => m !== null);
|
||||
return <DeployFormForPublishInfo {...params} modules={modules} />;
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
import { fetchPublisherProfile } from "components/contract-components/fetch-contracts-with-versions";
|
||||
import { format } from "date-fns/format";
|
||||
import { correctAndUniqueLicenses } from "lib/licenses";
|
||||
import { setOverrides } from "lib/vercel-utils";
|
||||
import { getPublishedContractsWithPublisherMapping } from "../utils/getPublishedContractsWithPublisherMapping";
|
||||
import { publishedContractOGImageTemplate } from "../utils/publishedContractOGImageTemplate";
|
||||
|
||||
export const runtime = "edge";
|
||||
|
||||
export const size = {
|
||||
width: 1200,
|
||||
height: 630,
|
||||
};
|
||||
|
||||
export default async function Image(props: {
|
||||
params: {
|
||||
publisher: string;
|
||||
contract_id: string;
|
||||
version: string;
|
||||
};
|
||||
}) {
|
||||
setOverrides();
|
||||
const { publisher, contract_id } = props.params;
|
||||
|
||||
const [publishedContracts, publisherProfile] = await Promise.all([
|
||||
getPublishedContractsWithPublisherMapping({
|
||||
publisher: publisher,
|
||||
contract_id: contract_id,
|
||||
}),
|
||||
fetchPublisherProfile(publisher),
|
||||
]);
|
||||
|
||||
const publishedContract =
|
||||
publishedContracts.find((p) => p.version === props.params.version) ||
|
||||
publishedContracts[0];
|
||||
|
||||
const publishedContractName =
|
||||
publishedContract?.displayName || publishedContract?.name;
|
||||
|
||||
return publishedContractOGImageTemplate({
|
||||
title: publishedContractName,
|
||||
description: publishedContract.description,
|
||||
version: publishedContract.version || "latest",
|
||||
publisher: publisherProfile?.name || publisher,
|
||||
license: correctAndUniqueLicenses(publishedContract.licenses),
|
||||
publishDate: format(
|
||||
new Date(
|
||||
Number.parseInt(publishedContract.publishTimestamp.toString() || "0") *
|
||||
1000,
|
||||
),
|
||||
"MMM dd, yyyy",
|
||||
),
|
||||
logo: publishedContract.logo,
|
||||
publisherAvatar: publisherProfile?.avatar,
|
||||
});
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { thirdwebClient } from "@/constants/client";
|
||||
import { SimpleGrid } from "@chakra-ui/react";
|
||||
import { isAddress } from "thirdweb";
|
||||
import { resolveAddress } from "thirdweb/extensions/ens";
|
||||
import { ChakraProviderSetup } from "../../../../../../@/components/ChakraProviderSetup";
|
||||
import { fetchPublishedContractVersions } from "../../../../../../components/contract-components/fetch-contracts-with-versions";
|
||||
import { PublishedContract } from "../../../../../../components/contract-components/published-contract";
|
||||
import { setOverrides } from "../../../../../../lib/vercel-utils";
|
||||
import { PublishedActions } from "../../../components/contract-actions-published.client";
|
||||
import { DeployContractHeader } from "../../../components/contract-header";
|
||||
|
||||
setOverrides();
|
||||
|
||||
function mapThirdwebPublisher(publisher: string) {
|
||||
if (publisher === "thirdweb.eth") {
|
||||
return "deployer.thirdweb.eth";
|
||||
}
|
||||
return publisher;
|
||||
}
|
||||
|
||||
type PublishedContractDeployPageProps = {
|
||||
params: {
|
||||
publisher: string;
|
||||
contract_id: string;
|
||||
version: string;
|
||||
};
|
||||
};
|
||||
|
||||
export default async function PublishedContractPage(
|
||||
props: PublishedContractDeployPageProps,
|
||||
) {
|
||||
// resolve ENS if required
|
||||
const publisherAddress = isAddress(props.params.publisher)
|
||||
? props.params.publisher
|
||||
: await resolveAddress({
|
||||
client: thirdwebClient,
|
||||
name: mapThirdwebPublisher(props.params.publisher),
|
||||
});
|
||||
|
||||
// get all the published versions of the contract
|
||||
const publishedContractVersions = await fetchPublishedContractVersions(
|
||||
publisherAddress,
|
||||
props.params.contract_id,
|
||||
);
|
||||
|
||||
// determine the "active" version of the contract based on the version that is passed
|
||||
const publishedContract =
|
||||
publishedContractVersions.find((v) => v.version === props.params.version) ||
|
||||
publishedContractVersions[0];
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeployContractHeader
|
||||
{...props.params}
|
||||
allVersions={publishedContractVersions}
|
||||
activeVersion={publishedContract}
|
||||
>
|
||||
<PublishedActions
|
||||
{...props.params}
|
||||
dispayName={publishedContract.displayName || publishedContract.name}
|
||||
/>
|
||||
</DeployContractHeader>
|
||||
<Separator />
|
||||
{/* TODO: remove the chakra things :) */}
|
||||
<ChakraProviderSetup>
|
||||
<SimpleGrid columns={12} gap={{ base: 6, md: 10 }} w="full">
|
||||
<PublishedContract
|
||||
publishedContract={publishedContract}
|
||||
walletOrEns={props.params.publisher}
|
||||
/>
|
||||
</SimpleGrid>
|
||||
</ChakraProviderSetup>
|
||||
</>
|
||||
);
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/components/ui/breadcrumb";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { Fragment } from "react";
|
||||
import { isAddress } from "thirdweb";
|
||||
import { shortenAddress } from "thirdweb/utils";
|
||||
|
||||
export function PublishedContractBreadcrumbs() {
|
||||
const pathname = usePathname() || "";
|
||||
const segments = pathname.split("/").filter((segment) => segment !== "");
|
||||
|
||||
return (
|
||||
<Breadcrumb className="py-4 px-6 border-b border-border">
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink aria-label="Explore" asChild>
|
||||
<Link href="/explore">Explore</Link>
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
|
||||
{segments.map((segment, index) => {
|
||||
const href = `/${segments.slice(0, index + 1).join("/")}`;
|
||||
const isLast = index === segments.length - 1;
|
||||
const renderedSegment = isAddress(segment)
|
||||
? shortenAddress(segment)
|
||||
: segment;
|
||||
|
||||
return (
|
||||
<Fragment key={href}>
|
||||
<BreadcrumbItem>
|
||||
{isLast ? (
|
||||
<BreadcrumbPage>{renderedSegment}</BreadcrumbPage>
|
||||
) : (
|
||||
<BreadcrumbLink asChild aria-label={segment}>
|
||||
<Link href={href}>{renderedSegment}</Link>
|
||||
</BreadcrumbLink>
|
||||
)}
|
||||
</BreadcrumbItem>
|
||||
{!isLast && <BreadcrumbSeparator />}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
);
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
"use client";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { useMemo } from "react";
|
||||
import { moduleFromBase64 } from "../../../utils/module-base-64";
|
||||
|
||||
export function useModules() {
|
||||
const modules = useSearchParams()?.getAll("module") || [];
|
||||
return useMemo(
|
||||
() => modules.map(moduleFromBase64).filter((m) => m !== null),
|
||||
[modules],
|
||||
);
|
||||
}
|
||||
|
||||
export function ModuleList() {
|
||||
const modules = useModules();
|
||||
|
||||
// if nothing, render nothing
|
||||
if (!modules.length) {
|
||||
return null;
|
||||
}
|
||||
// else render stuff
|
||||
return (
|
||||
<div className="flex flex-row flex-wrap gap-2">
|
||||
{modules.map((m) => (
|
||||
<Badge
|
||||
key={`${m.publisher}/${m.moduleId}/${m.version}`}
|
||||
variant="default"
|
||||
>
|
||||
{m.displayName}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { setOverrides } from "lib/vercel-utils";
|
||||
import { DeployFormForPublishInfo } from "../../../components/publish-based-deploy";
|
||||
import { moduleFromBase64 } from "../../../utils/module-base-64";
|
||||
|
||||
setOverrides();
|
||||
|
||||
type Props = {
|
||||
params: {
|
||||
publisher: string;
|
||||
contract_id: string;
|
||||
};
|
||||
searchParams: {
|
||||
module?: string[];
|
||||
};
|
||||
};
|
||||
|
||||
export default function PublishedContractDeployPage({
|
||||
params,
|
||||
searchParams,
|
||||
}: Props) {
|
||||
const modules = searchParams.module
|
||||
?.map((m) => moduleFromBase64(m))
|
||||
.filter((m) => m !== null);
|
||||
return <DeployFormForPublishInfo {...params} modules={modules} />;
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import { PublishedContractBreadcrumbs } from "./components/breadcrumbs.client";
|
||||
import { getPublishedContractsWithPublisherMapping } from "./utils/getPublishedContractsWithPublisherMapping";
|
||||
|
||||
type Params = { publisher: string; contract_id: string };
|
||||
|
||||
export default function PublishedContractLayout(props: {
|
||||
children: React.ReactNode;
|
||||
params: Params;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<PublishedContractBreadcrumbs />
|
||||
<div className="container flex flex-col gap-8 py-8">{props.children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: { params: Params }) {
|
||||
const { publisher, contract_id } = params;
|
||||
|
||||
const publishedContracts = await getPublishedContractsWithPublisherMapping({
|
||||
publisher: publisher,
|
||||
contract_id: contract_id,
|
||||
});
|
||||
|
||||
const publishedContract = publishedContracts[0];
|
||||
|
||||
const publishedContractName =
|
||||
publishedContract?.displayName || publishedContract?.name;
|
||||
|
||||
return {
|
||||
title: `${publishedContractName} | Published Smart Contract`,
|
||||
description: `${publishedContract.description}${publishedContract.description ? ". " : ""}Deploy ${publishedContractName} in one click with thirdweb.`,
|
||||
};
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
import { fetchPublisherProfile } from "components/contract-components/fetch-contracts-with-versions";
|
||||
import { format } from "date-fns/format";
|
||||
import { correctAndUniqueLicenses } from "lib/licenses";
|
||||
import { setOverrides } from "lib/vercel-utils";
|
||||
import { getPublishedContractsWithPublisherMapping } from "./utils/getPublishedContractsWithPublisherMapping";
|
||||
import { publishedContractOGImageTemplate } from "./utils/publishedContractOGImageTemplate";
|
||||
|
||||
export const runtime = "edge";
|
||||
|
||||
export const size = {
|
||||
width: 1200,
|
||||
height: 630,
|
||||
};
|
||||
|
||||
export default async function Image(props: {
|
||||
params: {
|
||||
publisher: string;
|
||||
contract_id: string;
|
||||
};
|
||||
}) {
|
||||
setOverrides();
|
||||
const { publisher, contract_id } = props.params;
|
||||
|
||||
const [publishedContracts, publisherProfile] = await Promise.all([
|
||||
getPublishedContractsWithPublisherMapping({
|
||||
publisher: publisher,
|
||||
contract_id: contract_id,
|
||||
}),
|
||||
fetchPublisherProfile(publisher),
|
||||
]);
|
||||
|
||||
const publishedContract = publishedContracts[0];
|
||||
|
||||
const publishedContractName =
|
||||
publishedContract?.displayName || publishedContract?.name;
|
||||
|
||||
return publishedContractOGImageTemplate({
|
||||
title: publishedContractName,
|
||||
description: publishedContract.description,
|
||||
version: publishedContract.version || "latest",
|
||||
publisher: publisherProfile?.name || publisher,
|
||||
license: correctAndUniqueLicenses(publishedContract.licenses),
|
||||
publishDate: format(
|
||||
new Date(
|
||||
Number.parseInt(publishedContract.publishTimestamp.toString() || "0") *
|
||||
1000,
|
||||
),
|
||||
"MMM dd, yyyy",
|
||||
),
|
||||
logo: publishedContract.logo,
|
||||
publisherAvatar: publisherProfile?.avatar,
|
||||
});
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { SimpleGrid } from "@chakra-ui/react";
|
||||
import { ChakraProviderSetup } from "../../../../../@/components/ChakraProviderSetup";
|
||||
import { PublishedContract } from "../../../../../components/contract-components/published-contract";
|
||||
import { setOverrides } from "../../../../../lib/vercel-utils";
|
||||
import { PublishedActions } from "../../components/contract-actions-published.client";
|
||||
import { DeployContractHeader } from "../../components/contract-header";
|
||||
import { getPublishedContractsWithPublisherMapping } from "./utils/getPublishedContractsWithPublisherMapping";
|
||||
|
||||
setOverrides();
|
||||
|
||||
type PublishedContractDeployPageProps = {
|
||||
params: {
|
||||
publisher: string;
|
||||
contract_id: string;
|
||||
};
|
||||
};
|
||||
|
||||
export default async function PublishedContractPage(
|
||||
props: PublishedContractDeployPageProps,
|
||||
) {
|
||||
const publishedContractVersions =
|
||||
await getPublishedContractsWithPublisherMapping({
|
||||
publisher: props.params.publisher,
|
||||
contract_id: props.params.contract_id,
|
||||
});
|
||||
|
||||
const publishedContract = publishedContractVersions[0];
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeployContractHeader
|
||||
{...props.params}
|
||||
allVersions={publishedContractVersions}
|
||||
activeVersion={publishedContract}
|
||||
>
|
||||
<PublishedActions
|
||||
{...props.params}
|
||||
dispayName={publishedContract.displayName || publishedContract.name}
|
||||
/>
|
||||
</DeployContractHeader>
|
||||
<Separator />
|
||||
{/* TODO: remove the chakra things :) */}
|
||||
<ChakraProviderSetup>
|
||||
<SimpleGrid columns={12} gap={{ base: 6, md: 10 }} w="full">
|
||||
<PublishedContract
|
||||
publishedContract={publishedContract}
|
||||
walletOrEns={props.params.publisher}
|
||||
/>
|
||||
</SimpleGrid>
|
||||
</ChakraProviderSetup>
|
||||
</>
|
||||
);
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import { thirdwebClient } from "@/constants/client";
|
||||
import { isAddress } from "thirdweb";
|
||||
import { resolveAddress } from "thirdweb/extensions/ens";
|
||||
import { fetchPublishedContractVersions } from "../../../../../../components/contract-components/fetch-contracts-with-versions";
|
||||
|
||||
function mapThirdwebPublisher(publisher: string) {
|
||||
if (publisher === "thirdweb.eth") {
|
||||
return "deployer.thirdweb.eth";
|
||||
}
|
||||
return publisher;
|
||||
}
|
||||
|
||||
export async function getPublishedContractsWithPublisherMapping(options: {
|
||||
publisher: string;
|
||||
contract_id: string;
|
||||
}) {
|
||||
const { publisher, contract_id } = options;
|
||||
|
||||
// resolve ENS
|
||||
const publisherAddress = isAddress(publisher)
|
||||
? publisher
|
||||
: await resolveAddress({
|
||||
client: thirdwebClient,
|
||||
name: mapThirdwebPublisher(publisher),
|
||||
});
|
||||
|
||||
// get all the published versions of the contract
|
||||
const publishedContractVersions = await fetchPublishedContractVersions(
|
||||
publisherAddress,
|
||||
contract_id,
|
||||
);
|
||||
|
||||
return publishedContractVersions;
|
||||
}
|
||||
+85
-86
@@ -1,37 +1,7 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { isProd } from "@/constants/env";
|
||||
import { ImageResponse } from "@vercel/og";
|
||||
import type { NextRequest } from "next/server";
|
||||
import { PublishedContractOG } from "og-lib/url-utils";
|
||||
|
||||
// Make sure the font exists in the specified path:
|
||||
export const config = {
|
||||
runtime: "edge",
|
||||
};
|
||||
|
||||
const image = fetch(
|
||||
new URL("og-lib/assets/publish/background.png", import.meta.url),
|
||||
).then((res) => res.arrayBuffer());
|
||||
|
||||
const inter400_ = fetch(
|
||||
new URL("og-lib/fonts/inter/400.ttf", import.meta.url),
|
||||
).then((res) => res.arrayBuffer());
|
||||
const inter500_ = fetch(
|
||||
new URL("og-lib/fonts/inter/500.ttf", import.meta.url),
|
||||
).then((res) => res.arrayBuffer());
|
||||
const inter700_ = fetch(
|
||||
new URL("og-lib/fonts/inter/700.ttf", import.meta.url),
|
||||
).then((res) => res.arrayBuffer());
|
||||
|
||||
const ibmPlexMono400_ = fetch(
|
||||
new URL("og-lib/fonts/ibm-plex-mono/400.ttf", import.meta.url),
|
||||
).then((res) => res.arrayBuffer());
|
||||
const ibmPlexMono500_ = fetch(
|
||||
new URL("og-lib/fonts/ibm-plex-mono/500.ttf", import.meta.url),
|
||||
).then((res) => res.arrayBuffer());
|
||||
const ibmPlexMono700_ = fetch(
|
||||
new URL("og-lib/fonts/ibm-plex-mono/700.ttf", import.meta.url),
|
||||
).then((res) => res.arrayBuffer());
|
||||
import { thirdwebClient } from "@/constants/client";
|
||||
import { ImageResponse } from "next/og";
|
||||
import { download } from "thirdweb/storage";
|
||||
|
||||
const OgBrandIcon: React.FC = () => (
|
||||
// biome-ignore lint/a11y/noSvgWithoutTitle: not needed
|
||||
@@ -160,27 +130,49 @@ function descriptionShortener(description: string) {
|
||||
return `${words.join(" ")} ...`;
|
||||
}
|
||||
|
||||
const IPFS_GATEWAY = process.env.API_ROUTES_CLIENT_ID
|
||||
? `https://${process.env.API_ROUTES_CLIENT_ID}.${isProd ? "ipfscdn.io/ipfs/" : "thirdwebstorage-dev.com/ipfs/"}`
|
||||
: "https://ipfs.io/ipfs/";
|
||||
export async function publishedContractOGImageTemplate(params: {
|
||||
title: string;
|
||||
description?: string;
|
||||
version: string;
|
||||
publisher: string;
|
||||
license: string[];
|
||||
publishDate: string;
|
||||
logo?: string;
|
||||
publisherAvatar?: string;
|
||||
extension?: string[];
|
||||
}) {
|
||||
const image = fetch(
|
||||
new URL("og-lib/assets/publish/background.png", import.meta.url),
|
||||
).then((res) => res.arrayBuffer());
|
||||
|
||||
function replaceAnyIpfsUrlWithGateway(url: string) {
|
||||
if (url.startsWith("ipfs://")) {
|
||||
return `${IPFS_GATEWAY}${url.slice(7)}`;
|
||||
}
|
||||
if (url.includes("/ipfs/")) {
|
||||
const [, after] = url.split("/ipfs/");
|
||||
return `${IPFS_GATEWAY}${after}`;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
const inter400_ = fetch(
|
||||
new URL("../../../../../../og-lib/fonts/inter/400.ttf", import.meta.url),
|
||||
).then((res) => res.arrayBuffer());
|
||||
const inter500_ = fetch(
|
||||
new URL("../../../../../../og-lib/fonts/inter/500.ttf", import.meta.url),
|
||||
).then((res) => res.arrayBuffer());
|
||||
const inter700_ = fetch(
|
||||
new URL("../../../../../../og-lib/fonts/inter/700.ttf", import.meta.url),
|
||||
).then((res) => res.arrayBuffer());
|
||||
|
||||
export default async function handler(req: NextRequest) {
|
||||
if (req.method !== "GET") {
|
||||
return new Response("Method not allowed", { status: 405 });
|
||||
}
|
||||
|
||||
const publishedContractData = PublishedContractOG.fromUrl(new URL(req.url));
|
||||
const ibmPlexMono400_ = fetch(
|
||||
new URL(
|
||||
"../../../../../../og-lib/fonts/ibm-plex-mono/400.ttf",
|
||||
import.meta.url,
|
||||
),
|
||||
).then((res) => res.arrayBuffer());
|
||||
const ibmPlexMono500_ = fetch(
|
||||
new URL(
|
||||
"../../../../../../og-lib/fonts/ibm-plex-mono/500.ttf",
|
||||
import.meta.url,
|
||||
),
|
||||
).then((res) => res.arrayBuffer());
|
||||
const ibmPlexMono700_ = fetch(
|
||||
new URL(
|
||||
"../../../../../../og-lib/fonts/ibm-plex-mono/700.ttf",
|
||||
import.meta.url,
|
||||
),
|
||||
).then((res) => res.arrayBuffer());
|
||||
|
||||
const [
|
||||
inter400,
|
||||
@@ -190,6 +182,8 @@ export default async function handler(req: NextRequest) {
|
||||
ibmPlexMono500,
|
||||
ibmPlexMono700,
|
||||
imageData,
|
||||
logo,
|
||||
avatar,
|
||||
] = await Promise.all([
|
||||
inter400_,
|
||||
inter500_,
|
||||
@@ -198,6 +192,18 @@ export default async function handler(req: NextRequest) {
|
||||
ibmPlexMono500_,
|
||||
ibmPlexMono700_,
|
||||
image,
|
||||
params.logo
|
||||
? download({
|
||||
uri: params.logo,
|
||||
client: thirdwebClient,
|
||||
}).then((res) => res.arrayBuffer())
|
||||
: undefined,
|
||||
params.publisherAvatar
|
||||
? download({
|
||||
uri: params.publisherAvatar,
|
||||
client: thirdwebClient,
|
||||
}).then((res) => res.arrayBuffer())
|
||||
: undefined,
|
||||
]);
|
||||
|
||||
return new ImageResponse(
|
||||
@@ -223,37 +229,30 @@ export default async function handler(req: NextRequest) {
|
||||
<div tw="flex justify-between items-start w-full">
|
||||
<div tw="flex flex-col flex-shrink">
|
||||
<div tw="flex items-center">
|
||||
{publishedContractData.logo && (
|
||||
<img
|
||||
src={replaceAnyIpfsUrlWithGateway(publishedContractData.logo)}
|
||||
tw="w-16 h-16 rounded-xl mr-4"
|
||||
alt=""
|
||||
/>
|
||||
)}
|
||||
<h1 tw="text-6xl font-bold text-white">
|
||||
{publishedContractData.name}
|
||||
</h1>
|
||||
{/* @ts-expect-error - this works fine */}
|
||||
{logo && <img src={logo} tw="w-16 h-16 rounded-xl mr-4" alt="" />}
|
||||
<h1 tw="text-6xl font-bold text-white">{params.title}</h1>
|
||||
</div>
|
||||
{publishedContractData?.description && (
|
||||
{params?.description && (
|
||||
<p tw="text-3xl font-medium text-gray-400 max-w-4xl">
|
||||
{descriptionShortener(publishedContractData.description)}
|
||||
{descriptionShortener(params.description)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div tw="flex flex-col shrink-0 items-center">
|
||||
<img
|
||||
alt=""
|
||||
tw="w-32 h-32 rounded-full"
|
||||
src={
|
||||
publishedContractData.publisherAvatar
|
||||
? replaceAnyIpfsUrlWithGateway(
|
||||
publishedContractData.publisherAvatar,
|
||||
)
|
||||
: `https://source.boringavatars.com/marble/120/${publishedContractData.publisher}?colors=264653,2a9d8f,e9c46a,f4a261,e76f51&square=true`
|
||||
}
|
||||
/>
|
||||
{avatar && (
|
||||
<img
|
||||
alt=""
|
||||
width={100}
|
||||
height={100}
|
||||
tw="w-32 h-32 rounded-full"
|
||||
// @ts-expect-error - this works fine
|
||||
src={avatar}
|
||||
/>
|
||||
)}
|
||||
|
||||
<h2 tw="text-2xl text-white font-medium max-w-full">
|
||||
{publishedContractData.publisher}
|
||||
{params.publisher}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
@@ -264,15 +263,15 @@ export default async function handler(req: NextRequest) {
|
||||
fontFamily: "IBM Plex Mono",
|
||||
}}
|
||||
>
|
||||
{publishedContractData.extension?.length ? (
|
||||
{params.extension?.length ? (
|
||||
<li
|
||||
tw="flex flex-row items-center overflow-hidden w-full"
|
||||
style={{ whiteSpace: "nowrap" }}
|
||||
>
|
||||
<PackageIcon />
|
||||
<span tw="ml-2">
|
||||
{Array.isArray(publishedContractData.extension)
|
||||
? categorizeExtensions(publishedContractData.extension).map(
|
||||
{Array.isArray(params.extension)
|
||||
? categorizeExtensions(params.extension).map(
|
||||
([ext, count]) => (
|
||||
<span key={ext} tw="flex flex-row items-center mr-3">
|
||||
{ext}
|
||||
@@ -282,27 +281,27 @@ export default async function handler(req: NextRequest) {
|
||||
</span>
|
||||
),
|
||||
)
|
||||
: publishedContractData.extension}
|
||||
: params.extension}
|
||||
</span>
|
||||
</li>
|
||||
) : null}
|
||||
{publishedContractData.license?.length ? (
|
||||
{params.license?.length ? (
|
||||
<li tw="flex flex-row items-center">
|
||||
<FileTextIcon />
|
||||
<span tw="ml-2">
|
||||
{Array.isArray(publishedContractData.license)
|
||||
? publishedContractData.license.join(", ")
|
||||
: publishedContractData.license}
|
||||
{Array.isArray(params.license)
|
||||
? params.license.join(", ")
|
||||
: params.license}
|
||||
</span>
|
||||
</li>
|
||||
) : null}
|
||||
<li tw="flex flex-row items-center">
|
||||
<VersionIcon />
|
||||
<span tw="ml-2">{publishedContractData.version}</span>
|
||||
<span tw="ml-2">{params.version}</span>
|
||||
</li>
|
||||
<li tw="flex flex-row items-center">
|
||||
<CalendarIcon />
|
||||
<span tw="ml-2">{publishedContractData.publishDate}</span>
|
||||
<span tw="ml-2">{params.publishDate}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div tw="flex flex-shrink-0">
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { shareLink } from "@/lib/shareLink";
|
||||
import Link from "next/link";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
|
||||
export function DeployActions(props: {
|
||||
publisher: string;
|
||||
contract_id: string;
|
||||
version?: string;
|
||||
dispayName: string;
|
||||
}) {
|
||||
const searchparams = useSearchParams();
|
||||
|
||||
const stringifiedSearchParams = searchparams?.toString();
|
||||
|
||||
return (
|
||||
<div className="flex gap-3">
|
||||
<Button asChild variant="outline">
|
||||
<Link
|
||||
href={`/${props.publisher}/${props.contract_id}${props.version ? `/${props.version}` : ""}${stringifiedSearchParams ? `?${stringifiedSearchParams}` : ""}`}
|
||||
target="_blank"
|
||||
>
|
||||
Contract
|
||||
</Link>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
shareLink({
|
||||
title: `Deploy ${props.dispayName}`,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Share
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { shareLink } from "@/lib/shareLink";
|
||||
import { ChevronsRightIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
|
||||
export function PublishedActions(props: {
|
||||
publisher: string;
|
||||
contract_id: string;
|
||||
version?: string;
|
||||
dispayName: string;
|
||||
}) {
|
||||
const searchparams = useSearchParams();
|
||||
const stringifiedSearchParams = searchparams?.toString();
|
||||
|
||||
return (
|
||||
<div className="flex gap-3">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
shareLink({
|
||||
title: `Deploy ${props.dispayName}`,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Share
|
||||
</Button>
|
||||
<Button asChild variant="primary" className="gap-2">
|
||||
<Link
|
||||
href={`/${props.publisher}/${props.contract_id}${props.version ? `/${props.version}` : ""}/deploy${stringifiedSearchParams ? `?${stringifiedSearchParams}` : ""}`}
|
||||
>
|
||||
Deploy Now
|
||||
<ChevronsRightIcon className="size-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
"use client";
|
||||
import type { PropsWithChildren } from "react";
|
||||
import type { PublishedContractWithVersion } from "../../../../components/contract-components/fetch-contracts-with-versions";
|
||||
import { ModuleList } from "../[publisher]/[contract_id]/components/module-list.client";
|
||||
import { DeployContractInfo } from "./contract-info";
|
||||
import { DeployContractVersionSelector } from "./version-selector";
|
||||
|
||||
type DeployContractHeaderProps = {
|
||||
publisher: string;
|
||||
contract_id: string;
|
||||
version?: string;
|
||||
allVersions: PublishedContractWithVersion[];
|
||||
activeVersion: PublishedContractWithVersion;
|
||||
};
|
||||
|
||||
export function DeployContractHeader(
|
||||
props: PropsWithChildren<DeployContractHeaderProps>,
|
||||
) {
|
||||
return (
|
||||
<div className="py-2 flex flex-col gap-4">
|
||||
<div className="flex flex-col md:flex-row justify-between gap-6 md:items-center">
|
||||
<DeployContractInfo
|
||||
name={props.activeVersion.name}
|
||||
displayName={props.activeVersion.displayName}
|
||||
description={props.activeVersion.description}
|
||||
logo={props.activeVersion.logo}
|
||||
/>
|
||||
<div className="flex flex-col-reverse md:flex-row gap-3">
|
||||
{
|
||||
<DeployContractVersionSelector
|
||||
availableVersions={
|
||||
props.allVersions
|
||||
?.map((v) => v.version)
|
||||
.filter((v) => v !== undefined) || []
|
||||
}
|
||||
version={props.activeVersion.version || "latest"}
|
||||
/>
|
||||
}
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
<ModuleList />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { thirdwebClient } from "@/constants/client";
|
||||
import { resolveScheme } from "thirdweb/storage";
|
||||
|
||||
export function DeployContractInfo(props: {
|
||||
name: string;
|
||||
displayName?: string;
|
||||
description?: string;
|
||||
logo?: string;
|
||||
}) {
|
||||
const contractNameDisplay = props.displayName || props.name;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex gap-4 items-center flex-1">
|
||||
{props.logo && (
|
||||
<div className="rounded-xl p-2 border border-border shrink-0 items-center justify-center hidden md:flex">
|
||||
{/*eslint-disable-next-line @next/next/no-img-element*/}
|
||||
<img
|
||||
className="size-12"
|
||||
alt={props.name}
|
||||
src={resolveScheme({
|
||||
client: thirdwebClient,
|
||||
uri: props.logo,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<h1 className="text-2xl md:text-3xl font-bold tracking-tight">
|
||||
{contractNameDisplay}
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-sm">{props.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { thirdwebClient } from "@/constants/client";
|
||||
import { isAddress } from "thirdweb";
|
||||
import { fetchDeployMetadata } from "thirdweb/contract";
|
||||
import { resolveAddress } from "thirdweb/extensions/ens";
|
||||
import {
|
||||
fetchPublishedContractVersion,
|
||||
fetchPublishedContractVersions,
|
||||
} from "../../../../components/contract-components/fetch-contracts-with-versions";
|
||||
import { DeployActions } from "./contract-actions-deploy.client";
|
||||
import { DeployContractHeader } from "./contract-header";
|
||||
import { DeployFormForUri } from "./uri-based-deploy";
|
||||
|
||||
type PublishBasedDeployProps = {
|
||||
publisher: string;
|
||||
contract_id: string;
|
||||
version?: string;
|
||||
modules?: Array<{ publisher: string; moduleId: string; version?: string }>;
|
||||
};
|
||||
|
||||
function mapThirdwebPublisher(publisher: string) {
|
||||
if (publisher === "thirdweb.eth") {
|
||||
return "deployer.thirdweb.eth";
|
||||
}
|
||||
return publisher;
|
||||
}
|
||||
|
||||
export async function DeployFormForPublishInfo(props: PublishBasedDeployProps) {
|
||||
// resolve ENS if required
|
||||
const publisherAddress = isAddress(props.publisher)
|
||||
? props.publisher
|
||||
: await resolveAddress({
|
||||
client: thirdwebClient,
|
||||
name: mapThirdwebPublisher(props.publisher),
|
||||
});
|
||||
|
||||
// get all the published versions of the contract
|
||||
const [publishedContractVersions, ...modules] = await Promise.all([
|
||||
fetchPublishedContractVersions(publisherAddress, props.contract_id),
|
||||
...(props.modules || []).map((m) =>
|
||||
fetchPublishedContractVersion(m.publisher, m.moduleId, m.version),
|
||||
),
|
||||
]);
|
||||
|
||||
// determine the "active" version of the contract based on the version that is passed
|
||||
const publishedContract =
|
||||
publishedContractVersions.find((v) => v.version === props.version) ||
|
||||
publishedContractVersions[0];
|
||||
|
||||
const moduleUris = modules
|
||||
.filter((m) => m !== null)
|
||||
.map((m) => m.publishMetadataUri);
|
||||
const [contractMetadata, ...fetchedModules] = await Promise.all([
|
||||
fetchDeployMetadata({
|
||||
client: thirdwebClient,
|
||||
// force `ipfs://` prefix
|
||||
uri: publishedContract.publishMetadataUri.startsWith("ipfs://")
|
||||
? publishedContract.publishMetadataUri
|
||||
: `ipfs://${publishedContract.publishMetadataUri}`,
|
||||
}).catch(() => null),
|
||||
...(moduleUris || []).map((uri) =>
|
||||
fetchDeployMetadata({
|
||||
client: thirdwebClient,
|
||||
// force `ipfs://` prefix
|
||||
uri: uri.startsWith("ipfs://") ? uri : `ipfs://${uri}`,
|
||||
}).catch(() => null),
|
||||
),
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeployContractHeader
|
||||
{...props}
|
||||
allVersions={publishedContractVersions}
|
||||
activeVersion={publishedContract}
|
||||
>
|
||||
<DeployActions
|
||||
{...props}
|
||||
dispayName={publishedContract.displayName || publishedContract.name}
|
||||
/>
|
||||
</DeployContractHeader>
|
||||
<Separator />
|
||||
<DeployFormForUri
|
||||
contractMetadata={contractMetadata}
|
||||
modules={fetchedModules.filter((m) => m !== null)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ChakraProviderSetup } from "@/components/ChakraProviderSetup";
|
||||
import { CustomContractForm } from "components/contract-components/contract-deploy-form/custom-contract";
|
||||
import type { FetchDeployMetadataResult } from "thirdweb/contract";
|
||||
|
||||
type DeployFormForUriProps = {
|
||||
contractMetadata: FetchDeployMetadataResult | null;
|
||||
modules: FetchDeployMetadataResult[] | null;
|
||||
};
|
||||
|
||||
export async function DeployFormForUri(props: DeployFormForUriProps) {
|
||||
const { contractMetadata, modules } = props;
|
||||
|
||||
if (!contractMetadata) {
|
||||
return <div>Could not fetch metadata</div>;
|
||||
}
|
||||
|
||||
// TODO: remove the `ChakraProviderSetup` wrapper once the form is updated to no longer use chrakra
|
||||
return (
|
||||
<ChakraProviderSetup>
|
||||
<CustomContractForm
|
||||
metadata={contractMetadata}
|
||||
modules={modules?.filter((m) => m !== null)}
|
||||
/>
|
||||
</ChakraProviderSetup>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { useDashboardRouter } from "@/lib/DashboardRouter";
|
||||
import { usePathname, useSearchParams } from "next/navigation";
|
||||
import { useMemo } from "react";
|
||||
|
||||
type DeployContractVersionSelectorProps = {
|
||||
availableVersions: string[];
|
||||
version: string;
|
||||
};
|
||||
|
||||
export function DeployContractVersionSelector({
|
||||
availableVersions,
|
||||
version,
|
||||
}: DeployContractVersionSelectorProps) {
|
||||
const router = useDashboardRouter();
|
||||
const pathname = usePathname() || "";
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const { isDeploy, rootPathName } = useMemo(() => {
|
||||
return {
|
||||
isDeploy: pathname.endsWith("/deploy"),
|
||||
rootPathName: pathname
|
||||
.replace(/\/deploy$/, "")
|
||||
.replace(`/${version}`, ""),
|
||||
};
|
||||
}, [pathname, version]);
|
||||
|
||||
return (
|
||||
<Select
|
||||
value={version}
|
||||
onValueChange={(val) => {
|
||||
if (availableVersions.includes(val)) {
|
||||
const stringifiedSearchParams = searchParams?.toString();
|
||||
const isLatestVersion = val === availableVersions[0];
|
||||
let pathName = isLatestVersion
|
||||
? rootPathName
|
||||
: `${rootPathName}/${val}`;
|
||||
|
||||
if (isDeploy) {
|
||||
// append the /deploy to the path
|
||||
pathName = `${pathName}/deploy`;
|
||||
}
|
||||
|
||||
// if any search params, add them to the path
|
||||
if (stringifiedSearchParams) {
|
||||
pathName = `${pathName}?${stringifiedSearchParams}`;
|
||||
}
|
||||
|
||||
router.push(pathName);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="min-w-[180px] bg-transparent hover:bg-accent">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{availableVersions.map((v, idx) => {
|
||||
return (
|
||||
<SelectItem value={v || "latest"} key={v}>
|
||||
{v}
|
||||
{idx === 0 && (
|
||||
<span className="text-muted-foreground"> (latest) </span>
|
||||
)}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { thirdwebClient } from "@/constants/client";
|
||||
import { redirect } from "next/navigation";
|
||||
import { fetchDeployMetadata } from "thirdweb/contract";
|
||||
|
||||
export default async function Page(props: {
|
||||
searchParams: {
|
||||
uri?: string;
|
||||
};
|
||||
}) {
|
||||
if (!props.searchParams?.uri) {
|
||||
// redirect back out if we do not have a uri
|
||||
return redirect("/dashboard");
|
||||
}
|
||||
|
||||
const contractMetadata = await fetchDeployMetadata({
|
||||
client: thirdwebClient,
|
||||
// force `ipfs://` prefix
|
||||
uri: props.searchParams.uri.startsWith("ipfs://")
|
||||
? props.searchParams.uri
|
||||
: `ipfs://${props.searchParams.uri}`,
|
||||
}).catch(() => null);
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<pre>{JSON.stringify(contractMetadata, null, 2)}</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
type Module = {
|
||||
publisher: string;
|
||||
moduleId: string;
|
||||
version?: string;
|
||||
};
|
||||
|
||||
function assertModule(val: unknown): asserts val is Module {
|
||||
if (
|
||||
typeof val !== "object" ||
|
||||
val === null ||
|
||||
typeof (val as Module).publisher !== "string" ||
|
||||
typeof (val as Module).moduleId !== "string" ||
|
||||
((val as Module).version !== undefined &&
|
||||
typeof (val as Module).version !== "string")
|
||||
) {
|
||||
throw new Error("Invalid module");
|
||||
}
|
||||
}
|
||||
|
||||
export function moduleFromBase64(base64: string) {
|
||||
try {
|
||||
const mod = JSON.parse(atob(base64));
|
||||
assertModule(mod);
|
||||
return {
|
||||
...mod,
|
||||
version: mod.version || "latest",
|
||||
displayName: mod.moduleId.split("ERC")[0],
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function moduleToBase64(mod: Module) {
|
||||
return btoa(JSON.stringify(mod));
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
export const PRODUCTS = [
|
||||
"connect",
|
||||
"engine",
|
||||
"contracts",
|
||||
"account",
|
||||
"other",
|
||||
] as const;
|
||||
export type Product = (typeof PRODUCTS)[number];
|
||||
|
||||
export function isProduct(value: string): value is Product {
|
||||
return PRODUCTS.includes(value as Product);
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
import { useTheme } from "next-themes";
|
||||
import { useId, useMemo, useState } from "react";
|
||||
import {
|
||||
Bar,
|
||||
BarChart as RechartsBarChart,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts";
|
||||
import { StackToolTip } from "./stack-tooltip";
|
||||
|
||||
type GenericDataType = Record<string, string | number>;
|
||||
|
||||
type IndexType = "date";
|
||||
|
||||
interface AutoBarChartProps<
|
||||
TData extends GenericDataType,
|
||||
TIndexKey extends keyof TData,
|
||||
> {
|
||||
data: TData[];
|
||||
index: {
|
||||
id: TIndexKey;
|
||||
label?: string;
|
||||
type?: IndexType;
|
||||
format?: (index: TData[TIndexKey]) => string;
|
||||
};
|
||||
showXAxis?: boolean;
|
||||
showYAxis?: boolean;
|
||||
stacked?: boolean;
|
||||
}
|
||||
|
||||
const BAR_COLORS_LIGHT = [
|
||||
"#649CDD",
|
||||
"#92BBE8",
|
||||
"#407DCC",
|
||||
"#94BD5D",
|
||||
"#729544",
|
||||
"#A48AC1",
|
||||
"#C5B5D7",
|
||||
"#8460AB",
|
||||
"#E1918E",
|
||||
"#D65E59",
|
||||
"#F3D570",
|
||||
"#F7E5A2",
|
||||
"#EFC64A",
|
||||
"#E7AB78",
|
||||
"#F0CDAD",
|
||||
"#DF8A47",
|
||||
"#A6D7D8",
|
||||
"#CEE9E9",
|
||||
];
|
||||
|
||||
const BAR_COLORS_DARK = [
|
||||
"#3682DA",
|
||||
"#6AADF5",
|
||||
"#1769D3",
|
||||
"#84BF35",
|
||||
"#629421",
|
||||
"#8751C3",
|
||||
"#AA85D3",
|
||||
"#6E38A9",
|
||||
"#DE6F6B",
|
||||
"#D43932",
|
||||
"#FACD38",
|
||||
"#FFE689",
|
||||
"#F0BF2A",
|
||||
"#E4934E",
|
||||
"#F4C294",
|
||||
"#DE7929",
|
||||
"#7CDEE0",
|
||||
"#C8FFFF",
|
||||
];
|
||||
|
||||
export const AutoBarChart = <
|
||||
TData extends GenericDataType,
|
||||
TIndexKey extends keyof TData,
|
||||
>({
|
||||
data,
|
||||
index,
|
||||
showXAxis,
|
||||
showYAxis,
|
||||
stacked,
|
||||
}: AutoBarChartProps<TData, TIndexKey>) => {
|
||||
const { theme } = useTheme();
|
||||
const [hoverKey, setHoverKey] = useState("");
|
||||
const id = useId();
|
||||
|
||||
const barColors = useMemo(() => {
|
||||
if (theme === "light") {
|
||||
return BAR_COLORS_LIGHT;
|
||||
}
|
||||
|
||||
return BAR_COLORS_DARK;
|
||||
}, [theme]);
|
||||
|
||||
const categories = useMemo(() => {
|
||||
const autoKeys: string[] = [];
|
||||
// biome-ignore lint/complexity/noForEach: FIXME
|
||||
data.forEach((item) => {
|
||||
for (const key of Object.keys(item)) {
|
||||
if (key === index.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!autoKeys.includes(key)) {
|
||||
autoKeys.push(key);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return autoKeys.map((key, idx) => ({
|
||||
id: key,
|
||||
label: key,
|
||||
color: barColors[idx % barColors.length],
|
||||
}));
|
||||
}, [data, index.id, barColors]);
|
||||
|
||||
const sortedData = useMemo(() => {
|
||||
return [...data].sort(
|
||||
(a, b) => (a[index.id] as number) - (b[index.id] as number),
|
||||
);
|
||||
}, [data, index]);
|
||||
|
||||
if (!data.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<RechartsBarChart data={data}>
|
||||
<defs>
|
||||
{categories.map((cat) => (
|
||||
<linearGradient
|
||||
key={`${cat.id as string}`}
|
||||
id={`bar_color_${id}_${cat.id as string}`}
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="0"
|
||||
y2="100%"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop
|
||||
offset="0"
|
||||
stopColor={cat.color || "#3385FF"}
|
||||
stopOpacity={1}
|
||||
/>
|
||||
<stop
|
||||
offset="1"
|
||||
stopColor={cat.color || "#3385FF"}
|
||||
stopOpacity={0.75}
|
||||
/>
|
||||
</linearGradient>
|
||||
))}
|
||||
</defs>
|
||||
|
||||
{categories.map((cat) => (
|
||||
<Bar
|
||||
key={`${cat.id as string}`}
|
||||
dataKey={cat.id as string}
|
||||
stackId={stacked ? "a" : (cat.id as string)}
|
||||
stroke={cat.color || "#3385FF"}
|
||||
fill={`url(#bar_color_${id}_${cat.id as string})`}
|
||||
opacity={cat.id === hoverKey || !hoverKey ? 1 : 0.5}
|
||||
strokeWidth={0}
|
||||
onMouseOver={(item) => {
|
||||
setHoverKey(item.tooltipPayload[0].dataKey);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setHoverKey("");
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<Tooltip
|
||||
wrapperStyle={{ outline: "none" }}
|
||||
content={({ active, payload }) => {
|
||||
if (!active || !payload) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { time, ...values } = payload[0].payload;
|
||||
return (
|
||||
<StackToolTip time={time} values={values} hoverKey={hoverKey} />
|
||||
);
|
||||
}}
|
||||
cursor={
|
||||
!hoverKey || categories.length <= 1
|
||||
? {
|
||||
stroke: "#EAEAEA",
|
||||
fill: "#EAEAEA",
|
||||
opacity: 0.3,
|
||||
strokeDasharray: 2,
|
||||
strokeWidth: 0,
|
||||
}
|
||||
: false
|
||||
}
|
||||
/>
|
||||
|
||||
<XAxis
|
||||
hide={!showXAxis}
|
||||
dataKey={index.id as string}
|
||||
tickFormatter={(payload) =>
|
||||
index.format
|
||||
? index.format(payload)
|
||||
: index.type === "date"
|
||||
? new Date(payload).toLocaleDateString(undefined, {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "2-digit",
|
||||
})
|
||||
: payload
|
||||
}
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
fontFamily: "var(--chakra-fonts-body)",
|
||||
}}
|
||||
stroke="var(--chakra-colors-paragraph)"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
interval="preserveStartEnd"
|
||||
minTickGap={5}
|
||||
domain={["dataMin - 86400000", "dataMax + 86400000"]}
|
||||
type="number"
|
||||
tick={{ transform: "translate(0, 6)" }}
|
||||
ticks={[
|
||||
sortedData[0][index.id],
|
||||
sortedData[Math.floor(sortedData.length / 2)][index.id],
|
||||
sortedData[sortedData.length - 1][index.id],
|
||||
]}
|
||||
/>
|
||||
|
||||
<YAxis
|
||||
hide={!showYAxis}
|
||||
width={60}
|
||||
tickFormatter={(payload) => {
|
||||
return payload.toLocaleString();
|
||||
}}
|
||||
className="text-xs font-sans"
|
||||
type="number"
|
||||
stroke="hsl(var(--muted-foreground))"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
/>
|
||||
</RechartsBarChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
};
|
||||
@@ -1,43 +0,0 @@
|
||||
import { AspectRatio } from "@/components/ui/aspect-ratio";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Suspense } from "react";
|
||||
import type { ComponentWithChildren } from "types/component-with-children";
|
||||
import { AreaChartLoadingState } from "./area-chart";
|
||||
|
||||
type SupportedChartType = "area";
|
||||
|
||||
interface ChartContainerProps {
|
||||
chartTitle?: JSX.Element;
|
||||
chartType?: SupportedChartType;
|
||||
ratio: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const ChartContainer: ComponentWithChildren<ChartContainerProps> = ({
|
||||
chartType = "area",
|
||||
chartTitle,
|
||||
ratio,
|
||||
children,
|
||||
className,
|
||||
}) => {
|
||||
return (
|
||||
<div className={cn("flex flex-col", className)}>
|
||||
{chartTitle}
|
||||
<AspectRatio ratio={ratio}>
|
||||
{/* suspense to handle loading state */}
|
||||
<Suspense fallback={<ChartLoadingState chartType={chartType} />}>
|
||||
{children}
|
||||
</Suspense>
|
||||
</AspectRatio>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ChartLoadingState: React.FC<Pick<ChartContainerProps, "chartType">> = ({
|
||||
chartType,
|
||||
}) => {
|
||||
if (chartType === "area") {
|
||||
return <AreaChartLoadingState />;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -1,51 +0,0 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type CustomToolTipProps = {
|
||||
time: string;
|
||||
hoverKey: string;
|
||||
values: Record<string, number>;
|
||||
};
|
||||
|
||||
const formattingOptions: Intl.DateTimeFormatOptions = {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
};
|
||||
|
||||
export const StackToolTip: React.FC<CustomToolTipProps> = ({
|
||||
time,
|
||||
values,
|
||||
hoverKey,
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex flex-col p-3 gap-2 border-none outline-none rounded-lg backdrop-blur-lg bg-[rgba(255,255,255,0.2)] dark:bg-[rgba(0,0,0,0.2)] ">
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<label className="text-xs font-semibold text-foreground">Date</label>
|
||||
<p className="text-xs">
|
||||
{new Date(time).toLocaleDateString(undefined, formattingOptions)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-0.5">
|
||||
{Object.entries(values)
|
||||
.reverse()
|
||||
.map(([key, value]) => (
|
||||
<p
|
||||
className={cn(
|
||||
"text-xs",
|
||||
hoverKey && key !== hoverKey ? "opacity-70" : "opacity-100",
|
||||
)}
|
||||
key={key}
|
||||
>
|
||||
{hoverKey && key !== hoverKey ? (
|
||||
`${key}: `
|
||||
) : (
|
||||
<strong>{key}: </strong>
|
||||
)}
|
||||
{value.toLocaleString()}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+1
-6
@@ -3,24 +3,21 @@ import { FormControl, Input } from "@chakra-ui/react";
|
||||
import { FileInput } from "components/shared/FileInput";
|
||||
import { useImageFileOrUrl } from "hooks/useImageFileOrUrl";
|
||||
import { FormErrorMessage, FormLabel } from "tw-components";
|
||||
import type { useContractPublishMetadataFromURI } from "../hooks";
|
||||
import { Fieldset } from "./common";
|
||||
import type { CustomContractDeploymentForm } from "./custom-contract";
|
||||
|
||||
interface ContractMetadataFieldsetProps {
|
||||
form: CustomContractDeploymentForm;
|
||||
metadata: ReturnType<typeof useContractPublishMetadataFromURI>;
|
||||
}
|
||||
|
||||
export const ContractMetadataFieldset: React.FC<
|
||||
ContractMetadataFieldsetProps
|
||||
> = ({ form, metadata }) => {
|
||||
> = ({ form }) => {
|
||||
return (
|
||||
<Fieldset legend="Contract Metadata">
|
||||
<div className="flex flex-col md:grid gap-6 md:grid-cols-[270px_1fr]">
|
||||
<div>
|
||||
<FormControl
|
||||
isDisabled={!metadata.isSuccess}
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
isInvalid={
|
||||
@@ -51,7 +48,6 @@ export const ContractMetadataFieldset: React.FC<
|
||||
<div className="flex flex-col gap-6">
|
||||
<FormControl
|
||||
isRequired
|
||||
isDisabled={!metadata.isSuccess}
|
||||
isInvalid={
|
||||
!!form.getFieldState("contractMetadata.name", form.formState)
|
||||
.error
|
||||
@@ -91,7 +87,6 @@ export const ContractMetadataFieldset: React.FC<
|
||||
</FormControl>
|
||||
|
||||
<FormControl
|
||||
isDisabled={!metadata.isSuccess}
|
||||
className="grow flex flex-col"
|
||||
isInvalid={
|
||||
!!form.getFieldState(
|
||||
|
||||
+656
-723
File diff suppressed because it is too large
Load Diff
-8
@@ -1,8 +0,0 @@
|
||||
import { thirdwebClient } from "@/constants/client";
|
||||
import { upload } from "thirdweb/storage";
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: FIXME
|
||||
export async function uploadContractMetadata(metadata: any) {
|
||||
const uri = await upload({ client: thirdwebClient, files: [metadata] });
|
||||
return uri;
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import { CustomSDKContext } from "contexts/custom-sdk-context";
|
||||
import { useSupportedChainsRecord } from "hooks/chains/configureChains";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useActiveAccount, useActiveWalletChain } from "thirdweb/react";
|
||||
import type { ContractId } from "../types";
|
||||
import CustomContractForm from "./custom-contract";
|
||||
|
||||
interface ContractDeployFormProps {
|
||||
contractId: ContractId;
|
||||
version?: string;
|
||||
chainId?: number;
|
||||
onSuccessCallback?: (contractAddress: string) => void;
|
||||
isImplementationDeploy?: true;
|
||||
}
|
||||
|
||||
export const ContractDeployForm: React.FC<ContractDeployFormProps> = ({
|
||||
contractId,
|
||||
version,
|
||||
chainId: chainIdProp,
|
||||
onSuccessCallback,
|
||||
isImplementationDeploy,
|
||||
}) => {
|
||||
const walletAddress = useActiveAccount()?.address;
|
||||
const connectedChainId = useActiveWalletChain()?.id;
|
||||
const configuredNetworksRecord = useSupportedChainsRecord();
|
||||
const [selectedChain, setSelectedChain] = useState<number | undefined>(
|
||||
chainIdProp
|
||||
? chainIdProp
|
||||
: connectedChainId && connectedChainId in configuredNetworksRecord
|
||||
? connectedChainId
|
||||
: undefined,
|
||||
);
|
||||
|
||||
// FIXME: all of this logic needs to be reworked
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
useEffect(() => {
|
||||
// If the user has not selected a chain, and the connected chain is configured, select it
|
||||
if (
|
||||
!selectedChain &&
|
||||
connectedChainId &&
|
||||
connectedChainId in configuredNetworksRecord
|
||||
) {
|
||||
setSelectedChain(connectedChainId);
|
||||
}
|
||||
}, [connectedChainId, selectedChain, configuredNetworksRecord]);
|
||||
|
||||
if (!contractId) {
|
||||
return null;
|
||||
}
|
||||
if (contractId === "custom") {
|
||||
return <div>Invalid attempt to deploy "custom" contract.</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<CustomSDKContext desiredChainId={selectedChain}>
|
||||
<CustomContractForm
|
||||
version={version}
|
||||
ipfsHash={contractId}
|
||||
selectedChain={selectedChain}
|
||||
onChainSelect={setSelectedChain}
|
||||
isImplementationDeploy={isImplementationDeploy}
|
||||
onSuccessCallback={onSuccessCallback}
|
||||
// has to be passed in because if we use `useAddress()` inside it will start out as undefined -> not be overwritten -> not trigger a re-render
|
||||
// we can clean this up once the we can can call functions without constructing a whole new SDK each time
|
||||
walletAddress={walletAddress}
|
||||
/>
|
||||
</CustomSDKContext>
|
||||
);
|
||||
};
|
||||
+32
-64
@@ -1,72 +1,33 @@
|
||||
import { FormControl } from "@chakra-ui/react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { SolidityInput } from "contract-ui/components/solidity-inputs";
|
||||
import { getModuleInstalledParams } from "contract-ui/tabs/manage/components/getModuleInstalledParams";
|
||||
import invariant from "tiny-invariant";
|
||||
import { useMemo } from "react";
|
||||
import type { FetchDeployMetadataResult } from "thirdweb/contract";
|
||||
import { FormErrorMessage, FormLabel } from "tw-components";
|
||||
import type { CustomContractDeploymentForm } from "./custom-contract";
|
||||
import { PrimarySaleFieldset } from "./primary-sale-fieldset";
|
||||
import { RoyaltyFieldset } from "./royalty-fieldset";
|
||||
|
||||
type ModuleMeta = {
|
||||
moduleName: string;
|
||||
moduleVersion: string;
|
||||
publisherAddress: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the install params for all given modules
|
||||
*/
|
||||
export function useModularContractsDefaultModulesInstallParams(props: {
|
||||
defaultModules?: ModuleMeta[];
|
||||
isQueryEnabled: boolean;
|
||||
}) {
|
||||
const { defaultModules, isQueryEnabled } = props;
|
||||
return useQuery({
|
||||
queryKey: [
|
||||
"useModularContractsDefaultModulesInstallParams",
|
||||
defaultModules,
|
||||
],
|
||||
queryFn: async () => {
|
||||
invariant(defaultModules, "defaultModules must be defined");
|
||||
return Promise.all(defaultModules.map(getModuleInstalledParams));
|
||||
},
|
||||
enabled: !!(isQueryEnabled && defaultModules),
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
export function getModuleInstallParams(mod: FetchDeployMetadataResult) {
|
||||
return (
|
||||
mod.abi
|
||||
.filter((a) => a.type === "function")
|
||||
.find((f) => f.name === "encodeBytesOnInstall")?.inputs || []
|
||||
);
|
||||
}
|
||||
|
||||
export type UseModularContractsDefaultModulesInstallParams = ReturnType<
|
||||
typeof useModularContractsDefaultModulesInstallParams
|
||||
>;
|
||||
|
||||
type Modules = NonNullable<
|
||||
UseModularContractsDefaultModulesInstallParams["data"]
|
||||
>;
|
||||
|
||||
type ModuleWithIndex = Modules[number] & { moduleIndex: number };
|
||||
|
||||
export function ModularContractDefaultModulesFieldset(props: {
|
||||
modules: Modules;
|
||||
modules: FetchDeployMetadataResult[];
|
||||
form: CustomContractDeploymentForm;
|
||||
isTWPublisher: boolean;
|
||||
}) {
|
||||
// save the index of the module before filtering out
|
||||
const modulesWithIndex: ModuleWithIndex[] = props.modules
|
||||
.map((v, i) => ({
|
||||
...v,
|
||||
moduleIndex: i,
|
||||
}))
|
||||
.filter((v) => v.params.length > 0);
|
||||
|
||||
return (
|
||||
<div className="py-4">
|
||||
<div className="flex flex-col gap-4">
|
||||
{modulesWithIndex.map((ext) => {
|
||||
{props.modules.map((mod) => {
|
||||
return (
|
||||
<RenderModule
|
||||
key={ext.moduleName}
|
||||
module={ext}
|
||||
key={mod.name}
|
||||
module={mod}
|
||||
isTWPublisher={props.isTWPublisher}
|
||||
form={props.form}
|
||||
/>
|
||||
@@ -78,15 +39,22 @@ export function ModularContractDefaultModulesFieldset(props: {
|
||||
}
|
||||
|
||||
function RenderModule(props: {
|
||||
module: ModuleWithIndex;
|
||||
module: FetchDeployMetadataResult;
|
||||
form: CustomContractDeploymentForm;
|
||||
isTWPublisher: boolean;
|
||||
}) {
|
||||
const { module, form } = props;
|
||||
|
||||
const params = useMemo(() => getModuleInstallParams(module), [module]);
|
||||
|
||||
if (params.length === 0) {
|
||||
// if a module has no params, don't render anything
|
||||
return null;
|
||||
}
|
||||
|
||||
// only consider mapping if published by thirdweb, else show the generic form
|
||||
if (props.isTWPublisher) {
|
||||
const paramNames = module.params.map((param) => param.name);
|
||||
const paramNames = params.map((p) => p.name).filter((n) => n !== undefined);
|
||||
|
||||
if (showRoyaltyFieldset(paramNames)) {
|
||||
return (
|
||||
@@ -104,12 +72,12 @@ function RenderModule(props: {
|
||||
return (
|
||||
<div>
|
||||
<h3 className="text-lg mb-2 text-muted-foreground font-medium">
|
||||
{module.moduleName}
|
||||
{module.name}
|
||||
</h3>
|
||||
<div className="flex flex-col gap-3">
|
||||
{module.params.map((param) => {
|
||||
{params.map((param) => {
|
||||
const formFieldKey =
|
||||
`modularContractDefaultModulesInstallParams.${module.moduleIndex}.${param.name}` as const;
|
||||
`moduleData.${module.name}.${param.name}` as const;
|
||||
|
||||
return (
|
||||
<FormControl
|
||||
@@ -122,6 +90,7 @@ function RenderModule(props: {
|
||||
<FormLabel> {param.name}</FormLabel>
|
||||
<SolidityInput
|
||||
solidityType={param.type}
|
||||
// @ts-expect-error - old types, need to update
|
||||
solidityComponents={param.components}
|
||||
variant="filled"
|
||||
{...form.register(formFieldKey)}
|
||||
@@ -141,14 +110,14 @@ function RenderModule(props: {
|
||||
}
|
||||
|
||||
function RenderPrimarySaleFieldset(prosp: {
|
||||
module: ModuleWithIndex;
|
||||
module: FetchDeployMetadataResult;
|
||||
form: CustomContractDeploymentForm;
|
||||
isTWPublisher: boolean;
|
||||
}) {
|
||||
const { module, form } = prosp;
|
||||
|
||||
const primarySaleRecipientPath =
|
||||
`modularContractDefaultModulesInstallParams.${module.moduleIndex}.primarySaleRecipient` as const;
|
||||
`moduleData.${module.name}.primarySaleRecipient` as const;
|
||||
|
||||
return (
|
||||
<PrimarySaleFieldset
|
||||
@@ -165,20 +134,19 @@ function RenderPrimarySaleFieldset(prosp: {
|
||||
}
|
||||
|
||||
function RenderRoyaltyFieldset(props: {
|
||||
module: ModuleWithIndex;
|
||||
module: FetchDeployMetadataResult;
|
||||
form: CustomContractDeploymentForm;
|
||||
isTWPublisher: boolean;
|
||||
}) {
|
||||
const { module: ext, form } = props;
|
||||
const { module, form } = props;
|
||||
|
||||
const royaltyRecipientPath =
|
||||
`modularContractDefaultModulesInstallParams.${ext.moduleIndex}.royaltyRecipient` as const;
|
||||
`moduleData.${module.name}.royaltyRecipient` as const;
|
||||
|
||||
const royaltyBpsPath =
|
||||
`modularContractDefaultModulesInstallParams.${ext.moduleIndex}.royaltyBps` as const;
|
||||
const royaltyBpsPath = `moduleData.${module.name}.royaltyBps` as const;
|
||||
|
||||
const transferValidatorPath =
|
||||
`modularContractDefaultModulesInstallParams.${ext.moduleIndex}.transferValidator` as const;
|
||||
`moduleData.${module.name}.transferValidator` as const;
|
||||
|
||||
return (
|
||||
<RoyaltyFieldset
|
||||
|
||||
-21
@@ -1,9 +1,6 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ToolTipLabel } from "@/components/ui/tooltip";
|
||||
import { Flex, FormControl, InputGroup } from "@chakra-ui/react";
|
||||
import { SolidityInput } from "contract-ui/components/solidity-inputs";
|
||||
import { FormErrorMessage, FormHelperText, FormLabel } from "tw-components";
|
||||
import { useDefaultForwarders } from "../hooks";
|
||||
import { Fieldset } from "./common";
|
||||
import type { CustomContractDeploymentForm } from "./custom-contract";
|
||||
|
||||
@@ -14,8 +11,6 @@ interface TrustedForwardersFieldsetProps {
|
||||
export const TrustedForwardersFieldset: React.FC<
|
||||
TrustedForwardersFieldsetProps
|
||||
> = ({ form }) => {
|
||||
const defaultForwarders = useDefaultForwarders();
|
||||
|
||||
return (
|
||||
<Fieldset legend="Gasless">
|
||||
<FormControl
|
||||
@@ -58,22 +53,6 @@ export const TrustedForwardersFieldset: React.FC<
|
||||
</Flex>
|
||||
</InputGroup>
|
||||
|
||||
<ToolTipLabel label="Click to apply default trusted forwarders">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="mt-2"
|
||||
onClick={() =>
|
||||
form.setValue(
|
||||
"deployParams._trustedForwarders",
|
||||
JSON.stringify(defaultForwarders.data),
|
||||
)
|
||||
}
|
||||
>
|
||||
Get Default
|
||||
</Button>
|
||||
</ToolTipLabel>
|
||||
|
||||
<FormErrorMessage>
|
||||
{
|
||||
form.getFieldState(
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import { thirdwebClient } from "@/constants/client";
|
||||
import type { ProfileMetadata } from "constants/schemas";
|
||||
import { type ThirdwebContract, getContract, isAddress } from "thirdweb";
|
||||
import { polygon } from "thirdweb/chains";
|
||||
import { fetchDeployMetadata } from "thirdweb/contract";
|
||||
import { resolveAddress } from "thirdweb/extensions/ens";
|
||||
import {
|
||||
getPublishedContractVersions,
|
||||
getPublisherProfileUri,
|
||||
} from "thirdweb/extensions/thirdweb";
|
||||
import { download } from "thirdweb/storage";
|
||||
|
||||
function mapThirdwebPublisher(publisher: string) {
|
||||
if (publisher === "thirdweb.eth") {
|
||||
return "deployer.thirdweb.eth";
|
||||
}
|
||||
return publisher;
|
||||
}
|
||||
|
||||
let publisherContract: ThirdwebContract;
|
||||
function getPublisherContract() {
|
||||
if (!publisherContract) {
|
||||
publisherContract = getContract({
|
||||
client: thirdwebClient,
|
||||
address: "0xf5b896Ddb5146D5dA77efF4efBb3Eae36E300808",
|
||||
chain: polygon,
|
||||
});
|
||||
}
|
||||
return publisherContract;
|
||||
}
|
||||
|
||||
export async function fetchPublisherProfile(publisherAddress: string) {
|
||||
const profileUri = await getPublisherProfileUri({
|
||||
contract: getPublisherContract(),
|
||||
publisher: isAddress(publisherAddress)
|
||||
? publisherAddress
|
||||
: await resolveAddress({
|
||||
client: thirdwebClient,
|
||||
name: mapThirdwebPublisher(publisherAddress),
|
||||
}),
|
||||
});
|
||||
if (!profileUri) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const res = await download({
|
||||
client: thirdwebClient,
|
||||
uri: profileUri,
|
||||
});
|
||||
return res.json() as Promise<ProfileMetadata>;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchPublishedContractVersions(
|
||||
publisherAddress: string,
|
||||
contractId: string,
|
||||
) {
|
||||
const allVersions = await getPublishedContractVersions({
|
||||
contract: getPublisherContract(),
|
||||
publisher: isAddress(publisherAddress)
|
||||
? publisherAddress
|
||||
: await resolveAddress({
|
||||
client: thirdwebClient,
|
||||
name: mapThirdwebPublisher(publisherAddress),
|
||||
}),
|
||||
contractId: contractId,
|
||||
});
|
||||
|
||||
const sortedVersions = allVersions.toSorted((a, b) => {
|
||||
if (a.publishTimestamp === b.publishTimestamp) {
|
||||
return 0;
|
||||
}
|
||||
return a.publishTimestamp > b.publishTimestamp ? -1 : 1;
|
||||
});
|
||||
|
||||
const responses = await Promise.allSettled(
|
||||
sortedVersions.map((v) =>
|
||||
fetchDeployMetadata({
|
||||
client: thirdwebClient,
|
||||
uri: v.publishMetadataUri,
|
||||
}).then((m) => ({ ...m, ...v })),
|
||||
),
|
||||
);
|
||||
|
||||
return responses.filter((r) => r.status === "fulfilled").map((r) => r.value);
|
||||
}
|
||||
|
||||
export async function fetchPublishedContractVersion(
|
||||
publisherAddress: string,
|
||||
contractId: string,
|
||||
version = "latest",
|
||||
) {
|
||||
const allVersions = await fetchPublishedContractVersions(
|
||||
publisherAddress,
|
||||
contractId,
|
||||
);
|
||||
if (allVersions.length === 0) {
|
||||
return null;
|
||||
}
|
||||
if (version === "latest") {
|
||||
return allVersions[0];
|
||||
}
|
||||
return allVersions.find((v) => v.version === version) || allVersions[0];
|
||||
}
|
||||
|
||||
export type PublishedContractWithVersion = Awaited<
|
||||
ReturnType<typeof fetchPublishedContractVersions>
|
||||
>[number];
|
||||
@@ -1,127 +1,43 @@
|
||||
import { thirdwebClient } from "@/constants/client";
|
||||
import {
|
||||
contractKeys,
|
||||
networkKeys,
|
||||
useDashboardEVMChainId,
|
||||
} from "@3rdweb-sdk/react";
|
||||
import { networkKeys, useDashboardEVMChainId } from "@3rdweb-sdk/react";
|
||||
import { useMutationWithInvalidate } from "@3rdweb-sdk/react/hooks/query/useQueryWithNetwork";
|
||||
import {
|
||||
type QueryClient,
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from "@tanstack/react-query";
|
||||
import { useSDK, useSigner } from "@thirdweb-dev/react";
|
||||
import { useSDK } from "@thirdweb-dev/react";
|
||||
import {
|
||||
type DeploymentTransaction,
|
||||
type ExtraPublishMetadata,
|
||||
type FeatureName,
|
||||
type FeatureWithEnabled,
|
||||
type PublishedContract,
|
||||
type ThirdwebSDK,
|
||||
detectFeatures,
|
||||
fetchAndCacheDeployMetadata,
|
||||
fetchContractMetadata,
|
||||
fetchPreDeployMetadata,
|
||||
fetchRawPredeployMetadata,
|
||||
getTrustedForwarders,
|
||||
isExtensionEnabled,
|
||||
} from "@thirdweb-dev/sdk";
|
||||
import {
|
||||
getZkTransactionsForDeploy,
|
||||
zkDeployContractFromUri,
|
||||
} from "@thirdweb-dev/sdk/evm/zksync";
|
||||
import type { Abi } from "abitype";
|
||||
import type { ProfileMetadata, ProfileMetadataInput } from "constants/schemas";
|
||||
import type { providers } from "ethers";
|
||||
import { useSupportedChain } from "hooks/chains/configureChains";
|
||||
import { isEnsName, resolveEns } from "lib/ens";
|
||||
import { getDashboardChainRpc } from "lib/rpc";
|
||||
import { StorageSingleton, getThirdwebSDK } from "lib/sdk";
|
||||
import { useRouter } from "next/router";
|
||||
import { useMemo } from "react";
|
||||
import { getContract } from "thirdweb";
|
||||
import {
|
||||
abstractTestnet,
|
||||
polygon,
|
||||
zkCandySepolia,
|
||||
zkSync,
|
||||
zkSyncSepolia,
|
||||
} from "thirdweb/chains";
|
||||
import { polygon } from "thirdweb/chains";
|
||||
import { resolveContractAbi } from "thirdweb/contract";
|
||||
import {
|
||||
useActiveAccount,
|
||||
useActiveWallet,
|
||||
useActiveWalletChain,
|
||||
} from "thirdweb/react";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import { isAddress } from "thirdweb/utils";
|
||||
import invariant from "tiny-invariant";
|
||||
import { Web3Provider } from "zksync-ethers";
|
||||
import { useV5DashboardChain } from "../../lib/v5-adapter";
|
||||
import type { CustomContractDeploymentFormData } from "./contract-deploy-form/custom-contract";
|
||||
import type {
|
||||
DeployModalStep,
|
||||
DeployStatusModal,
|
||||
} from "./contract-deploy-form/deploy-context-modal";
|
||||
import { uploadContractMetadata } from "./contract-deploy-form/deploy-form-utils";
|
||||
import type {} from "./contract-deploy-form/deploy-context-modal";
|
||||
import {
|
||||
type PublishedContractWithVersion,
|
||||
fetchPublishedContractVersions,
|
||||
} from "./fetch-contracts-with-versions";
|
||||
import type { ContractId } from "./types";
|
||||
import { addContractToMultiChainRegistry } from "./utils";
|
||||
|
||||
function isChainIdZkSync(chainId?: number) {
|
||||
switch (chainId) {
|
||||
case zkSync.id:
|
||||
case zkSyncSepolia.id:
|
||||
case zkCandySepolia.id:
|
||||
case abstractTestnet.id:
|
||||
// TODO - add more ZK chains here
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// metadata PRE publish, only has the compiler output info (from CLI)
|
||||
export async function fetchRawPredeployMetadataFromURI(contractId: ContractId) {
|
||||
const contractIdIpfsHash = toContractIdIpfsHash(contractId);
|
||||
|
||||
invariant(contractId !== "ipfs://undefined", "uri can't be undefined");
|
||||
let resolved:
|
||||
| Awaited<ReturnType<typeof fetchRawPredeployMetadata>>
|
||||
| undefined = undefined;
|
||||
try {
|
||||
resolved = await fetchRawPredeployMetadata(
|
||||
contractIdIpfsHash,
|
||||
StorageSingleton,
|
||||
);
|
||||
} catch (err) {
|
||||
console.error("failed to resolvePreDeployMetadata", err);
|
||||
}
|
||||
|
||||
if (!resolved) {
|
||||
return {
|
||||
name: "",
|
||||
metadataUri: "",
|
||||
bytecodeUri: "",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
name: resolved.name,
|
||||
metadataUri: resolved.metadataUri,
|
||||
bytecodeUri: resolved.bytecodeUri,
|
||||
analytics: removeUndefinedFromObject(resolved.analytics),
|
||||
compilers: resolved.compilers,
|
||||
};
|
||||
}
|
||||
|
||||
export function useContractRawPredeployMetadataFromURI(contractId: ContractId) {
|
||||
return useQuery({
|
||||
queryKey: ["raw-predeploy-metadata", contractId],
|
||||
queryFn: () => fetchRawPredeployMetadataFromURI(contractId),
|
||||
|
||||
enabled: !!contractId,
|
||||
});
|
||||
}
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: FIXME
|
||||
function removeUndefinedFromObject(obj: Record<string, any>) {
|
||||
@@ -136,9 +52,7 @@ function removeUndefinedFromObject(obj: Record<string, any>) {
|
||||
}
|
||||
|
||||
// metadata PRE publish, only has the compiler output info (from CLI)
|
||||
export async function fetchContractPublishMetadataFromURI(
|
||||
contractId: ContractId,
|
||||
) {
|
||||
async function fetchContractPublishMetadataFromURI(contractId: ContractId) {
|
||||
const contractIdIpfsHash = toContractIdIpfsHash(contractId);
|
||||
|
||||
invariant(contractId !== "ipfs://undefined", "uri can't be undefined");
|
||||
@@ -181,21 +95,6 @@ export function useContractPublishMetadataFromURI(contractId: ContractId) {
|
||||
});
|
||||
}
|
||||
|
||||
export function useDefaultForwarders() {
|
||||
const sdk = useSDK();
|
||||
const provider = sdk?.getProvider();
|
||||
invariant(provider, "Require provider");
|
||||
|
||||
const chainId = useDashboardEVMChainId();
|
||||
|
||||
return useQuery({
|
||||
queryKey: ["default-forwarders", chainId],
|
||||
queryFn: () => {
|
||||
return getTrustedForwarders(provider, StorageSingleton);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// metadata PRE publish, only contains the compiler output
|
||||
// if passing an address, also fetches the latest version of the matching contract
|
||||
export function useContractPrePublishMetadata(uri: string, address?: string) {
|
||||
@@ -295,63 +194,21 @@ export function usePublisherProfile(publisherAddress?: string) {
|
||||
return useQuery(publisherProfileQuery(publisherAddress));
|
||||
}
|
||||
|
||||
export async function fetchAllVersions(
|
||||
sdk?: ThirdwebSDK,
|
||||
publisherAddress?: string,
|
||||
contractName?: string,
|
||||
) {
|
||||
invariant(publisherAddress, "address is not defined");
|
||||
invariant(contractName, "contract name is not defined");
|
||||
invariant(sdk, "sdk not provided");
|
||||
|
||||
const allVersions = await sdk
|
||||
.getPublisher()
|
||||
.getAllVersions(publisherAddress, contractName);
|
||||
|
||||
const publishedVersions = [];
|
||||
|
||||
for (let i = 0; i < allVersions.length; i++) {
|
||||
const contractInfo = await sdk
|
||||
.getPublisher()
|
||||
.fetchPublishedContractInfo(allVersions[i])
|
||||
.catch(() => {
|
||||
console.error(
|
||||
`failed to fetchPublishedContractInfo for metadataUri: ${allVersions[i].metadataUri} - ignoring version`,
|
||||
);
|
||||
return null;
|
||||
});
|
||||
if (!contractInfo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
publishedVersions.unshift({
|
||||
...allVersions[i],
|
||||
version: contractInfo.publishedMetadata.version,
|
||||
name: contractInfo.publishedMetadata.name,
|
||||
displayName: contractInfo.publishedMetadata.displayName || "",
|
||||
description: contractInfo.publishedMetadata.description || "",
|
||||
publisher: contractInfo.publishedMetadata.publisher || "",
|
||||
audit: contractInfo.publishedMetadata.audit || "",
|
||||
logo: contractInfo.publishedMetadata.logo || "",
|
||||
});
|
||||
}
|
||||
|
||||
return publishedVersions;
|
||||
}
|
||||
|
||||
export function useAllVersions(
|
||||
publisherAddress?: string,
|
||||
contractName?: string,
|
||||
publisherAddress: string | undefined,
|
||||
contractId: string | undefined,
|
||||
) {
|
||||
const sdk = getThirdwebSDK(
|
||||
polygon.id,
|
||||
getDashboardChainRpc(polygon.id, undefined),
|
||||
);
|
||||
return useQuery({
|
||||
queryKey: ["all-releases", publisherAddress, contractName],
|
||||
queryFn: () => fetchAllVersions(sdk, publisherAddress, contractName),
|
||||
queryKey: ["all-releases", publisherAddress, contractId],
|
||||
queryFn: () => {
|
||||
if (!publisherAddress || !contractId) {
|
||||
// should never happen because we check for this in the enabled check
|
||||
throw new Error("publisherAddress or contractId is not defined");
|
||||
}
|
||||
return fetchPublishedContractVersions(publisherAddress, contractId);
|
||||
},
|
||||
|
||||
enabled: !!publisherAddress && !!contractName && !!sdk,
|
||||
enabled: !!publisherAddress && !!contractId,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -398,82 +255,86 @@ export function usePublishedContractsFromDeploy(
|
||||
});
|
||||
}
|
||||
|
||||
function publishedContractWithVersionToLegacy(
|
||||
input: PublishedContractWithVersion,
|
||||
) {
|
||||
return {
|
||||
...input,
|
||||
timestamp: input.publishTimestamp.toString(),
|
||||
id: input.contractId,
|
||||
metadataUri: input.publishMetadataUri,
|
||||
} satisfies PublishedContract;
|
||||
}
|
||||
|
||||
export async function fetchPublishedContractInfo(
|
||||
sdk?: ThirdwebSDK,
|
||||
contract?: PublishedContract,
|
||||
contract?: PublishedContractWithVersion,
|
||||
) {
|
||||
invariant(contract, "contract is not defined");
|
||||
invariant(sdk, "sdk not provided");
|
||||
return await sdk.getPublisher().fetchPublishedContractInfo(contract);
|
||||
return await sdk
|
||||
.getPublisher()
|
||||
.fetchPublishedContractInfo(publishedContractWithVersionToLegacy(contract));
|
||||
}
|
||||
|
||||
export function usePublishedContractInfo(contract: PublishedContract) {
|
||||
export function usePublishedContractInfo(
|
||||
contract: PublishedContractWithVersion,
|
||||
) {
|
||||
const sdk = getThirdwebSDK(
|
||||
polygon.id,
|
||||
getDashboardChainRpc(polygon.id, undefined),
|
||||
);
|
||||
return useQuery({
|
||||
queryKey: ["released-contract", contract],
|
||||
queryFn: () => fetchPublishedContractInfo(sdk, contract),
|
||||
|
||||
queryFn: () =>
|
||||
fetchPublishedContractInfo(
|
||||
sdk,
|
||||
publishedContractWithVersionToLegacy(contract),
|
||||
),
|
||||
enabled: !!contract,
|
||||
});
|
||||
}
|
||||
export function usePublishedContractFunctions(contract: PublishedContract) {
|
||||
const publishedContractInfo = usePublishedContractInfo(contract);
|
||||
const compositeAbi =
|
||||
publishedContractInfo.data?.publishedMetadata.compositeAbi;
|
||||
export function usePublishedContractFunctions(
|
||||
publishedContract: PublishedContractWithVersion,
|
||||
) {
|
||||
const compositeAbi = publishedContract.compositeAbi;
|
||||
|
||||
const { data: meta } = useContractPublishMetadataFromURI(
|
||||
contract.metadataUri,
|
||||
);
|
||||
|
||||
const dynamicContractType =
|
||||
publishedContractInfo.data?.publishedMetadata.routerType;
|
||||
const dynamicContractType = publishedContract.routerType;
|
||||
if (
|
||||
compositeAbi &&
|
||||
(dynamicContractType === "plugin" ||
|
||||
dynamicContractType === "dynamic" ||
|
||||
!publishedContractInfo.data?.publishedMetadata.deployType ||
|
||||
publishedContractInfo.data?.publishedMetadata.name.includes(
|
||||
"MarketplaceV3",
|
||||
))
|
||||
!publishedContract.deployType ||
|
||||
publishedContract.name.includes("MarketplaceV3"))
|
||||
) {
|
||||
return compositeAbi.filter((f) => f.type === "function");
|
||||
}
|
||||
|
||||
return meta?.abi?.filter((f) => f.type === "function") || [];
|
||||
return publishedContract?.abi?.filter((f) => f.type === "function") || [];
|
||||
}
|
||||
export function usePublishedContractEvents(contract: PublishedContract) {
|
||||
const publishedContractInfo = usePublishedContractInfo(contract);
|
||||
const compositeAbi =
|
||||
publishedContractInfo.data?.publishedMetadata.compositeAbi;
|
||||
export function usePublishedContractEvents(
|
||||
publishedContract: PublishedContractWithVersion,
|
||||
) {
|
||||
const compositeAbi = publishedContract.compositeAbi;
|
||||
|
||||
const { data: meta } = useContractPublishMetadataFromURI(
|
||||
contract.metadataUri,
|
||||
);
|
||||
|
||||
const dynamicContractType =
|
||||
publishedContractInfo.data?.publishedMetadata.routerType;
|
||||
const dynamicContractType = publishedContract.routerType;
|
||||
if (
|
||||
compositeAbi &&
|
||||
(dynamicContractType === "plugin" ||
|
||||
dynamicContractType === "dynamic" ||
|
||||
!publishedContractInfo.data?.publishedMetadata.deployType ||
|
||||
publishedContractInfo.data?.publishedMetadata.name.includes(
|
||||
"MarketplaceV3",
|
||||
))
|
||||
!publishedContract.deployType ||
|
||||
publishedContract.name.includes("MarketplaceV3"))
|
||||
) {
|
||||
return compositeAbi.filter((f) => f.type === "event");
|
||||
}
|
||||
|
||||
return meta?.abi?.filter((f) => f.type === "event") || [];
|
||||
return publishedContract?.abi?.filter((f) => f.type === "event") || [];
|
||||
}
|
||||
|
||||
export function usePublishedContractCompilerMetadata(
|
||||
contract: PublishedContract,
|
||||
contract: PublishedContractWithVersion,
|
||||
) {
|
||||
return useContractPublishMetadataFromURI(contract.metadataUri);
|
||||
return useContractPublishMetadataFromURI(contract.publishMetadataUri);
|
||||
}
|
||||
|
||||
export function useFunctionParamsFromABI(abi?: Abi, functionName?: string) {
|
||||
@@ -549,393 +410,6 @@ export function useEditProfileMutation() {
|
||||
);
|
||||
}
|
||||
|
||||
type ContractDeployMutationParams = CustomContractDeploymentFormData & {
|
||||
address: string | undefined;
|
||||
addToDashboard: boolean;
|
||||
};
|
||||
|
||||
export function useCustomContractDeployMutation(options: {
|
||||
ipfsHash: string;
|
||||
version?: string;
|
||||
forceDirectDeploy?: boolean;
|
||||
hasContractURI?: boolean;
|
||||
hasRoyalty?: boolean;
|
||||
isSplit?: boolean;
|
||||
isVote?: boolean;
|
||||
isErc721SharedMetadadata?: boolean;
|
||||
deployStatusModal: DeployStatusModal;
|
||||
}) {
|
||||
const {
|
||||
ipfsHash,
|
||||
version,
|
||||
hasContractURI,
|
||||
hasRoyalty,
|
||||
isSplit,
|
||||
isVote,
|
||||
isErc721SharedMetadadata,
|
||||
deployStatusModal,
|
||||
forceDirectDeploy,
|
||||
} = options;
|
||||
const sdk = useSDK();
|
||||
const queryClient = useQueryClient();
|
||||
const account = useActiveAccount();
|
||||
const walletAddress = account?.address;
|
||||
const chainId = useActiveWalletChain()?.id;
|
||||
const signer = useSigner();
|
||||
const { data: transactions } = useTransactionsForDeploy(ipfsHash);
|
||||
const fullPublishMetadata = useContractFullPublishMetadata(ipfsHash);
|
||||
const rawPredeployMetadata = useContractRawPredeployMetadataFromURI(ipfsHash);
|
||||
const router = useRouter();
|
||||
|
||||
const walletId = useActiveWallet()?.id;
|
||||
|
||||
return useMutation(
|
||||
async (_data: ContractDeployMutationParams) => {
|
||||
const data = { ..._data };
|
||||
|
||||
invariant(
|
||||
sdk && "getPublisher" in sdk,
|
||||
"sdk is not ready or does not support publishing",
|
||||
);
|
||||
invariant(signer, "signer is not provided");
|
||||
invariant(walletAddress, "walletAddress is not provided");
|
||||
|
||||
const requiresSignature = walletId !== "inApp";
|
||||
|
||||
const stepDeploy: DeployModalStep = {
|
||||
signatureCount: requiresSignature ? transactions?.length || 1 : 0,
|
||||
type: "deploy",
|
||||
};
|
||||
|
||||
const steps = [stepDeploy];
|
||||
|
||||
if (isErc721SharedMetadadata) {
|
||||
steps.push({
|
||||
type: "setNFTMetadata",
|
||||
signatureCount: requiresSignature ? 1 : 0,
|
||||
});
|
||||
}
|
||||
|
||||
if (data.addToDashboard) {
|
||||
steps.push({
|
||||
type: "import",
|
||||
signatureCount: requiresSignature ? 1 : 0,
|
||||
});
|
||||
}
|
||||
|
||||
// open the modal with the appropriate steps
|
||||
deployStatusModal.open(steps);
|
||||
|
||||
const isZkSync = isChainIdZkSync(chainId);
|
||||
|
||||
let contractAddress: string;
|
||||
try {
|
||||
if (hasContractURI) {
|
||||
data.deployParams._contractURI = await uploadContractMetadata({
|
||||
...data.contractMetadata,
|
||||
...(hasRoyalty && {
|
||||
seller_fee_basis_points:
|
||||
typeof data.deployParams._royaltyBps === "string"
|
||||
? Number.parseInt(data.deployParams._royaltyBps, 10)
|
||||
: data.deployParams?._royaltyBps || 0,
|
||||
}),
|
||||
...(hasRoyalty && {
|
||||
fee_recipient: data.deployParams._royaltyRecipient,
|
||||
}),
|
||||
...(isSplit && {
|
||||
recipients: data.recipients,
|
||||
}),
|
||||
...(isVote && {
|
||||
voting_delay_in_blocks: Number(
|
||||
data.deployParams._initialVotingDelay,
|
||||
),
|
||||
voting_period_in_blocks: Number(
|
||||
data.deployParams._initialVotingPeriod,
|
||||
),
|
||||
voting_token_address: data.deployParams._token,
|
||||
voting_quorum_fraction: Number(
|
||||
data.deployParams._initialVoteQuorumFraction,
|
||||
),
|
||||
proposal_token_threshold:
|
||||
data.deployParams._initialProposalThreshold,
|
||||
}),
|
||||
});
|
||||
if ("_name" in data.deployParams) {
|
||||
data.deployParams._name = data.contractMetadata?.name || "";
|
||||
}
|
||||
if ("_symbol" in data.deployParams) {
|
||||
data.deployParams._symbol = data.contractMetadata?.symbol || "";
|
||||
}
|
||||
}
|
||||
|
||||
if (isSplit) {
|
||||
data.deployParams._payees = JSON.stringify(
|
||||
data.recipients?.map((r) => r.address) || [],
|
||||
);
|
||||
data.deployParams._shares = JSON.stringify(
|
||||
data.recipients?.map((r) => r.sharesBps) || [],
|
||||
);
|
||||
}
|
||||
|
||||
if (data.deployParams?._defaultAdmin === "") {
|
||||
data.deployParams._defaultAdmin = data.address || "";
|
||||
}
|
||||
|
||||
// Handle ZkSync deployments separately
|
||||
|
||||
// deploy contract
|
||||
if (isZkSync) {
|
||||
// Get metamask signer using zksync-ethers library -- for custom fields in signature
|
||||
let deploySigner = signer;
|
||||
|
||||
if (fullPublishMetadata?.data?.deployType === "standard") {
|
||||
// if its a direct deploy AND its zksync, use the ethers zksync signer for now
|
||||
// NOTE: this only works with injected wallets
|
||||
// TODO - implement deploying to zksync in v5 account and remove this
|
||||
const fakeExternalProvider: providers.ExternalProvider = {
|
||||
// fake tell it its metamask always (lul)
|
||||
isMetaMask: true,
|
||||
request({ method, params }) {
|
||||
switch (method) {
|
||||
case "eth_accounts": {
|
||||
return Promise.resolve([walletAddress]);
|
||||
}
|
||||
case "eth_signTypedData_v4": {
|
||||
invariant(params?.[1], "invalid signTypedData call");
|
||||
// yo dawg, I heard you like signing typed data
|
||||
const { domain, types, message, primaryType } = JSON.parse(
|
||||
params[1],
|
||||
);
|
||||
return (signer as providers.JsonRpcSigner)._signTypedData(
|
||||
domain,
|
||||
// don't ask...
|
||||
{ [primaryType]: types[primaryType] },
|
||||
message,
|
||||
);
|
||||
}
|
||||
case "eth_sendTransaction": {
|
||||
const tx = params?.[0];
|
||||
if (!tx) {
|
||||
throw new Error("No transaction provided");
|
||||
}
|
||||
return signer.sendTransaction(tx);
|
||||
}
|
||||
case "personal_sign": {
|
||||
const data = params?.[0];
|
||||
if (!data) {
|
||||
throw new Error("Nothing to sign");
|
||||
}
|
||||
return signer.signMessage(data);
|
||||
}
|
||||
default: {
|
||||
return (signer as providers.JsonRpcSigner)?.provider?.send(
|
||||
method,
|
||||
params ?? [],
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
const zkSigner = new Web3Provider(fakeExternalProvider).getSigner();
|
||||
deploySigner = zkSigner;
|
||||
}
|
||||
|
||||
const publishUri = ipfsHash.startsWith("ipfs://")
|
||||
? ipfsHash
|
||||
: `ipfs://${ipfsHash}`;
|
||||
|
||||
let uriToRegister = "";
|
||||
|
||||
if (
|
||||
fullPublishMetadata?.data?.compilers?.zksolc ||
|
||||
rawPredeployMetadata?.data?.compilers?.zksolc
|
||||
) {
|
||||
if (data.deployDeterministic) {
|
||||
const salt = data.signerAsSalt
|
||||
? (await signer?.getAddress())?.concat(
|
||||
data.saltForCreate2 || "",
|
||||
)
|
||||
: data.saltForCreate2;
|
||||
|
||||
contractAddress = await zkDeployContractFromUri(
|
||||
publishUri,
|
||||
Object.values(data.deployParams),
|
||||
deploySigner,
|
||||
StorageSingleton,
|
||||
chainId as number,
|
||||
{
|
||||
compilerOptions: {
|
||||
compilerType: "zksolc",
|
||||
},
|
||||
saltForProxyDeploy: salt,
|
||||
},
|
||||
true,
|
||||
);
|
||||
} else {
|
||||
contractAddress = await zkDeployContractFromUri(
|
||||
publishUri,
|
||||
Object.values(data.deployParams),
|
||||
deploySigner,
|
||||
StorageSingleton,
|
||||
chainId as number,
|
||||
{
|
||||
compilerOptions: {
|
||||
compilerType: "zksolc",
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const { compilerMetadata } = await fetchAndCacheDeployMetadata(
|
||||
publishUri,
|
||||
StorageSingleton,
|
||||
{
|
||||
compilerType: "zksolc",
|
||||
},
|
||||
);
|
||||
uriToRegister = compilerMetadata.fetchedMetadataUri;
|
||||
} else {
|
||||
contractAddress = await zkDeployContractFromUri(
|
||||
publishUri,
|
||||
Object.values(data.deployParams),
|
||||
signer,
|
||||
StorageSingleton,
|
||||
chainId as number,
|
||||
);
|
||||
|
||||
const { compilerMetadata } = await fetchAndCacheDeployMetadata(
|
||||
publishUri,
|
||||
StorageSingleton,
|
||||
);
|
||||
uriToRegister = compilerMetadata.fetchedMetadataUri;
|
||||
}
|
||||
|
||||
// register deployed zksync contract on multichain registry
|
||||
await addContractToMultiChainRegistry(
|
||||
{
|
||||
address: contractAddress,
|
||||
chainId: chainId as number,
|
||||
metadataURI: uriToRegister,
|
||||
},
|
||||
account,
|
||||
300000n,
|
||||
);
|
||||
} else {
|
||||
if (data.deployDeterministic) {
|
||||
const salt = data.signerAsSalt
|
||||
? (await signer?.getAddress())?.concat(data.saltForCreate2 || "")
|
||||
: data.saltForCreate2;
|
||||
contractAddress =
|
||||
await sdk.deployer.deployPublishedContractDeterministic(
|
||||
fullPublishMetadata.data?.name as string,
|
||||
Object.values(data.deployParams),
|
||||
fullPublishMetadata.data?.publisher as string,
|
||||
// this is either the contract version or it falls back to "latest"
|
||||
version,
|
||||
salt,
|
||||
);
|
||||
} else {
|
||||
contractAddress = await sdk.deployer.deployContractFromUri(
|
||||
ipfsHash.startsWith("ipfs://") ? ipfsHash : `ipfs://${ipfsHash}`,
|
||||
Object.values(data.deployParams),
|
||||
{
|
||||
forceDirectDeploy,
|
||||
defaultModules: data.defaultModules,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
deployStatusModal.nextStep();
|
||||
} catch (e) {
|
||||
// failed to deploy contract - close modal for now
|
||||
deployStatusModal.close();
|
||||
// re-throw error
|
||||
throw e;
|
||||
}
|
||||
|
||||
const contract = await sdk.getContract(contractAddress);
|
||||
|
||||
if (isErc721SharedMetadadata) {
|
||||
try {
|
||||
await contract.erc721.sharedMetadata.set({
|
||||
name: data.contractMetadata?.name || "",
|
||||
description: data.contractMetadata?.description || "",
|
||||
image: data.contractMetadata?.image || "",
|
||||
});
|
||||
|
||||
deployStatusModal.nextStep();
|
||||
} catch {
|
||||
// failed to set metadata - for now just close the modal
|
||||
deployStatusModal.close();
|
||||
// not re-throwing the error, this is not technically a failure to deploy, just to set metadata - the contract is deployed already at this stage
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// let user decide if they want this or not
|
||||
if (data.addToDashboard && !isZkSync) {
|
||||
invariant(chainId, "chainId is not provided");
|
||||
await addContractToMultiChainRegistry(
|
||||
{
|
||||
address: contractAddress,
|
||||
chainId,
|
||||
},
|
||||
account,
|
||||
300000n,
|
||||
);
|
||||
|
||||
deployStatusModal.nextStep();
|
||||
}
|
||||
} catch {
|
||||
// failed to add to dashboard - for now just close the modal
|
||||
deployStatusModal.close();
|
||||
router.replace(`/${chainId}/${contractAddress}`);
|
||||
|
||||
// not re-throwing the error, this is not technically a failure to deploy, just to add to dashboard - the contract is deployed already at this stage
|
||||
}
|
||||
|
||||
return contractAddress;
|
||||
},
|
||||
{
|
||||
onSuccess: async () => {
|
||||
return await queryClient.invalidateQueries([
|
||||
...networkKeys.chain(chainId),
|
||||
...contractKeys.list(walletAddress),
|
||||
[networkKeys.multiChainRegistry, walletAddress],
|
||||
]);
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function useTransactionsForDeploy(publishMetadataOrUri: string) {
|
||||
const sdk = useSDK();
|
||||
const chainId = useActiveWalletChain()?.id;
|
||||
|
||||
const queryResult = useQuery<DeploymentTransaction[]>({
|
||||
queryKey: ["transactions-for-deploy", publishMetadataOrUri, chainId],
|
||||
queryFn: async () => {
|
||||
invariant(sdk, "sdk not provided");
|
||||
|
||||
// Handle separately for ZkSync
|
||||
if (isChainIdZkSync(chainId)) {
|
||||
return await getZkTransactionsForDeploy();
|
||||
}
|
||||
|
||||
return await sdk.deployer.getTransactionsForDeploy(
|
||||
publishMetadataOrUri.startsWith("ipfs://")
|
||||
? publishMetadataOrUri
|
||||
: `ipfs://${publishMetadataOrUri}`,
|
||||
);
|
||||
},
|
||||
|
||||
enabled: !!publishMetadataOrUri && !!sdk,
|
||||
});
|
||||
|
||||
return queryResult;
|
||||
}
|
||||
|
||||
export async function fetchPublishedContracts(
|
||||
sdk: ThirdwebSDK,
|
||||
queryClient: QueryClient,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Divider,
|
||||
Flex,
|
||||
@@ -6,199 +8,118 @@ import {
|
||||
List,
|
||||
ListItem,
|
||||
} from "@chakra-ui/react";
|
||||
import { SiTwitter } from "@react-icons/all-files/si/SiTwitter";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
type PublishedContract as PublishedContractType,
|
||||
type PublishedMetadata,
|
||||
fetchSourceFilesFromMetadata,
|
||||
} from "@thirdweb-dev/sdk";
|
||||
import type { Abi } from "abitype";
|
||||
import { ContractFunctionsOverview } from "components/contract-functions/contract-functions";
|
||||
import { replaceDeployerAddress } from "components/explore/publisher";
|
||||
import { ShareButton } from "components/share-buttom";
|
||||
import { THIRDWEB_DOMAIN } from "constants/urls";
|
||||
import { Extensions } from "contract-ui/tabs/overview/components/Extensions";
|
||||
import { format } from "date-fns/format";
|
||||
import { correctAndUniqueLicenses } from "lib/licenses";
|
||||
import { StorageSingleton, replaceIpfsUrl } from "lib/sdk";
|
||||
import { getAbsoluteUrl } from "lib/vercel-utils";
|
||||
import { NextSeo } from "next-seo";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import { PublishedContractOG } from "og-lib/url-utils";
|
||||
import { replaceIpfsUrl } from "lib/sdk";
|
||||
import { useMemo } from "react";
|
||||
import { BiPencil } from "react-icons/bi";
|
||||
import { BsShieldCheck } from "react-icons/bs";
|
||||
import { VscBook, VscCalendar, VscServer } from "react-icons/vsc";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import { download } from "thirdweb/storage";
|
||||
import invariant from "tiny-invariant";
|
||||
import { Card, Heading, Link, LinkButton, Text } from "tw-components";
|
||||
import { thirdwebClient } from "../../../@/constants/client";
|
||||
import type { PublishedContractWithVersion } from "../fetch-contracts-with-versions";
|
||||
import {
|
||||
Card,
|
||||
Heading,
|
||||
Link,
|
||||
LinkButton,
|
||||
Text,
|
||||
TrackedIconButton,
|
||||
} from "tw-components";
|
||||
import { shortenIfAddress } from "utils/usedapp-external";
|
||||
import {
|
||||
useContractEnabledExtensions,
|
||||
useContractPublishMetadataFromURI,
|
||||
useEns,
|
||||
usePublishedContractCompilerMetadata,
|
||||
usePublishedContractEvents,
|
||||
usePublishedContractFunctions,
|
||||
usePublishedContractInfo,
|
||||
usePublisherProfile,
|
||||
} from "../hooks";
|
||||
import { PublisherHeader } from "../publisher/publisher-header";
|
||||
import { AddressesModal } from "./addresses-modal";
|
||||
import { MarkdownRenderer } from "./markdown-renderer";
|
||||
|
||||
export interface ExtendedPublishedContract extends PublishedContractType {
|
||||
export interface ExtendedPublishedContract
|
||||
extends PublishedContractWithVersion {
|
||||
name: string;
|
||||
displayName?: string;
|
||||
description: string;
|
||||
version: string | undefined;
|
||||
publisher: string;
|
||||
description?: string;
|
||||
version?: string;
|
||||
publisher?: string;
|
||||
tags?: string[];
|
||||
logo?: string;
|
||||
audit?: string;
|
||||
}
|
||||
|
||||
interface PublishedContractProps {
|
||||
contract: ExtendedPublishedContract;
|
||||
publishedContract: ExtendedPublishedContract;
|
||||
walletOrEns: string;
|
||||
}
|
||||
|
||||
export const PublishedContract: React.FC<PublishedContractProps> = ({
|
||||
contract,
|
||||
publishedContract,
|
||||
walletOrEns,
|
||||
}) => {
|
||||
const address = useActiveAccount()?.address;
|
||||
const publishedContractInfo = usePublishedContractInfo(contract);
|
||||
const { data: compilerInfo } = usePublishedContractCompilerMetadata(contract);
|
||||
|
||||
const router = useRouter();
|
||||
const contractPublishMetadata = useContractPublishMetadataFromURI(
|
||||
contract.metadataUri,
|
||||
);
|
||||
|
||||
const dynamicContractType =
|
||||
publishedContractInfo.data?.publishedMetadata.routerType;
|
||||
|
||||
const compositeAbi =
|
||||
publishedContractInfo.data?.publishedMetadata.compositeAbi;
|
||||
|
||||
const abi =
|
||||
compositeAbi &&
|
||||
(dynamicContractType === "plugin" ||
|
||||
dynamicContractType === "dynamic" ||
|
||||
!publishedContractInfo.data?.publishedMetadata.deployType ||
|
||||
publishedContractInfo.data?.publishedMetadata.name.includes(
|
||||
"MarketplaceV3",
|
||||
))
|
||||
? compositeAbi
|
||||
: contractPublishMetadata.data?.abi;
|
||||
|
||||
const enabledExtensions = useContractEnabledExtensions(
|
||||
abi as Abi | undefined,
|
||||
);
|
||||
|
||||
const publisherProfile = usePublisherProfile(contract.publisher);
|
||||
|
||||
const currentRoute = `${THIRDWEB_DOMAIN}${router.asPath.replace(
|
||||
"/publish",
|
||||
"",
|
||||
)}`.replace("deployer.thirdweb.eth", "thirdweb.eth");
|
||||
|
||||
const contractFunctions = usePublishedContractFunctions(contract);
|
||||
const contractEvents = usePublishedContractEvents(contract);
|
||||
|
||||
const ensQuery = useEns(contract.publisher);
|
||||
|
||||
const publisherEnsOrAddress = replaceDeployerAddress(
|
||||
shortenIfAddress(ensQuery.data?.ensName || contract.publisher),
|
||||
);
|
||||
const contractFunctions = usePublishedContractFunctions(publishedContract);
|
||||
const contractEvents = usePublishedContractEvents(publishedContract);
|
||||
|
||||
const publishDate = format(
|
||||
new Date(
|
||||
Number.parseInt(
|
||||
publishedContractInfo?.data?.publishedTimestamp.toString() || "0",
|
||||
) * 1000,
|
||||
Number.parseInt(publishedContract?.publishTimestamp.toString() || "0") *
|
||||
1000,
|
||||
),
|
||||
"MMM dd, yyyy",
|
||||
);
|
||||
|
||||
const licenses = correctAndUniqueLicenses(compilerInfo?.licenses || []);
|
||||
const licenses = correctAndUniqueLicenses(publishedContract?.licenses || []);
|
||||
|
||||
const publishedContractName = useMemo(() => {
|
||||
if (contract.displayName) {
|
||||
return contract.displayName;
|
||||
}
|
||||
return contract.name;
|
||||
}, [contract.displayName, contract.name]);
|
||||
// const publishedContractName =
|
||||
// publishedContract?.displayName || publishedContract?.name;
|
||||
|
||||
const extensionNames = useMemo(() => {
|
||||
return enabledExtensions.map((ext) => ext.name);
|
||||
}, [enabledExtensions]);
|
||||
// const extensionNames = useMemo(() => {
|
||||
// return enabledExtensions.map((ext) => ext.name);
|
||||
// }, [enabledExtensions]);
|
||||
|
||||
const ogImageUrl = useMemo(
|
||||
() =>
|
||||
PublishedContractOG.toUrl({
|
||||
name: publishedContractName,
|
||||
description: contract.description,
|
||||
version: contract.version || "latest",
|
||||
publisher: publisherEnsOrAddress,
|
||||
extension: extensionNames,
|
||||
license: licenses,
|
||||
publishDate,
|
||||
publisherAvatar: publisherProfile.data?.avatar || undefined,
|
||||
logo: contract.logo,
|
||||
}),
|
||||
[
|
||||
extensionNames,
|
||||
licenses,
|
||||
contract.description,
|
||||
contract.logo,
|
||||
publishedContractName,
|
||||
contract.version,
|
||||
publishDate,
|
||||
publisherEnsOrAddress,
|
||||
publisherProfile.data?.avatar,
|
||||
],
|
||||
);
|
||||
// const ogImageUrl = useMemo(
|
||||
// () =>
|
||||
// PublishedContractOG.toUrl({
|
||||
// name: publishedContractName,
|
||||
// description: contract.description,
|
||||
// version: contract.version || "latest",
|
||||
// publisher: publisherEnsOrAddress,
|
||||
// extension: extensionNames,
|
||||
// license: licenses,
|
||||
// publishDate,
|
||||
// publisherAvatar: publisherProfile.data?.avatar || undefined,
|
||||
// logo: contract.logo,
|
||||
// }),
|
||||
// [
|
||||
// extensionNames,
|
||||
// licenses,
|
||||
// contract.description,
|
||||
// contract.logo,
|
||||
// publishedContractName,
|
||||
// contract.version,
|
||||
// publishDate,
|
||||
// publisherEnsOrAddress,
|
||||
// publisherProfile.data?.avatar,
|
||||
// ],
|
||||
// );
|
||||
|
||||
const twitterIntentUrl = useMemo(() => {
|
||||
const url = new URL("https://twitter.com/intent/tweet");
|
||||
url.searchParams.append(
|
||||
"text",
|
||||
`Check out this ${publishedContractName} contract on @thirdweb
|
||||
// const twitterIntentUrl = useMemo(() => {
|
||||
// const url = new URL("https://twitter.com/intent/tweet");
|
||||
// url.searchParams.append(
|
||||
// "text",
|
||||
// `Check out this ${publishedContractName} contract on @thirdweb
|
||||
|
||||
Deploy it in one click`,
|
||||
);
|
||||
url.searchParams.append("url", currentRoute);
|
||||
return url.href;
|
||||
}, [publishedContractName, currentRoute]);
|
||||
// Deploy it in one click`,
|
||||
// );
|
||||
// url.searchParams.append("url", currentRoute);
|
||||
// return url.href;
|
||||
// }, [publishedContractName, currentRoute]);
|
||||
|
||||
const sources = useQuery({
|
||||
queryKey: ["sources", contract],
|
||||
queryKey: ["sources", publishedContract.publishMetadataUri],
|
||||
queryFn: async () => {
|
||||
invariant(
|
||||
contractPublishMetadata.data?.compilerMetadata?.sources,
|
||||
publishedContract.metadata.sources,
|
||||
"no compilerMetadata sources available",
|
||||
);
|
||||
return (
|
||||
await fetchSourceFilesFromMetadata(
|
||||
{
|
||||
metadata: {
|
||||
sources: contractPublishMetadata.data.compilerMetadata.sources,
|
||||
},
|
||||
} as unknown as PublishedMetadata,
|
||||
StorageSingleton,
|
||||
)
|
||||
)
|
||||
return (await fetchSourceFilesFromMetadata(publishedContract))
|
||||
.map((source) => {
|
||||
return {
|
||||
...source,
|
||||
@@ -208,44 +129,30 @@ Deploy it in one click`,
|
||||
.slice()
|
||||
.reverse();
|
||||
},
|
||||
enabled: !!contractPublishMetadata.data?.compilerMetadata?.sources,
|
||||
enabled: !!publishedContract.metadata.sources,
|
||||
});
|
||||
|
||||
const title = useMemo(() => {
|
||||
let clearType = "";
|
||||
if (extensionNames.includes("ERC721")) {
|
||||
clearType = "ERC721";
|
||||
} else if (extensionNames.includes("ERC20")) {
|
||||
clearType = "ERC20";
|
||||
} else if (extensionNames.includes("ERC1155")) {
|
||||
clearType = "ERC1155";
|
||||
}
|
||||
if (clearType) {
|
||||
return `${publishedContractName} - ${clearType} | Published Smart Contract`;
|
||||
}
|
||||
// const title = useMemo(() => {
|
||||
// let clearType = "";
|
||||
// if (extensionNames.includes("ERC721")) {
|
||||
// clearType = "ERC721";
|
||||
// } else if (extensionNames.includes("ERC20")) {
|
||||
// clearType = "ERC20";
|
||||
// } else if (extensionNames.includes("ERC1155")) {
|
||||
// clearType = "ERC1155";
|
||||
// }
|
||||
// if (clearType) {
|
||||
// return `${publishedContractName} - ${clearType} | Published Smart Contract`;
|
||||
// }
|
||||
|
||||
return `${publishedContractName} | Published Smart Contract`;
|
||||
}, [extensionNames, publishedContractName]);
|
||||
// return `${publishedContractName} | Published Smart Contract`;
|
||||
// }, [extensionNames, publishedContractName]);
|
||||
|
||||
const implementationAddresses = useMemo(
|
||||
() =>
|
||||
publishedContractInfo.data?.publishedMetadata?.factoryDeploymentData
|
||||
?.implementationAddresses,
|
||||
[
|
||||
publishedContractInfo.data?.publishedMetadata?.factoryDeploymentData
|
||||
?.implementationAddresses,
|
||||
],
|
||||
);
|
||||
const implementationAddresses =
|
||||
publishedContract.factoryDeploymentData?.implementationAddresses;
|
||||
|
||||
const factoryAddresses = useMemo(
|
||||
() =>
|
||||
publishedContractInfo.data?.publishedMetadata?.factoryDeploymentData
|
||||
?.factoryAddresses,
|
||||
[
|
||||
publishedContractInfo.data?.publishedMetadata?.factoryDeploymentData
|
||||
?.factoryAddresses,
|
||||
],
|
||||
);
|
||||
const factoryAddresses =
|
||||
publishedContract.factoryDeploymentData?.factoryAddresses;
|
||||
|
||||
const hasImplementationAddresses = useMemo(
|
||||
() => Object.values(implementationAddresses || {}).some((v) => v !== ""),
|
||||
@@ -259,7 +166,7 @@ Deploy it in one click`,
|
||||
|
||||
return (
|
||||
<>
|
||||
<NextSeo
|
||||
{/* <NextSeo
|
||||
title={title}
|
||||
description={`${contract.description}${
|
||||
contract.description ? ". " : ""
|
||||
@@ -276,10 +183,10 @@ Deploy it in one click`,
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
/> */}
|
||||
|
||||
{/* Farcaster frames headers */}
|
||||
<Head>
|
||||
{/* <Head>
|
||||
<meta property="fc:frame" content="vNext" />
|
||||
<meta property="fc:frame:image" content={ogImageUrl.toString()} />
|
||||
<meta
|
||||
@@ -288,38 +195,33 @@ Deploy it in one click`,
|
||||
/>
|
||||
<meta property="fc:frame:button:1" content="Deploy now" />
|
||||
<meta name="fc:frame:button:1:action" content="post_redirect" />
|
||||
</Head>
|
||||
</Head> */}
|
||||
|
||||
<GridItem colSpan={{ base: 12, md: 9 }}>
|
||||
<Flex flexDir="column" gap={6}>
|
||||
{address === contract.publisher && (
|
||||
{address === publishedContract.publisher && (
|
||||
<LinkButton
|
||||
ml="auto"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
leftIcon={<Icon as={BiPencil} />}
|
||||
href={`/contracts/publish/${encodeURIComponent(
|
||||
contract.metadataUri.replace("ipfs://", ""),
|
||||
publishedContract.publishMetadataUri.replace("ipfs://", ""),
|
||||
)}`}
|
||||
>
|
||||
Edit
|
||||
</LinkButton>
|
||||
)}
|
||||
{publishedContractInfo.data?.publishedMetadata?.readme && (
|
||||
{publishedContract?.readme && (
|
||||
<Card as={Flex} flexDir="column" gap={2} p={6} position="relative">
|
||||
<MarkdownRenderer
|
||||
markdownText={
|
||||
publishedContractInfo.data?.publishedMetadata?.readme
|
||||
}
|
||||
/>
|
||||
<MarkdownRenderer markdownText={publishedContract?.readme} />
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{publishedContractInfo.data?.publishedMetadata?.changelog && (
|
||||
{publishedContract?.changelog && (
|
||||
<Card as={Flex} flexDir="column" gap={2} p={0}>
|
||||
<Heading px={6} pt={5} pb={2} size="title.sm">
|
||||
{publishedContractInfo.data?.publishedMetadata?.version} Release
|
||||
Notes
|
||||
{publishedContract?.version} Release Notes
|
||||
</Heading>
|
||||
<Divider />
|
||||
|
||||
@@ -327,22 +229,17 @@ Deploy it in one click`,
|
||||
px={6}
|
||||
pt={2}
|
||||
pb={5}
|
||||
markdownText={
|
||||
publishedContractInfo.data?.publishedMetadata?.changelog
|
||||
}
|
||||
markdownText={publishedContract?.changelog}
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
{contractFunctions && (
|
||||
<Card p={0}>
|
||||
<ContractFunctionsOverview
|
||||
// @ts-expect-error - wrong function type
|
||||
functions={contractFunctions}
|
||||
// @ts-expect-error - wrong event type
|
||||
events={contractEvents}
|
||||
sources={sources.data}
|
||||
// @ts-expect-error - wrong contract type
|
||||
abi={contractPublishMetadata.data?.abi}
|
||||
abi={publishedContract?.abi}
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
@@ -358,7 +255,7 @@ Deploy it in one click`,
|
||||
</Heading>
|
||||
<List as={Flex} flexDir="column" gap={3}>
|
||||
<>
|
||||
{publishedContractInfo.data?.publishedTimestamp && (
|
||||
{publishedContract.publishTimestamp && (
|
||||
<ListItem>
|
||||
<Flex gap={2} alignItems="flex-start">
|
||||
<Icon color="paragraph" as={VscCalendar} boxSize={5} />
|
||||
@@ -373,7 +270,7 @@ Deploy it in one click`,
|
||||
</Flex>
|
||||
</ListItem>
|
||||
)}
|
||||
{publishedContractInfo.data?.publishedMetadata?.audit && (
|
||||
{publishedContract?.audit && (
|
||||
<ListItem>
|
||||
<Flex gap={2} alignItems="flex-start">
|
||||
<Icon as={BsShieldCheck} boxSize={5} color="green" />
|
||||
@@ -383,10 +280,7 @@ Deploy it in one click`,
|
||||
</Heading>
|
||||
<Text size="body.md" lineHeight={1.2}>
|
||||
<Link
|
||||
href={replaceIpfsUrl(
|
||||
publishedContractInfo.data.publishedMetadata
|
||||
.audit,
|
||||
)}
|
||||
href={replaceIpfsUrl(publishedContract.audit)}
|
||||
isExternal
|
||||
_dark={{
|
||||
color: "blue.400",
|
||||
@@ -418,19 +312,16 @@ Deploy it in one click`,
|
||||
</Flex>
|
||||
</Flex>
|
||||
</ListItem>
|
||||
{(publishedContractInfo.data?.publishedMetadata
|
||||
?.isDeployableViaProxy &&
|
||||
{(publishedContract?.isDeployableViaProxy &&
|
||||
hasImplementationAddresses) ||
|
||||
(publishedContractInfo.data?.publishedMetadata
|
||||
?.isDeployableViaFactory &&
|
||||
(publishedContract?.isDeployableViaFactory &&
|
||||
hasFactoryAddresses) ? (
|
||||
<ListItem>
|
||||
<Flex gap={2} alignItems="flex-start">
|
||||
<Icon color="paragraph" as={VscServer} boxSize={5} />
|
||||
<Flex direction="column" gap={1}>
|
||||
<Heading as="h5" size="label.sm">
|
||||
{publishedContractInfo.data?.publishedMetadata
|
||||
?.isDeployableViaFactory
|
||||
{publishedContract?.isDeployableViaFactory
|
||||
? "Factory"
|
||||
: "Proxy"}{" "}
|
||||
Enabled
|
||||
@@ -445,8 +336,7 @@ Deploy it in one click`,
|
||||
) : null}
|
||||
{factoryAddresses &&
|
||||
hasFactoryAddresses &&
|
||||
publishedContractInfo.data?.publishedMetadata
|
||||
?.isDeployableViaFactory ? (
|
||||
publishedContract?.isDeployableViaFactory ? (
|
||||
<AddressesModal
|
||||
chainAddressRecord={factoryAddresses}
|
||||
buttonTitle="Factories"
|
||||
@@ -461,51 +351,6 @@ Deploy it in one click`,
|
||||
</List>
|
||||
</Flex>
|
||||
<Divider />
|
||||
{contractPublishMetadata.data?.abi && (
|
||||
<Extensions
|
||||
// @ts-expect-error - wrong abi type
|
||||
abi={
|
||||
compositeAbi &&
|
||||
(dynamicContractType === "plugin" ||
|
||||
dynamicContractType === "dynamic" ||
|
||||
!publishedContractInfo.data?.publishedMetadata.deployType ||
|
||||
publishedContractInfo.data?.publishedMetadata.name.includes(
|
||||
"MarketplaceV3",
|
||||
))
|
||||
? compositeAbi
|
||||
: contractPublishMetadata.data?.abi
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Divider />
|
||||
<Flex flexDir="column" gap={4}>
|
||||
<Heading as="h4" size="title.sm">
|
||||
Share
|
||||
</Heading>
|
||||
<Flex gap={2} alignItems="center">
|
||||
<ShareButton
|
||||
url={currentRoute}
|
||||
title={`${shortenIfAddress(
|
||||
publisherEnsOrAddress,
|
||||
)}/${publishedContractName}`}
|
||||
text={`Deploy ${shortenIfAddress(
|
||||
publisherEnsOrAddress,
|
||||
)}/${publishedContractName} in one click with thirdweb.`}
|
||||
/>
|
||||
<TrackedIconButton
|
||||
as={LinkButton}
|
||||
isExternal
|
||||
noIcon
|
||||
href={twitterIntentUrl}
|
||||
bg="transparent"
|
||||
aria-label="twitter"
|
||||
icon={<Icon boxSize={5} as={SiTwitter} />}
|
||||
category="released-contract"
|
||||
label="share-twitter"
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Divider />
|
||||
<Flex flexDir="column" gap={4}>
|
||||
<Flex gap={2} alignItems="center">
|
||||
<LinkButton
|
||||
@@ -515,7 +360,7 @@ Deploy it in one click`,
|
||||
variant="ghost"
|
||||
isExternal
|
||||
>
|
||||
Learn about Publish
|
||||
Learn more about Publish
|
||||
</LinkButton>
|
||||
</Flex>
|
||||
</Flex>
|
||||
@@ -524,3 +369,53 @@ Deploy it in one click`,
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// TODO: find a place to put this
|
||||
export type ContractSource = {
|
||||
filename: string;
|
||||
source: string;
|
||||
};
|
||||
async function fetchSourceFilesFromMetadata(
|
||||
publishedMetadata: ExtendedPublishedContract,
|
||||
): Promise<ContractSource[]> {
|
||||
return await Promise.all(
|
||||
Object.entries(publishedMetadata.metadata.sources).map(
|
||||
async ([path, info]) => {
|
||||
const urls = "urls" in info ? info.urls : undefined;
|
||||
const ipfsLink = urls
|
||||
? urls.find((url) => url.includes("ipfs"))
|
||||
: undefined;
|
||||
if (ipfsLink) {
|
||||
const ipfsHash = ipfsLink.split("ipfs/")[1];
|
||||
// 3 sec timeout for sources that haven't been uploaded to ipfs
|
||||
const timeout = new Promise<string>((_resolve, reject) =>
|
||||
setTimeout(() => reject("timeout"), 3000),
|
||||
);
|
||||
const source = await Promise.race([
|
||||
(
|
||||
await download({
|
||||
uri: `ipfs://${ipfsHash}`,
|
||||
client: thirdwebClient,
|
||||
})
|
||||
).text(),
|
||||
timeout,
|
||||
]);
|
||||
return {
|
||||
filename: path,
|
||||
source,
|
||||
};
|
||||
}
|
||||
if ("content" in info) {
|
||||
return {
|
||||
filename: path,
|
||||
source: info.content || "Could not find source for this contract",
|
||||
};
|
||||
}
|
||||
return {
|
||||
filename: path,
|
||||
source: "Could not find source for this contract",
|
||||
};
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,14 +20,20 @@ import {
|
||||
Thead,
|
||||
Tr,
|
||||
} from "@chakra-ui/react";
|
||||
import type { Abi, AbiEvent, AbiFunction } from "abitype";
|
||||
import {
|
||||
type Abi,
|
||||
type AbiEvent,
|
||||
type AbiFunction,
|
||||
formatAbiItem,
|
||||
} from "abitype";
|
||||
import { useContractEnabledExtensions } from "components/contract-components/hooks";
|
||||
import { camelToTitle } from "contract-ui/components/solidity-inputs/helpers";
|
||||
import { useRouter } from "next/router";
|
||||
import { usePathname, useSearchParams } from "next/navigation";
|
||||
import { type Dispatch, type SetStateAction, useMemo, useState } from "react";
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
import { toFunctionSelector } from "thirdweb/utils";
|
||||
import { Badge, Button, Card, Heading, Text } from "tw-components";
|
||||
import { useDashboardRouter } from "../../@/lib/DashboardRouter";
|
||||
import {
|
||||
COMMANDS,
|
||||
formatSnippet,
|
||||
@@ -66,13 +72,12 @@ function joinABIs(abis: Abi[], abiWithConstructor?: Abi): Abi {
|
||||
const flattenedAbis = filteredABIs.flat();
|
||||
|
||||
const finalABIs = unique(flattenedAbis, (a, b): boolean => {
|
||||
return (
|
||||
"name" in a &&
|
||||
"name" in b &&
|
||||
a.name === b.name &&
|
||||
a.type === b.type &&
|
||||
a.inputs.length === b.inputs.length
|
||||
);
|
||||
if (a.type !== b.type) {
|
||||
// cannot be the same if the types are different
|
||||
return false;
|
||||
}
|
||||
// just compare the formatted abi items (since they would be the same if the abi items are the same)
|
||||
return formatAbiItem(a) === formatAbiItem(b);
|
||||
});
|
||||
|
||||
return finalABIs;
|
||||
@@ -326,8 +331,8 @@ export const ContractFunctionsPanel: React.FC<ContractFunctionsPanelProps> = ({
|
||||
}, [fnsOrEvents]);
|
||||
|
||||
// Load state from the URL
|
||||
const router = useRouter();
|
||||
const _selector = router.query.selector;
|
||||
const searchParams = useSearchParams();
|
||||
const _selector = searchParams?.get("selector");
|
||||
const _item = _selector
|
||||
? fnsOrEvents.find((o) => {
|
||||
if (o.type === "function") {
|
||||
@@ -337,10 +342,10 @@ export const ContractFunctionsPanel: React.FC<ContractFunctionsPanelProps> = ({
|
||||
return null;
|
||||
})
|
||||
: undefined;
|
||||
|
||||
const [selectedFunction, setSelectedFunction] = useState<
|
||||
AbiFunction | AbiEvent
|
||||
>(_item ?? fnsOrEvents[0]);
|
||||
|
||||
// Set the active tab to Write or Read depends on the `_item`
|
||||
const _defaultTabIndex =
|
||||
_item &&
|
||||
@@ -479,7 +484,8 @@ const FunctionsOrEventsListItem: React.FC<FunctionsOrEventsListItemProps> = ({
|
||||
const isActive =
|
||||
selectedFunction?.name === fn.name &&
|
||||
selectedFunction.inputs?.length === fn.inputs?.length;
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const router = useDashboardRouter();
|
||||
return (
|
||||
<ListItem my={0.5}>
|
||||
<Button
|
||||
@@ -488,13 +494,11 @@ const FunctionsOrEventsListItem: React.FC<FunctionsOrEventsListItemProps> = ({
|
||||
opacity={isActive ? 1 : 0.65}
|
||||
onClick={() => {
|
||||
setSelectedFunction(fn);
|
||||
const path = router.asPath.split("?")[0];
|
||||
|
||||
// Only apply to the Explorer page
|
||||
if (path.endsWith("/explorer") && fn.type === "function") {
|
||||
if (pathname?.endsWith("/explorer") && fn.type === "function") {
|
||||
const selector = toFunctionSelector(fn);
|
||||
router.push({ pathname: path, query: { selector } }, undefined, {
|
||||
shallow: true,
|
||||
});
|
||||
router.push(`${pathname}?selector=${selector}`);
|
||||
}
|
||||
}}
|
||||
color="heading"
|
||||
|
||||
@@ -366,7 +366,8 @@ export const InteractiveAbiFunction: React.FC<InteractiveAbiFunctionProps> = ({
|
||||
<SolidityInput
|
||||
solidityName={item.key}
|
||||
solidityType={item.type}
|
||||
// solidityComponents={item.components}
|
||||
// @ts-expect-error - old types, need to update
|
||||
solidityComponents={item.components}
|
||||
{...form.register(`params.${index}.value`)}
|
||||
functionName={abiFunction.name}
|
||||
/>
|
||||
|
||||
@@ -15,6 +15,7 @@ import { RocketIcon, ShieldCheckIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { polygon } from "thirdweb/chains";
|
||||
import invariant from "tiny-invariant";
|
||||
import { moduleToBase64 } from "../../../app/(dashboard)/published-contract/utils/module-base-64";
|
||||
import { ContractPublisher, replaceDeployerAddress } from "../publisher";
|
||||
|
||||
interface ContractCardProps {
|
||||
@@ -71,14 +72,14 @@ function getContractUrl(
|
||||
const moudleUrl = new URLSearchParams();
|
||||
|
||||
for (const m of modules) {
|
||||
moudleUrl.append("module", btoa(JSON.stringify(m)));
|
||||
moudleUrl.append("module", moduleToBase64(m));
|
||||
}
|
||||
|
||||
if (titleOverride) {
|
||||
moudleUrl.append("displayName", titleOverride);
|
||||
}
|
||||
|
||||
pathName += `?${moudleUrl.toString()}`;
|
||||
pathName += moudleUrl.toString() ? `?${moudleUrl.toString()}` : "";
|
||||
return replaceDeployerAddress(pathName);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import { Flex, SimpleGrid } from "@chakra-ui/react";
|
||||
import { Children } from "react";
|
||||
import { Heading } from "tw-components";
|
||||
import type { ComponentWithChildren } from "types/component-with-children";
|
||||
|
||||
interface GettingStartedBoxProps {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const GettingStartedBox: ComponentWithChildren<
|
||||
GettingStartedBoxProps
|
||||
> = ({ title, children }) => {
|
||||
const lengthOfChildren = Math.min(Children.count(children), 3);
|
||||
return (
|
||||
<Flex
|
||||
flexDirection="column"
|
||||
gap={8}
|
||||
px={{ base: 3, md: 6 }}
|
||||
py={{ base: 3, md: 6 }}
|
||||
transition="all 0.2s"
|
||||
position="relative"
|
||||
overflow="hidden"
|
||||
>
|
||||
<Flex direction="column" gap={3}>
|
||||
<Heading textAlign="center" size="title.sm">
|
||||
{title}
|
||||
</Heading>
|
||||
</Flex>
|
||||
<SimpleGrid
|
||||
columns={{ base: 1, md: lengthOfChildren }}
|
||||
gap={6}
|
||||
transition="all 200ms"
|
||||
transformOrigin="top"
|
||||
overflow="hidden"
|
||||
>
|
||||
{children}
|
||||
</SimpleGrid>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
@@ -1,56 +0,0 @@
|
||||
import { Flex, type PropsOf } from "@chakra-ui/react";
|
||||
import { ChakraNextImage } from "components/Image";
|
||||
import type { StaticImageData } from "next/image";
|
||||
import { Card, Heading, Text, TrackedLink } from "tw-components";
|
||||
import type { ComponentWithChildren } from "types/component-with-children";
|
||||
|
||||
interface GettingStartedCardProps {
|
||||
title: string;
|
||||
description: string | JSX.Element;
|
||||
linkProps?: PropsOf<typeof TrackedLink>;
|
||||
icon: StaticImageData;
|
||||
}
|
||||
|
||||
export const GettingStartedCard: ComponentWithChildren<
|
||||
GettingStartedCardProps
|
||||
> = ({ title, description, linkProps, icon, children }) => {
|
||||
return (
|
||||
<Card as={Flex} flexDir="column" gap={3} p={6}>
|
||||
<Flex align="center" gap={2}>
|
||||
<ChakraNextImage
|
||||
flexShrink={0}
|
||||
boxSize={{ base: 8, md: 12 }}
|
||||
src={icon}
|
||||
alt=""
|
||||
/>
|
||||
<Heading size="title.sm">{title}</Heading>
|
||||
</Flex>
|
||||
<Text size="body.md">{description}</Text>
|
||||
|
||||
{linkProps && (
|
||||
<TrackedLink
|
||||
mt="auto"
|
||||
gap={1.5}
|
||||
transition="all 0.2s"
|
||||
_light={{
|
||||
color: "blue.500",
|
||||
_hover: {
|
||||
color: "blue.500",
|
||||
},
|
||||
}}
|
||||
_dark={{
|
||||
color: "blue.400",
|
||||
_hover: {
|
||||
color: "blue.500",
|
||||
},
|
||||
}}
|
||||
alignSelf="flex-end"
|
||||
alignItems="center"
|
||||
display="flex"
|
||||
{...linkProps}
|
||||
/>
|
||||
)}
|
||||
{children}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
export function DotNetIcon(props: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 456 456"
|
||||
className={props.className}
|
||||
role="presentation"
|
||||
>
|
||||
<rect width="456" height="456" fill="#512BD4" />
|
||||
<path
|
||||
d="M81.2738 291.333C78.0496 291.333 75.309 290.259 73.052 288.11C70.795 285.906 69.6665 283.289 69.6665 280.259C69.6665 277.173 70.795 274.529 73.052 272.325C75.309 270.121 78.0496 269.019 81.2738 269.019C84.5518 269.019 87.3193 270.121 89.5763 272.325C91.887 274.529 93.0424 277.173 93.0424 280.259C93.0424 283.289 91.887 285.906 89.5763 288.11C87.3193 290.259 84.5518 291.333 81.2738 291.333Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M210.167 289.515H189.209L133.994 202.406C132.597 200.202 131.441 197.915 130.528 195.546H130.044C130.474 198.081 130.689 203.508 130.689 211.827V289.515H112.149V171H134.477L187.839 256.043C190.096 259.57 191.547 261.994 192.192 263.316H192.514C191.977 260.176 191.708 254.859 191.708 247.365V171H210.167V289.515Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M300.449 289.515H235.561V171H297.87V187.695H254.746V221.249H294.485V237.861H254.746V272.903H300.449V289.515Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M392.667 187.695H359.457V289.515H340.272V187.695H307.143V171H392.667V187.695Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
export function ReactIcon(props: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
role="presentation"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 21 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={props.className}
|
||||
>
|
||||
<path
|
||||
d="M20.2926 9.00206C20.2926 7.66308 18.6091 6.39414 16.0281 5.60723C16.6237 2.98695 16.359 0.902266 15.1925 0.234836C14.9237 0.0782787 14.6093 0.00411987 14.266 0.00411987V0.922866C14.4563 0.922866 14.6093 0.959945 14.7376 1.02998C15.3001 1.35134 15.5441 2.57496 15.3539 4.14878C15.3084 4.53605 15.2339 4.94392 15.1429 5.36004C14.3322 5.16228 13.447 5.00984 12.5164 4.91096C11.958 4.14878 11.3789 3.45663 10.7957 2.851C12.1441 1.60265 13.4098 0.918746 14.2702 0.918746V0C13.1327 0 11.6436 0.807508 10.138 2.20829C8.63241 0.815748 7.14335 0.0164798 6.00588 0.0164798V0.935225C6.86208 0.935225 8.13192 1.61501 9.48035 2.85512C8.90127 3.46075 8.32219 4.14878 7.77207 4.91096C6.83727 5.00984 5.9521 5.16228 5.14139 5.36416C5.04626 4.95216 4.97594 4.55253 4.92631 4.16937C4.7319 2.59556 4.97181 1.37194 5.5302 1.04646C5.65429 0.972305 5.81561 0.939345 6.00588 0.939345V0.0205997C5.65843 0.0205997 5.34407 0.0947585 5.07108 0.251316C3.90878 0.918746 3.6482 2.99931 4.24796 5.61135C1.67519 6.40238 0 7.6672 0 9.00206C0 10.341 1.68346 11.61 4.2645 12.3969C3.66888 15.0172 3.9336 17.1019 5.10003 17.7693C5.36889 17.9258 5.68325 18 6.03069 18C7.16817 18 8.65723 17.1925 10.1628 15.7917C11.6684 17.1843 13.1575 17.9835 14.295 17.9835C14.6424 17.9835 14.9568 17.9094 15.2298 17.7528C16.3921 17.0854 16.6527 15.0048 16.0529 12.3928C18.6174 11.6059 20.2926 10.3369 20.2926 9.00206ZM14.9071 6.25406C14.7541 6.78553 14.5638 7.33348 14.3487 7.88144C14.1792 7.55184 14.0013 7.22225 13.8069 6.89265C13.6166 6.56306 13.4139 6.2417 13.2113 5.92859C13.7986 6.01511 14.3653 6.12222 14.9071 6.25406ZM13.0127 10.6418C12.6901 11.198 12.3592 11.7253 12.0159 12.2156C11.3996 12.2692 10.775 12.298 10.1463 12.298C9.52171 12.298 8.89713 12.2692 8.28496 12.2197C7.94165 11.7295 7.60661 11.2062 7.28398 10.6542C6.96963 10.1144 6.68422 9.56649 6.42364 9.01442C6.68009 8.46235 6.96963 7.91028 7.27985 7.37056C7.60248 6.81437 7.93338 6.28702 8.27669 5.79675C8.893 5.74319 9.51758 5.71435 10.1463 5.71435C10.7709 5.71435 11.3954 5.74319 12.0076 5.79263C12.3509 6.2829 12.686 6.80613 13.0086 7.35821C13.323 7.89792 13.6084 8.44587 13.8689 8.99794C13.6084 9.55001 13.3229 10.1021 13.0127 10.6418ZM14.3487 10.1062C14.5721 10.6583 14.7624 11.2103 14.9196 11.7459C14.3777 11.8778 13.8069 11.989 13.2154 12.0755C13.4181 11.7583 13.6208 11.4328 13.811 11.0991C14.0013 10.7695 14.1792 10.4358 14.3487 10.1062ZM10.1546 14.5022C9.76989 14.1067 9.38521 13.6658 9.00468 13.1838C9.37694 13.2003 9.75748 13.2126 10.1422 13.2126C10.531 13.2126 10.9156 13.2044 11.292 13.1838C10.9198 13.6658 10.5351 14.1067 10.1546 14.5022ZM7.07717 12.0755C6.48982 11.989 5.92315 11.8819 5.3813 11.7501C5.53434 11.2186 5.72461 10.6706 5.93969 10.1227C6.10928 10.4523 6.28714 10.7819 6.48155 11.1115C6.67595 11.4411 6.87449 11.7624 7.07717 12.0755ZM10.1339 3.50195C10.5186 3.89746 10.9032 4.33829 11.2838 4.82032C10.9115 4.80384 10.531 4.79149 10.1463 4.79149C9.75748 4.79149 9.37281 4.79973 8.9964 4.82032C9.36867 4.33829 9.75334 3.89746 10.1339 3.50195ZM7.07304 5.92859C6.87036 6.24582 6.66768 6.5713 6.47741 6.90501C6.28714 7.23461 6.10928 7.5642 5.93969 7.8938C5.71634 7.34173 5.52607 6.78965 5.36889 6.25406C5.91074 6.12634 6.48155 6.01511 7.07304 5.92859ZM3.3297 11.0867C1.86546 10.4646 0.918254 9.64889 0.918254 9.00206C0.918254 8.35523 1.86546 7.53536 3.3297 6.91737C3.68542 6.76493 4.07423 6.62898 4.47545 6.50126C4.71122 7.30877 5.02144 8.14923 5.40611 9.0103C5.02558 9.86725 4.71949 10.7036 4.48786 11.507C4.07837 11.3793 3.68956 11.2392 3.3297 11.0867ZM5.55502 16.9741C4.99249 16.6528 4.74845 15.4292 4.93872 13.8553C4.98422 13.4681 5.05867 13.0602 5.14967 12.6441C5.96038 12.8418 6.84554 12.9943 7.7762 13.0932C8.3346 13.8553 8.91368 14.5475 9.49689 15.1531C8.14847 16.4015 6.88277 17.0854 6.02242 17.0854C5.83629 17.0813 5.67911 17.0442 5.55502 16.9741ZM15.3663 13.8347C15.5607 15.4086 15.3208 16.6322 14.7624 16.9577C14.6383 17.0318 14.477 17.0648 14.2867 17.0648C13.4305 17.0648 12.1607 16.385 10.8122 15.1449C11.3913 14.5393 11.9704 13.8512 12.5205 13.089C13.4553 12.9902 14.3405 12.8377 15.1512 12.6358C15.2463 13.052 15.3208 13.4516 15.3663 13.8347ZM16.9587 11.0867C16.603 11.2392 16.2142 11.3751 15.813 11.5029C15.5772 10.6954 15.267 9.85489 14.8823 8.99382C15.2629 8.13687 15.5689 7.30053 15.8006 6.49714C16.2101 6.62486 16.5989 6.76493 16.9629 6.91737C18.4271 7.53948 19.3743 8.35523 19.3743 9.00206C19.3702 9.64889 18.423 10.4688 16.9587 11.0867Z"
|
||||
fill="#61DAFB"
|
||||
/>
|
||||
<path
|
||||
d="M10.1421 10.8849C11.1861 10.8849 12.0324 10.0419 12.0324 9.00207C12.0324 7.96223 11.1861 7.11926 10.1421 7.11926C9.09814 7.11926 8.25183 7.96223 8.25183 9.00207C8.25183 10.0419 9.09814 10.8849 10.1421 10.8849Z"
|
||||
fill="#61DAFB"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
export function TypeScriptIcon(props: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
role="presentation"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 48 48"
|
||||
className={props.className}
|
||||
>
|
||||
<rect width="36" height="36" x="6" y="6" fill="#1976d2" />
|
||||
<polygon
|
||||
fill="#fff"
|
||||
points="27.49,22 14.227,22 14.227,25.264 18.984,25.264 18.984,40 22.753,40 22.753,25.264 27.49,25.264"
|
||||
/>
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M39.194,26.084c0,0-1.787-1.192-3.807-1.192s-2.747,0.96-2.747,1.986 c0,2.648,7.381,2.383,7.381,7.712c0,8.209-11.254,4.568-11.254,4.568V35.22c0,0,2.152,1.622,4.733,1.622s2.483-1.688,2.483-1.92 c0-2.449-7.315-2.449-7.315-7.878c0-7.381,10.658-4.469,10.658-4.469L39.194,26.084z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
export function UnityIcon(props: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
role="presentation"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 48 48"
|
||||
className={props.className}
|
||||
>
|
||||
<path
|
||||
fill="#ccc"
|
||||
d="M36.272,36.185l-7.243-12.184l7.243-12.221l3.502,12.221L36.272,36.185z M20.126,34.994l-9.115-8.991 H25.52l7.254,12.153L20.126,34.994z M20.126,12.934l12.611-3.162l-7.254,12.231H10.975L20.126,12.934z M38.653,3.782L22.172,8.061 l-2.455,4.204l-4.947-0.037L2.75,24.001l12.019,11.737l4.948-0.037l2.455,4.204l16.481,4.315l4.426-16.176l-2.478-4.04l2.477-4.047 L38.653,3.782z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
export function UnrealIcon(props: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
role="presentation"
|
||||
fill="#ffffff"
|
||||
width="24px"
|
||||
height="25px"
|
||||
viewBox="0 0 32 32"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={props.className}
|
||||
>
|
||||
<path d="M16 0c-8.766 0-15.865 7.161-15.865 16s7.099 16 15.865 16c8.76 0 15.865-7.161 15.865-16s-7.104-16-15.87-16zM16 0.703c4.047 0 7.859 1.594 10.724 4.479 2.859 2.875 4.453 6.766 4.443 10.818 0 4.083-1.578 7.927-4.443 10.818-2.828 2.87-6.693 4.484-10.724 4.479-4.031 0.005-7.896-1.609-10.724-4.479-2.859-2.875-4.458-6.766-4.448-10.818 0-4.083 1.583-7.927 4.443-10.818 2.828-2.875 6.698-4.49 10.729-4.479zM15.203 6.333c-2.583 0.693-4.974 2.021-8.161 5.677s-2.583 6.677-2.583 6.677c0 0 0.88-2.078 2.995-4.266 1.005-1.036 1.75-1.385 2.266-1.385 0.458-0.026 0.844 0.344 0.844 0.802v7.422c0 0.734-0.474 0.896-0.911 0.885-0.37-0.005-0.714-0.135-0.714-0.135 2.172 3.156 7.37 3.599 7.37 3.599l2.281-2.438 0.052 0.047 2.089 1.781c3.823-2.271 5.667-6.479 5.667-6.479-1.708 1.802-2.792 2.224-3.438 2.224-0.573-0.005-0.797-0.339-0.797-0.339-0.031-0.156-0.083-2.417-0.104-4.677-0.021-2.339 0-4.682 0.115-4.688 0.661-1.24 2.766-3.74 2.766-3.74-3.932 0.776-6.073 3.354-6.073 3.354-0.635-0.5-1.927-0.417-1.927-0.417 0.604 0.333 1.208 1.302 1.208 2.104v7.896c0 0-1.318 1.161-2.333 1.161-0.604 0-0.974-0.328-1.177-0.599-0.078-0.104-0.146-0.219-0.198-0.344v-9.75c-0.141 0.104-0.313 0.161-0.484 0.167-0.219 0-0.443-0.109-0.594-0.427-0.115-0.24-0.188-0.599-0.188-1.125 0-1.797 2.031-2.99 2.031-2.99z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,250 +0,0 @@
|
||||
import { Spinner } from "@/components/ui/Spinner/Spinner";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { useDashboardRouter } from "@/lib/DashboardRouter";
|
||||
import { shareLink } from "@/lib/shareLink";
|
||||
import { Divider, Flex, GridItem, SimpleGrid } from "@chakra-ui/react";
|
||||
import { useAllVersions, useEns } from "components/contract-components/hooks";
|
||||
import { PublishedContract } from "components/contract-components/published-contract";
|
||||
import { useTrack } from "hooks/analytics/useTrack";
|
||||
import { replaceIpfsUrl } from "lib/sdk";
|
||||
import { ChevronsRightIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { usePathname, useSearchParams } from "next/navigation";
|
||||
import { useMemo } from "react";
|
||||
import { ContractDeployForm } from "../contract-components/contract-deploy-form";
|
||||
|
||||
export interface PublishWithVersionPageProps {
|
||||
author: string;
|
||||
contractName: string;
|
||||
version: string;
|
||||
isDeploy: boolean;
|
||||
}
|
||||
|
||||
export const PublishWithVersionPage: React.FC<PublishWithVersionPageProps> = ({
|
||||
author,
|
||||
contractName,
|
||||
version: _version,
|
||||
isDeploy,
|
||||
}) => {
|
||||
const ensQuery = useEns(author);
|
||||
const allVersions = useAllVersions(
|
||||
ensQuery.data?.address || undefined,
|
||||
contractName,
|
||||
);
|
||||
|
||||
const availableVersions = allVersions.data?.map(({ version: v }) => v) || [];
|
||||
const version = _version || availableVersions[0];
|
||||
const trackEvent = useTrack();
|
||||
const router = useDashboardRouter();
|
||||
const pathname = usePathname();
|
||||
const searchparams = useSearchParams();
|
||||
const modules = (searchparams?.getAll("module") || [])
|
||||
.map((m) => JSON.parse(atob(m)))
|
||||
.map((m) => ({
|
||||
publisherAddress: m.publisher,
|
||||
moduleName: m.moduleId,
|
||||
moduleVersion: m.version || "latest",
|
||||
}));
|
||||
|
||||
const stringifiedSearchParams = searchparams?.toString();
|
||||
|
||||
const publishedContract = useMemo(() => {
|
||||
return (
|
||||
allVersions.data?.find((v) => v.version === version) ||
|
||||
allVersions.data?.[0]
|
||||
);
|
||||
}, [allVersions?.data, version]);
|
||||
|
||||
const deployContractId = publishedContract?.metadataUri.replace(
|
||||
"ipfs://",
|
||||
"",
|
||||
);
|
||||
|
||||
const contractNameDisplay =
|
||||
publishedContract?.displayName || publishedContract?.name;
|
||||
|
||||
const versionSelector = (
|
||||
<Select
|
||||
value={version}
|
||||
onValueChange={(val) => {
|
||||
if (availableVersions.includes(val)) {
|
||||
trackEvent({
|
||||
category: "release-selector",
|
||||
action: "click",
|
||||
version_selected: val,
|
||||
});
|
||||
|
||||
const pathName =
|
||||
val === allVersions.data?.[0].version
|
||||
? `/${author}/${contractName}`
|
||||
: `/${author}/${contractName}/${val}`;
|
||||
|
||||
if (isDeploy) {
|
||||
router.push(
|
||||
`${pathName}/deploy${stringifiedSearchParams ? `?${stringifiedSearchParams}` : ""}`,
|
||||
);
|
||||
} else {
|
||||
router.push(
|
||||
pathName +
|
||||
(stringifiedSearchParams ? `?${stringifiedSearchParams}` : ""),
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="min-w-[180px] bg-transparent hover:bg-accent">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{availableVersions.map((v, idx) => {
|
||||
return (
|
||||
<SelectItem value={v || "latest"} key={v}>
|
||||
{v}
|
||||
{idx === 0 && (
|
||||
<span className="text-muted-foreground"> (latest) </span>
|
||||
)}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
|
||||
const contractInfo = (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex gap-4 items-center flex-1">
|
||||
{publishedContract?.logo && (
|
||||
<div className="rounded-xl p-2 border border-border shrink-0 items-center justify-center hidden md:flex">
|
||||
{/*eslint-disable-next-line @next/next/no-img-element*/}
|
||||
<img
|
||||
className="size-12"
|
||||
alt={publishedContract.name}
|
||||
src={replaceIpfsUrl(publishedContract.logo)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<h1 className="text-2xl md:text-3xl font-bold tracking-tight">
|
||||
{contractNameDisplay}
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{publishedContract?.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const modulesList = modules.length ? (
|
||||
<>
|
||||
<div className="mt-auto flex flex-row gap-2 flex-wrap">
|
||||
{modules.map((m) => (
|
||||
<Badge
|
||||
variant="default"
|
||||
key={m.publisherAddress + m.moduleName + m.moduleVersion}
|
||||
>
|
||||
{m.moduleName.split("ERC")[0]}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
) : null;
|
||||
|
||||
if (isDeploy) {
|
||||
const showLoading = !deployContractId || !publishedContract;
|
||||
return (
|
||||
<div className="flex-1">
|
||||
{/* header */}
|
||||
<div className="pb-6 flex flex-col gap-4 border-b border-border md:pb-8">
|
||||
<div className="flex flex-col md:flex-row justify-between gap-6 md:items-center">
|
||||
{contractInfo}
|
||||
<div className="flex flex-col-reverse md:flex-row gap-3">
|
||||
{versionSelector}
|
||||
<div className="flex gap-3">
|
||||
<Button asChild variant="outline">
|
||||
<Link
|
||||
href={`${pathname?.replace("/deploy", "") + (stringifiedSearchParams ? `?${stringifiedSearchParams}` : "")}`}
|
||||
target="_blank"
|
||||
>
|
||||
Contract
|
||||
</Link>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
shareLink({
|
||||
title: `Deploy ${contractNameDisplay}`,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Share
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{modulesList}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="pt-6 md:pt-10">
|
||||
{showLoading ? (
|
||||
<div className="h-[300px] flex items-center justify-center">
|
||||
<Spinner className="size-10" />
|
||||
</div>
|
||||
) : (
|
||||
<ContractDeployForm
|
||||
contractId={deployContractId}
|
||||
version={publishedContract.version || "latest"}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap={{ base: 6, md: 10 }}>
|
||||
{/* Header */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col md:flex-row justify-between gap-6">
|
||||
{contractInfo}
|
||||
<div className="flex gap-3">
|
||||
{versionSelector}
|
||||
<Button asChild variant="primary" className="gap-2">
|
||||
<Link
|
||||
href={`${pathname}/deploy${stringifiedSearchParams ? `?${stringifiedSearchParams}` : ""}`}
|
||||
>
|
||||
Deploy Now
|
||||
<ChevronsRightIcon className="size-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{modulesList}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<SimpleGrid columns={12} gap={{ base: 6, md: 10 }} w="full">
|
||||
<GridItem colSpan={12} display={{ base: "inherit", md: "none" }}>
|
||||
<Divider />
|
||||
</GridItem>
|
||||
{publishedContract && (
|
||||
<PublishedContract
|
||||
contract={publishedContract}
|
||||
walletOrEns={author}
|
||||
/>
|
||||
)}
|
||||
</SimpleGrid>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
@@ -1,47 +0,0 @@
|
||||
import { Icon, useClipboard } from "@chakra-ui/react";
|
||||
import { FiCheck, FiShare2 } from "react-icons/fi";
|
||||
import { toast } from "sonner";
|
||||
import { TrackedIconButton } from "tw-components";
|
||||
|
||||
export const ShareButton: React.FC<ShareData> = (props) => {
|
||||
const currentUrl = typeof window !== "undefined" ? window.location.href : "";
|
||||
const url = props.url || currentUrl;
|
||||
const shareData = { ...props, url };
|
||||
const { onCopy, hasCopied } = useClipboard(shareData.url);
|
||||
|
||||
const onShareClick = async () => {
|
||||
// if browser supports sharing use native sharing
|
||||
if (
|
||||
"canShare" in navigator &&
|
||||
navigator.canShare &&
|
||||
navigator.canShare(shareData)
|
||||
) {
|
||||
try {
|
||||
await navigator.share(shareData);
|
||||
return;
|
||||
} catch (err) {
|
||||
console.warn("failed to share", err);
|
||||
}
|
||||
} else {
|
||||
onCopy();
|
||||
toast.info("URL copied to clipboard");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TrackedIconButton
|
||||
variant="ghost"
|
||||
aria-label="copy-url"
|
||||
icon={
|
||||
<Icon
|
||||
boxSize={5}
|
||||
color={hasCopied ? "green.500" : "inherit"}
|
||||
as={hasCopied ? FiCheck : FiShare2}
|
||||
/>
|
||||
}
|
||||
category="released-contract"
|
||||
label="copy-url"
|
||||
onClick={onShareClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -30,7 +30,7 @@ const PLACEHOLDER_CHAIN: StoredChain = {
|
||||
testnet: false,
|
||||
};
|
||||
|
||||
export const CustomSDKContext: ComponentWithChildren<{
|
||||
const CustomSDKContext: ComponentWithChildren<{
|
||||
desiredChainId?: number;
|
||||
options?: SDKOptions;
|
||||
}> = ({ desiredChainId, options, children }) => {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { CopyTextButton } from "@/components/ui/CopyTextButton";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
|
||||
@@ -244,12 +244,12 @@ const WALLETS_SNIPPETS = [
|
||||
link: "https://portal.thirdweb.com/references/wallets/latest/SmartWallet",
|
||||
supportedLanguages: {
|
||||
javascript: `import { defineChain } from "thirdweb";
|
||||
import { embeddedWallet, smartWallet } from "thirdweb/wallets";
|
||||
import { inAppWallet, smartWallet } from "thirdweb/wallets";
|
||||
|
||||
const chain = defineChain({{chainId}});
|
||||
|
||||
// First, connect the personal wallet, which can be any wallet (metamask, embedded, etc.)
|
||||
const personalWallet = embeddedWallet();
|
||||
// First, connect the personal wallet, which can be any wallet (metamask, in-app, etc.)
|
||||
const personalWallet = inAppWallet();
|
||||
const peronalAccount = await personalWallet.connect({
|
||||
client,
|
||||
chain,
|
||||
@@ -618,12 +618,12 @@ export const CodeOverview: React.FC<CodeOverviewProps> = ({
|
||||
the{" "}
|
||||
<TrackedLink
|
||||
isExternal
|
||||
href="https://portal.thirdweb.com/references/wallets/latest/SmartWallet"
|
||||
href="https://portal.thirdweb.com/connect/account-abstraction/overview"
|
||||
category="accounts-page"
|
||||
label="wallet-sdk"
|
||||
color="primary.500"
|
||||
>
|
||||
Wallet SDK
|
||||
Connect SDK
|
||||
</TrackedLink>{" "}
|
||||
in your applications. This will ensure account contracts are
|
||||
deployed for your users only when they need it.
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
import { extractFunctionParamsFromAbi } from "@thirdweb-dev/sdk";
|
||||
import { polygon } from "thirdweb/chains";
|
||||
import { thirdwebClient } from "@/constants/client";
|
||||
import { fetchPublishedContractVersions } from "components/contract-components/fetch-contracts-with-versions";
|
||||
import { isAddress } from "thirdweb";
|
||||
import { resolveAddress } from "thirdweb/extensions/ens";
|
||||
import invariant from "tiny-invariant";
|
||||
import {
|
||||
fetchAllVersions,
|
||||
fetchContractPublishMetadataFromURI,
|
||||
} from "../../../../components/contract-components/hooks";
|
||||
import { getDashboardChainRpc } from "../../../../lib/rpc";
|
||||
import { getThirdwebSDK } from "../../../../lib/sdk";
|
||||
import type { ModuleMeta } from "./install-module-params";
|
||||
|
||||
export async function getModuleInstalledParams(ext: ModuleMeta) {
|
||||
const sdk = getThirdwebSDK(
|
||||
polygon.id,
|
||||
getDashboardChainRpc(polygon.id, undefined),
|
||||
);
|
||||
|
||||
// get all versions of the module
|
||||
const allPublishedModules = await fetchAllVersions(
|
||||
sdk,
|
||||
ext.publisherAddress,
|
||||
// if the publisher is an ens name, resolve it
|
||||
const publisherAddress = isAddress(ext.publisherAddress)
|
||||
? ext.publisherAddress
|
||||
: await resolveAddress({
|
||||
client: thirdwebClient,
|
||||
name: ext.publisherAddress,
|
||||
});
|
||||
const allPublishedModules = await fetchPublishedContractVersions(
|
||||
publisherAddress,
|
||||
ext.moduleName,
|
||||
);
|
||||
|
||||
@@ -33,18 +30,14 @@ export async function getModuleInstalledParams(ext: ModuleMeta) {
|
||||
`Module ${ext.moduleName} version "${ext.moduleVersion}" not found`,
|
||||
);
|
||||
|
||||
// get the ABI
|
||||
const contractMetadata = await fetchContractPublishMetadataFromURI(
|
||||
publishedModule.metadataUri,
|
||||
);
|
||||
|
||||
invariant(contractMetadata.abi, `ABI not found for module ${ext.moduleName}`);
|
||||
invariant(publishedModule.abi, `ABI not found for module ${ext.moduleName}`);
|
||||
|
||||
// get encodeBytesOnInstall function's install params
|
||||
const installParamsForModule = extractFunctionParamsFromAbi(
|
||||
contractMetadata.abi,
|
||||
"encodeBytesOnInstall",
|
||||
);
|
||||
|
||||
const installParamsForModule =
|
||||
publishedModule.abi
|
||||
.filter((f) => f.type === "function")
|
||||
.find((f) => f.name === "encodeBytesOnInstall")?.inputs || [];
|
||||
|
||||
return {
|
||||
moduleName: ext.moduleName,
|
||||
|
||||
@@ -56,7 +56,10 @@ export function ModuleInstallParams(props: {
|
||||
<FormLabel> {param.name}</FormLabel>
|
||||
<SolidityInput
|
||||
solidityType={param.type}
|
||||
solidityComponents={param.components}
|
||||
// @ts-expect-error - old types, need to update
|
||||
solidityComponents={
|
||||
"components" in param ? param.components : undefined
|
||||
}
|
||||
variant="filled"
|
||||
{...form.register(formFieldKey)}
|
||||
isDisabled={props.disableInputs}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import { Flex, Image, List, ListItem } from "@chakra-ui/react";
|
||||
import type { Abi } from "abitype";
|
||||
import { useContractEnabledExtensions } from "components/contract-components/hooks";
|
||||
import { Heading, Text, TrackedLink } from "tw-components";
|
||||
|
||||
type ExtensionsProps = {
|
||||
abi: Abi;
|
||||
};
|
||||
|
||||
export const Extensions: React.FC<ExtensionsProps> = ({ abi }) => {
|
||||
const enabledExtensions = useContractEnabledExtensions(abi);
|
||||
|
||||
return (
|
||||
<Flex flexDir="column" gap={4}>
|
||||
<Heading as="h4" size="title.sm">
|
||||
Extensions
|
||||
</Heading>
|
||||
<List as={Flex} flexDir="column" gap={3}>
|
||||
{enabledExtensions.length ? (
|
||||
enabledExtensions.map((extension) => (
|
||||
<ListItem key={extension.name}>
|
||||
<Flex gap={2} alignItems="center">
|
||||
<Image
|
||||
src="/assets/dashboard/extension-check.svg"
|
||||
alt="Extension detected"
|
||||
objectFit="contain"
|
||||
mb="2px"
|
||||
/>
|
||||
<Text size="label.md">
|
||||
<TrackedLink
|
||||
href={`https://portal.thirdweb.com/solidity/extensions/${extension.docLinks.contracts}`}
|
||||
isExternal
|
||||
category="extension"
|
||||
label={extension.name}
|
||||
>
|
||||
{extension.name}
|
||||
</TrackedLink>
|
||||
</Text>
|
||||
</Flex>
|
||||
</ListItem>
|
||||
))
|
||||
) : (
|
||||
<ListItem>
|
||||
<Text size="body.md" fontStyle="italic">
|
||||
No extensions detected
|
||||
</Text>
|
||||
</ListItem>
|
||||
)}
|
||||
</List>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
@@ -1,48 +0,0 @@
|
||||
import { Flex, Progress } from "@chakra-ui/react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Text } from "tw-components";
|
||||
|
||||
export type UploadProgressEvent = {
|
||||
progress: number;
|
||||
total: number;
|
||||
};
|
||||
|
||||
interface ProgressBoxProps {
|
||||
progress: UploadProgressEvent;
|
||||
}
|
||||
|
||||
export const ProgressBox: React.FC<ProgressBoxProps> = ({ progress }) => {
|
||||
const isFinished = progress.progress >= progress.total;
|
||||
const [takingLong, setTakingLong] = useState(false);
|
||||
|
||||
// legitimate use-case
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
useEffect(() => {
|
||||
if (isFinished) {
|
||||
const t = setTimeout(() => {
|
||||
setTakingLong(true);
|
||||
}, 10000);
|
||||
|
||||
return () => {
|
||||
clearTimeout(t);
|
||||
};
|
||||
}
|
||||
}, [isFinished]);
|
||||
|
||||
return (
|
||||
<Flex w="full" direction="column">
|
||||
<Progress
|
||||
borderRadius="md"
|
||||
mt="12px"
|
||||
transition="500ms ease"
|
||||
value={(progress.progress / progress.total) * 100}
|
||||
size="sm"
|
||||
/>
|
||||
{takingLong && progress.progress !== 0 && (
|
||||
<Text size="body.sm" textAlign="center" mt={3}>
|
||||
This may take a while.
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
@@ -1,10 +1,13 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { getSingleQueryValue } from "utils/router";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import {} from "next/router";
|
||||
|
||||
/**
|
||||
* @deprecated use `useSearchParams` from `next/navigation` instead
|
||||
*/
|
||||
export function useSingleQueryParam<T extends string>(
|
||||
key: string,
|
||||
): T | undefined {
|
||||
const { query } = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
return getSingleQueryValue(query, key) as T;
|
||||
return searchParams?.get(key) as T | undefined;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export function getAbsoluteUrl(): string {
|
||||
return "https://thirdweb.com";
|
||||
}
|
||||
|
||||
function setOverrides() {
|
||||
export function setOverrides() {
|
||||
if (getVercelEnv() === "production") {
|
||||
// no overrides
|
||||
return;
|
||||
|
||||
@@ -88,7 +88,7 @@ export async function middleware(request: NextRequest) {
|
||||
}
|
||||
// if we have more than 1 path part, we're in the <address>/<slug> case -> publish page
|
||||
if (paths.length > 1) {
|
||||
return rewrite(request, `/publish${pathname}`);
|
||||
return rewrite(request, `/published-contract${pathname}`);
|
||||
}
|
||||
}
|
||||
// END /<address>/... case
|
||||
|
||||
@@ -101,13 +101,6 @@ function fromUrl(type: keyof OgProps, url: URL): OgProps[typeof type] {
|
||||
}
|
||||
}
|
||||
|
||||
export const PublishedContractOG = {
|
||||
toUrl: (props: OgProps["publishedContract"]) =>
|
||||
toUrl("publishedContract", props),
|
||||
fromUrl: (url: URL) =>
|
||||
fromUrl("publishedContract", url) as OgProps["publishedContract"],
|
||||
};
|
||||
|
||||
export const ProfileOG = {
|
||||
toUrl: (props: OgProps["profile"]) => toUrl("profile", props),
|
||||
fromUrl: (url: URL) => fromUrl("profile", url) as OgProps["profile"],
|
||||
|
||||
@@ -174,19 +174,12 @@ export enum PageId {
|
||||
// example: thirdweb.com/jns.eth
|
||||
Profile = "profile",
|
||||
|
||||
// thirdweb.com/:wallet/:contractId
|
||||
// example: thirdweb.com/jns.eth/PermissionedERC721A
|
||||
PublishedContract = "published-contract",
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// "deploy" product pages
|
||||
// ---------------------------------------------------------------------------
|
||||
// thirdweb.com/contracts/deploy
|
||||
DeployMultiple = "deploy-multiple-contracts",
|
||||
|
||||
// thirdweb.com/contracts/deploy/:id
|
||||
DeploySingle = "deploy-single-contract",
|
||||
|
||||
// thirdweb.com/:network/:contractAddress (evm)
|
||||
// example: thirdweb.com/goerli/0x2eaDAa60dBB74Ead3E20b23E4C5A0Dd789932846
|
||||
DeployedContract = "deployed-contract",
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
import {
|
||||
Box,
|
||||
Center,
|
||||
Divider,
|
||||
Flex,
|
||||
Icon,
|
||||
IconButton,
|
||||
Image,
|
||||
LinkBox,
|
||||
LinkOverlay,
|
||||
Skeleton,
|
||||
} from "@chakra-ui/react";
|
||||
import { AppLayout } from "components/app-layouts/app";
|
||||
import { ContractDeployForm } from "components/contract-components/contract-deploy-form";
|
||||
import {
|
||||
useContractEnabledExtensions,
|
||||
useContractPublishMetadataFromURI,
|
||||
} from "components/contract-components/hooks";
|
||||
import { ContractIdImage } from "components/contract-components/shared/contract-id-image";
|
||||
import { useTrack } from "hooks/analytics/useTrack";
|
||||
import { useSingleQueryParam } from "hooks/useQueryParam";
|
||||
import { useRouter } from "next/router";
|
||||
import { PageId } from "page-id";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { FiArrowLeft, FiExternalLink } from "react-icons/fi";
|
||||
import { Card, Heading, LinkButton, Text, TrackedLink } from "tw-components";
|
||||
import type { ThirdwebNextPage } from "utils/types";
|
||||
|
||||
const ContractDeployDetailPage: ThirdwebNextPage = () => {
|
||||
const router = useRouter();
|
||||
const trackEvent = useTrack();
|
||||
const contractId = useSingleQueryParam("contractId");
|
||||
const from = useSingleQueryParam("from");
|
||||
const publishMetadataQuery = useContractPublishMetadataFromURI(
|
||||
contractId || "",
|
||||
);
|
||||
|
||||
const hasTrackedImpression = useRef<boolean>(false);
|
||||
// TODO: find better way to do impression tracking
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
useEffect(() => {
|
||||
if (publishMetadataQuery.data && !hasTrackedImpression.current) {
|
||||
hasTrackedImpression.current = true;
|
||||
trackEvent({
|
||||
action: "impression",
|
||||
category: "deploy",
|
||||
analytics: publishMetadataQuery.data.analytics,
|
||||
});
|
||||
}
|
||||
}, [publishMetadataQuery.data, trackEvent]);
|
||||
|
||||
const enabledFeatures = useContractEnabledExtensions(
|
||||
// @ts-expect-error - "wrong" ABI data type
|
||||
publishMetadataQuery.data?.abi,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex direction="column" as="section" gap={4}>
|
||||
<Flex align="center" gap={4} justify="space-between" as="header">
|
||||
<Flex align="center" gap={2}>
|
||||
{from && (
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
aria-label="back"
|
||||
onClick={() => router.back()}
|
||||
icon={<Icon boxSize={6} as={FiArrowLeft} />}
|
||||
/>
|
||||
)}
|
||||
{contractId && (
|
||||
<ContractIdImage boxSize={12} contractId={contractId} />
|
||||
)}
|
||||
<Flex direction="column" gap={1} align="flex-start">
|
||||
<Skeleton isLoaded={publishMetadataQuery.isSuccess}>
|
||||
<Heading size="title.md">
|
||||
{publishMetadataQuery.isSuccess
|
||||
? publishMetadataQuery.data?.name
|
||||
: "testing testing testing"}
|
||||
</Heading>
|
||||
</Skeleton>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Divider />
|
||||
<Flex gap={12} direction="column" as="main">
|
||||
{contractId && (
|
||||
<Flex gap={4} direction="column">
|
||||
<Card p={{ base: 6, md: 10 }}>
|
||||
<Flex direction="column" gap={8}>
|
||||
{enabledFeatures.length > 0 && (
|
||||
<Flex direction="column" gap={4}>
|
||||
<Box>
|
||||
<Heading size="subtitle.md">
|
||||
<TrackedLink
|
||||
href="https://portal.thirdweb.com/contracts/build/overview"
|
||||
category="extensions-deploy"
|
||||
label="header"
|
||||
isExternal
|
||||
>
|
||||
<Flex alignItems="center" gap={2}>
|
||||
Detected Extensions
|
||||
<Icon as={FiExternalLink} />
|
||||
</Flex>
|
||||
</TrackedLink>
|
||||
</Heading>
|
||||
<Text>
|
||||
These extensions will automatically be available for
|
||||
this contract in the dashboard as well as in the SDKs.
|
||||
</Text>
|
||||
</Box>
|
||||
<Flex gap={2} flexWrap="wrap">
|
||||
{enabledFeatures.map((feature) => (
|
||||
<EnabledFeature
|
||||
key={feature.name}
|
||||
feature={feature}
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
</Flex>
|
||||
)}
|
||||
<ContractDeployForm contractId={contractId} />
|
||||
</Flex>
|
||||
</Card>
|
||||
</Flex>
|
||||
)}
|
||||
<Center>
|
||||
<LinkButton
|
||||
variant="outline"
|
||||
isExternal
|
||||
href="https://portal.thirdweb.com/contracts/build/overview"
|
||||
>
|
||||
Learn about thirdweb extensions
|
||||
</LinkButton>
|
||||
</Center>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// const AppLayout = dynamic(
|
||||
// async () => (await import("components/app-layouts/app")).AppLayout,
|
||||
// );
|
||||
|
||||
ContractDeployDetailPage.getLayout = function getLayout(page, props) {
|
||||
return <AppLayout {...props}>{page}</AppLayout>;
|
||||
};
|
||||
|
||||
ContractDeployDetailPage.pageId = PageId.DeploySingle;
|
||||
|
||||
export default ContractDeployDetailPage;
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: This will be removed once we fully migrate to v5
|
||||
type FeatureWithEnabled = any & {
|
||||
features: Record<string, FeatureWithEnabled>;
|
||||
enabled: boolean;
|
||||
};
|
||||
interface EnabledFeatureProps {
|
||||
feature: FeatureWithEnabled;
|
||||
}
|
||||
|
||||
const EnabledFeature: React.FC<EnabledFeatureProps> = ({ feature }) => {
|
||||
const trackEvent = useTrack();
|
||||
return (
|
||||
<Card
|
||||
overflow="hidden"
|
||||
py={2}
|
||||
as={LinkBox}
|
||||
_hover={{ opacity: 0.8 }}
|
||||
borderRadius="full"
|
||||
>
|
||||
<Flex gap={2} align="center" justify="space-between">
|
||||
<Flex gap={2} align="center">
|
||||
<Image
|
||||
boxSize={4}
|
||||
src="/assets/dashboard/extension-check.svg"
|
||||
alt=""
|
||||
/>
|
||||
<LinkOverlay
|
||||
href={`https://portal.thirdweb.com/solidity/extensions/${feature.docLinks.contracts}`}
|
||||
isExternal
|
||||
onClick={() =>
|
||||
trackEvent({
|
||||
category: "extensions-deploy",
|
||||
action: "click",
|
||||
label: feature.name,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Heading textAlign="left" size="subtitle.sm">
|
||||
{feature.name}
|
||||
</Heading>
|
||||
</LinkOverlay>
|
||||
<Icon as={FiExternalLink} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -1,173 +0,0 @@
|
||||
import { Flex, Spinner } from "@chakra-ui/react";
|
||||
import {
|
||||
type DehydratedState,
|
||||
QueryClient,
|
||||
dehydrate,
|
||||
} from "@tanstack/react-query";
|
||||
import { AppLayout } from "components/app-layouts/app";
|
||||
import {
|
||||
ensQuery,
|
||||
fetchAllVersions,
|
||||
fetchContractPublishMetadataFromURI,
|
||||
fetchPublishedContractInfo,
|
||||
publisherProfileQuery,
|
||||
} from "components/contract-components/hooks";
|
||||
import {
|
||||
PublishWithVersionPage,
|
||||
type PublishWithVersionPageProps,
|
||||
} from "components/pages/publish";
|
||||
import { PublisherSDKContext } from "contexts/custom-sdk-context";
|
||||
import { getDashboardChainRpc } from "lib/rpc";
|
||||
import { getThirdwebSDK } from "lib/sdk";
|
||||
import type { GetStaticPaths, GetStaticProps } from "next";
|
||||
import { PageId } from "page-id";
|
||||
import { polygon } from "thirdweb/chains";
|
||||
import { getAddress, isAddress } from "thirdweb/utils";
|
||||
import type { ThirdwebNextPage } from "utils/types";
|
||||
import { ContractsSidebarLayout } from "../../core-ui/sidebar/contracts";
|
||||
|
||||
type PublishPageProps = {
|
||||
dehydratedState: DehydratedState;
|
||||
} & PublishWithVersionPageProps;
|
||||
|
||||
const PublishPage: ThirdwebNextPage = (props: PublishPageProps) => {
|
||||
return (
|
||||
<PublisherSDKContext>
|
||||
<PublishWithVersionPage
|
||||
author={props.author}
|
||||
contractName={props.contractName}
|
||||
version={props.version}
|
||||
isDeploy={props.isDeploy}
|
||||
/>
|
||||
</PublisherSDKContext>
|
||||
);
|
||||
};
|
||||
|
||||
PublishPage.pageId = PageId.PublishedContract;
|
||||
|
||||
PublishPage.getLayout = (page, props: PublishPageProps) => {
|
||||
return (
|
||||
<AppLayout
|
||||
dehydratedState={props.dehydratedState}
|
||||
pageContainerClassName="!max-w-full !px-0"
|
||||
mainClassName="!pt-0"
|
||||
>
|
||||
<ContractsSidebarLayout mobileSidebarClassName="hidden">
|
||||
{page}
|
||||
</ContractsSidebarLayout>
|
||||
</AppLayout>
|
||||
);
|
||||
};
|
||||
|
||||
PublishPage.fallback = (
|
||||
<AppLayout>
|
||||
<Flex h="100%" justifyContent="center" alignItems="center">
|
||||
<Spinner size="xl" />
|
||||
</Flex>
|
||||
</AppLayout>
|
||||
);
|
||||
|
||||
export default PublishPage;
|
||||
|
||||
export const getStaticProps: GetStaticProps<PublishPageProps> = async (ctx) => {
|
||||
const paths = ctx.params?.paths as string[];
|
||||
const [authorAddress, contractName, versionOrDeploy = "", deployStr] = paths;
|
||||
let version = "";
|
||||
let isDeploy = false;
|
||||
|
||||
if (versionOrDeploy === "deploy") {
|
||||
isDeploy = true;
|
||||
version = "";
|
||||
} else if (versionOrDeploy && deployStr === "deploy") {
|
||||
version = versionOrDeploy;
|
||||
isDeploy = true;
|
||||
}
|
||||
|
||||
if (!contractName) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
const polygonSdk = getThirdwebSDK(
|
||||
polygon.id,
|
||||
getDashboardChainRpc(polygon.id, undefined),
|
||||
);
|
||||
|
||||
const lowercaseAddress = authorAddress.toLowerCase();
|
||||
const checksummedAddress = isAddress(lowercaseAddress)
|
||||
? getAddress(lowercaseAddress)
|
||||
: lowercaseAddress;
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
const { address, ensName } = await queryClient.fetchQuery(
|
||||
ensQuery(checksummedAddress),
|
||||
);
|
||||
|
||||
if (!address) {
|
||||
return {
|
||||
notFound: true,
|
||||
} as const;
|
||||
}
|
||||
|
||||
// TODO get the latest version instead of all versions
|
||||
// OR wait till contract upgrade to have a faster call for this
|
||||
|
||||
let allVersions: ReturnType<typeof fetchAllVersions> extends Promise<infer X>
|
||||
? X
|
||||
: never = [];
|
||||
try {
|
||||
allVersions = await queryClient.fetchQuery(
|
||||
["all-releases", address, contractName],
|
||||
() => fetchAllVersions(polygonSdk, address, contractName),
|
||||
);
|
||||
} catch {
|
||||
return {
|
||||
notFound: true,
|
||||
} as const;
|
||||
}
|
||||
|
||||
const publishedContract =
|
||||
allVersions.find((v) => v.version === version) || allVersions[0];
|
||||
|
||||
const ensQueries = [queryClient.prefetchQuery(ensQuery(checksummedAddress))];
|
||||
if (ensName) {
|
||||
ensQueries.push(queryClient.prefetchQuery(ensQuery(ensName)));
|
||||
}
|
||||
|
||||
// this can be very slow
|
||||
await Promise.all([
|
||||
...ensQueries,
|
||||
queryClient.prefetchQuery(["released-contract", publishedContract], () =>
|
||||
fetchPublishedContractInfo(polygonSdk, publishedContract),
|
||||
),
|
||||
queryClient.prefetchQuery(
|
||||
["publish-metadata", publishedContract.metadataUri],
|
||||
() => fetchContractPublishMetadataFromURI(publishedContract.metadataUri),
|
||||
),
|
||||
queryClient.prefetchQuery(
|
||||
publisherProfileQuery(publishedContract.publisher),
|
||||
),
|
||||
]);
|
||||
|
||||
const props: PublishPageProps = {
|
||||
dehydratedState: dehydrate(queryClient),
|
||||
author: checksummedAddress,
|
||||
contractName,
|
||||
version,
|
||||
isDeploy,
|
||||
};
|
||||
|
||||
return {
|
||||
props,
|
||||
};
|
||||
};
|
||||
|
||||
// generate the explore contracts at build time
|
||||
// others will be generated at runtime via fallback
|
||||
export const getStaticPaths: GetStaticPaths = async () => {
|
||||
return {
|
||||
fallback: true,
|
||||
paths: [],
|
||||
};
|
||||
};
|
||||
@@ -1,5 +1,8 @@
|
||||
import type { ParsedUrlQuery } from "node:querystring";
|
||||
|
||||
/**
|
||||
* @deprecated use `SearchParams` instead
|
||||
*/
|
||||
export function getSingleQueryValue(
|
||||
query: ParsedUrlQuery,
|
||||
key: string,
|
||||
|
||||
@@ -102,6 +102,7 @@ function SocialProfileCard({ profile }: { profile: SocialProfile }) {
|
||||
return (
|
||||
<div className="flex gap-4 bg-background border rounded-lg p-4 shadow-md">
|
||||
{avatar ? (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img src={avatar} alt={profile.name} className="size-10 rounded-full" />
|
||||
) : (
|
||||
<div className="size-10 rounded-full bg-muted-foreground" />
|
||||
|
||||
@@ -305,7 +305,7 @@
|
||||
"dev": "tsc --project ./tsconfig.build.json --module es2020 --outDir ./dist/esm --watch",
|
||||
"dev:cjs": "printf '{\"type\":\"commonjs\"}' > ./dist/cjs/package.json && tsc --noCheck --project ./tsconfig.build.json --module commonjs --outDir ./dist/cjs --verbatimModuleSyntax false --watch",
|
||||
"dev:esm": "printf '{\"type\": \"module\",\"sideEffects\":false}' > ./dist/esm/package.json && tsc --noCheck --project ./tsconfig.build.json --module es2020 --outDir ./dist/esm --watch",
|
||||
"build": "pnpm clean && pnpm build:cjs && pnpm build:esm && pnpm build:types",
|
||||
"build": "pnpm clean && pnpm build:types && pnpm build:cjs && pnpm build:esm",
|
||||
"build:cjs": "tsc --noCheck --project ./tsconfig.build.json --module commonjs --outDir ./dist/cjs --verbatimModuleSyntax false && printf '{\"type\":\"commonjs\"}' > ./dist/cjs/package.json",
|
||||
"build:esm": "tsc --noCheck --project ./tsconfig.build.json --module es2020 --outDir ./dist/esm && printf '{\"type\": \"module\",\"sideEffects\":false}' > ./dist/esm/package.json",
|
||||
"build:types": "tsc --project ./tsconfig.build.json --module esnext --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap",
|
||||
|
||||
@@ -5,12 +5,14 @@ import { encodeAbiParameters } from "../../utils/abi/encodeAbiParameters.js";
|
||||
import { normalizeFunctionParams } from "../../utils/abi/normalizeFunctionParams.js";
|
||||
import { computeDeploymentAddress } from "../../utils/any-evm/compute-deployment-address.js";
|
||||
import { computeDeploymentInfoFromBytecode } from "../../utils/any-evm/compute-published-contract-deploy-info.js";
|
||||
import { isContractDeployed } from "../../utils/bytecode/is-contract-deployed.js";
|
||||
import { ensureBytecodePrefix } from "../../utils/bytecode/prefix.js";
|
||||
import { concatHex } from "../../utils/encoding/helpers/concat-hex.js";
|
||||
import { type Hex, isHex } from "../../utils/encoding/hex.js";
|
||||
import type { Prettify } from "../../utils/type-utils.js";
|
||||
import type { ClientAndChain } from "../../utils/types.js";
|
||||
import type { Account } from "../../wallets/interfaces/wallet.js";
|
||||
import { getContract } from "../contract.js";
|
||||
|
||||
/**
|
||||
* @extension DEPLOY
|
||||
@@ -122,6 +124,22 @@ export async function deployContract(
|
||||
if (options.salt !== undefined) {
|
||||
// Deploy with CREATE2 if salt is provided
|
||||
const info = await computeDeploymentInfoFromBytecode(options);
|
||||
const address = computeDeploymentAddress({
|
||||
bytecode: options.bytecode,
|
||||
encodedArgs: info.encodedArgs,
|
||||
create2FactoryAddress: info.create2FactoryAddress,
|
||||
salt: options.salt,
|
||||
});
|
||||
const isDeployed = await isContractDeployed(
|
||||
getContract({
|
||||
client: options.client,
|
||||
chain: options.chain,
|
||||
address,
|
||||
}),
|
||||
);
|
||||
if (isDeployed) {
|
||||
throw new Error(`Contract already deployed at address: ${address}`);
|
||||
}
|
||||
await sendAndConfirmTransaction({
|
||||
account: options.account,
|
||||
transaction: prepareTransaction({
|
||||
@@ -131,12 +149,7 @@ export async function deployContract(
|
||||
data: info.initBytecodeWithsalt,
|
||||
}),
|
||||
});
|
||||
return computeDeploymentAddress({
|
||||
bytecode: options.bytecode,
|
||||
encodedArgs: info.encodedArgs,
|
||||
create2FactoryAddress: info.create2FactoryAddress,
|
||||
salt: options.salt,
|
||||
});
|
||||
return address;
|
||||
}
|
||||
|
||||
const deployTx = prepareDirectDeployTransaction(options);
|
||||
|
||||
@@ -46,4 +46,4 @@ export {
|
||||
type PrepareDirectDeployTransactionOptions,
|
||||
} from "../contract/deployment/deploy-with-abi.js";
|
||||
export { computePublishedContractAddress } from "../utils/any-evm/compute-published-contract-address.js";
|
||||
export { getRequiredTransactionCount } from "../extensions/prebuilts/get-required-transactions.js";
|
||||
export { getRequiredTransactions } from "../extensions/prebuilts/get-required-transactions.js";
|
||||
|
||||
@@ -18,6 +18,7 @@ import type { FetchDeployMetadataResult } from "../../utils/any-evm/deploy-metad
|
||||
import { isZkSyncChain } from "../../utils/any-evm/zksync/isZkSyncChain.js";
|
||||
import type { Hex } from "../../utils/encoding/hex.js";
|
||||
import type { Account } from "../../wallets/interfaces/wallet.js";
|
||||
import { getAllDefaultConstructorParamsForImplementation } from "./get-required-transactions.js";
|
||||
|
||||
/**
|
||||
* @extension DEPLOY
|
||||
@@ -168,7 +169,12 @@ export async function deployContractfromDeployMetadata(
|
||||
client,
|
||||
account,
|
||||
contractId: deployMetadata.name,
|
||||
constructorParams: implementationConstructorParams,
|
||||
constructorParams:
|
||||
implementationConstructorParams ||
|
||||
(await getAllDefaultConstructorParamsForImplementation({
|
||||
chain,
|
||||
client,
|
||||
})),
|
||||
publisher: deployMetadata.publisher,
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
|
||||
import { CLEAN_ANVIL_CHAIN } from "../../../test/src/chains.js";
|
||||
import { TEST_CLIENT } from "../../../test/src/test-clients.js";
|
||||
import { fetchPublishedContractMetadata } from "../../contract/deployment/publisher.js";
|
||||
import { getRequiredTransactionCount } from "./get-required-transactions.js";
|
||||
import { getRequiredTransactions } from "./get-required-transactions.js";
|
||||
|
||||
describe.runIf(process.env.TW_SECRET_KEY)(
|
||||
"getRequiredTransactions",
|
||||
@@ -11,47 +11,49 @@ describe.runIf(process.env.TW_SECRET_KEY)(
|
||||
},
|
||||
() => {
|
||||
it("should count transactions for a published direct deploy contract", async () => {
|
||||
const metadata = await fetchPublishedContractMetadata({
|
||||
const deployMetadata = await fetchPublishedContractMetadata({
|
||||
client: TEST_CLIENT,
|
||||
contractId: "AccountFactory",
|
||||
});
|
||||
const results = await getRequiredTransactionCount({
|
||||
const results = await getRequiredTransactions({
|
||||
client: TEST_CLIENT,
|
||||
chain: CLEAN_ANVIL_CHAIN,
|
||||
metadata,
|
||||
deployMetadata,
|
||||
});
|
||||
expect(results.length).toBe(1);
|
||||
});
|
||||
|
||||
it("should count transactions for a published autofactory contract", async () => {
|
||||
const metadata = await fetchPublishedContractMetadata({
|
||||
const deployMetadata = await fetchPublishedContractMetadata({
|
||||
client: TEST_CLIENT,
|
||||
contractId: "Airdrop",
|
||||
});
|
||||
const results = await getRequiredTransactionCount({
|
||||
const results = await getRequiredTransactions({
|
||||
client: TEST_CLIENT,
|
||||
chain: CLEAN_ANVIL_CHAIN,
|
||||
metadata,
|
||||
deployMetadata,
|
||||
});
|
||||
expect(results.length).toBe(4);
|
||||
});
|
||||
|
||||
it("should count transactions for a modular contract with modules", async () => {
|
||||
const metadata = await fetchPublishedContractMetadata({
|
||||
const deployMetadata = await fetchPublishedContractMetadata({
|
||||
client: TEST_CLIENT,
|
||||
contractId: "ERC721CoreInitializable",
|
||||
});
|
||||
const results = await getRequiredTransactionCount({
|
||||
const results = await getRequiredTransactions({
|
||||
client: TEST_CLIENT,
|
||||
chain: CLEAN_ANVIL_CHAIN,
|
||||
metadata,
|
||||
deployMetadata,
|
||||
modules: await Promise.all(
|
||||
["ClaimableERC721", "BatchMetadataERC721", "RoyaltyERC721"].map(
|
||||
(contractId) =>
|
||||
fetchPublishedContractMetadata({
|
||||
client: TEST_CLIENT,
|
||||
contractId,
|
||||
}),
|
||||
}).then((m) => ({
|
||||
deployMetadata: m,
|
||||
})),
|
||||
),
|
||||
),
|
||||
});
|
||||
|
||||
@@ -5,6 +5,8 @@ import { getDeployedInfraContract } from "../../contract/deployment/utils/infra.
|
||||
import { getDeployedInfraContractFromMetadata } from "../../contract/deployment/utils/infra.js";
|
||||
import { computePublishedContractAddress } from "../../utils/any-evm/compute-published-contract-address.js";
|
||||
import type { FetchDeployMetadataResult } from "../../utils/any-evm/deploy-metadata.js";
|
||||
import { isZkSyncChain } from "../../utils/any-evm/zksync/isZkSyncChain.js";
|
||||
import type { DeployContractfromDeployMetadataOptions } from "./deploy-published.js";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@@ -13,6 +15,7 @@ export type DeployTransactionType =
|
||||
| "infra"
|
||||
| "implementation"
|
||||
| "module"
|
||||
| "extension"
|
||||
| "proxy";
|
||||
|
||||
/**
|
||||
@@ -26,36 +29,42 @@ export type DeployTransactionResult = {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export async function getRequiredTransactionCount(options: {
|
||||
chain: Chain;
|
||||
client: ThirdwebClient;
|
||||
metadata: FetchDeployMetadataResult;
|
||||
constructorParams?: Record<string, unknown>;
|
||||
implementationConstructorParams?: Record<string, unknown>;
|
||||
modules?: FetchDeployMetadataResult[];
|
||||
}): Promise<DeployTransactionResult[]> {
|
||||
export async function getRequiredTransactions(
|
||||
options: Omit<
|
||||
DeployContractfromDeployMetadataOptions,
|
||||
"account" | "initializeParams"
|
||||
>,
|
||||
): Promise<DeployTransactionResult[]> {
|
||||
const {
|
||||
chain,
|
||||
client,
|
||||
metadata,
|
||||
deployMetadata,
|
||||
implementationConstructorParams,
|
||||
modules = [],
|
||||
} = options;
|
||||
|
||||
if (metadata?.deployType === "autoFactory") {
|
||||
const results: (DeployTransactionResult | null)[] = await Promise.all([
|
||||
if (deployMetadata?.deployType === "autoFactory") {
|
||||
const results: (
|
||||
| DeployTransactionResult
|
||||
| DeployTransactionResult[]
|
||||
| null
|
||||
)[] = await Promise.all([
|
||||
getDeployedCreate2Factory({
|
||||
chain,
|
||||
client,
|
||||
}).then((c) =>
|
||||
c ? null : ({ type: "infra", contractId: "Create2Factory" } as const),
|
||||
c || isZkSyncChain(chain)
|
||||
? null
|
||||
: ({ type: "infra", contractId: "Create2Factory" } as const),
|
||||
),
|
||||
getDeployedInfraContract({
|
||||
chain,
|
||||
client,
|
||||
contractId: "Forwarder",
|
||||
}).then((c) =>
|
||||
c ? null : ({ type: "infra", contractId: "Forwarder" } as const),
|
||||
c || isZkSyncChain(chain)
|
||||
? null
|
||||
: ({ type: "infra", contractId: "Forwarder" } as const),
|
||||
),
|
||||
getDeployedInfraContract({
|
||||
chain,
|
||||
@@ -69,42 +78,150 @@ export async function getRequiredTransactionCount(options: {
|
||||
}),
|
||||
},
|
||||
}).then((c) =>
|
||||
c ? null : ({ type: "infra", contractId: "TWCloneFactory" } as const),
|
||||
c || isZkSyncChain(chain)
|
||||
? null
|
||||
: ({ type: "infra", contractId: "TWCloneFactory" } as const),
|
||||
),
|
||||
// TODO (deploy): add WETH contract check for implementations that need it (check implementation constructor params)
|
||||
getDeployedInfraContract({
|
||||
getTransactionsForImplementation({
|
||||
chain,
|
||||
client,
|
||||
contractId: metadata.name,
|
||||
constructorParams: implementationConstructorParams,
|
||||
publisher: metadata.publisher,
|
||||
version: metadata.version,
|
||||
}).then((c) =>
|
||||
c
|
||||
? null
|
||||
: ({
|
||||
type: "implementation",
|
||||
contractId: metadata.name,
|
||||
} as const),
|
||||
),
|
||||
deployMetadata,
|
||||
implementationConstructorParams,
|
||||
}),
|
||||
...modules.map((m) =>
|
||||
getDeployedInfraContractFromMetadata({
|
||||
chain,
|
||||
client,
|
||||
contractMetadata: m,
|
||||
contractMetadata: m.deployMetadata,
|
||||
}).then((c) =>
|
||||
c
|
||||
? null
|
||||
: ({
|
||||
type: "module",
|
||||
contractId: m.name,
|
||||
contractId: m.deployMetadata.name,
|
||||
} as const),
|
||||
),
|
||||
),
|
||||
]);
|
||||
results.push({ type: "proxy", contractId: metadata.name });
|
||||
return results.filter((r) => r !== null);
|
||||
results.push({ type: "proxy", contractId: deployMetadata.name });
|
||||
return results.flat().filter((r) => r !== null);
|
||||
}
|
||||
|
||||
return [{ type: "implementation", contractId: metadata.name }];
|
||||
return [{ type: "implementation", contractId: deployMetadata.name }];
|
||||
}
|
||||
|
||||
async function getTransactionsForImplementation(options: {
|
||||
chain: Chain;
|
||||
client: ThirdwebClient;
|
||||
deployMetadata: FetchDeployMetadataResult;
|
||||
implementationConstructorParams?: Record<string, unknown>;
|
||||
}): Promise<DeployTransactionResult[]> {
|
||||
const { chain, client, deployMetadata, implementationConstructorParams } =
|
||||
options;
|
||||
|
||||
if (deployMetadata.name === "MarketplaceV3") {
|
||||
return getTransactionsForMaketplaceV3(options);
|
||||
}
|
||||
|
||||
const constructorParams =
|
||||
implementationConstructorParams ??
|
||||
(await getAllDefaultConstructorParamsForImplementation({
|
||||
chain,
|
||||
client,
|
||||
}));
|
||||
|
||||
const result = await getDeployedInfraContract({
|
||||
chain,
|
||||
client,
|
||||
contractId: deployMetadata.name,
|
||||
constructorParams,
|
||||
publisher: deployMetadata.publisher,
|
||||
version: deployMetadata.version,
|
||||
}).then((c) =>
|
||||
c
|
||||
? null
|
||||
: ({
|
||||
type: "implementation",
|
||||
contractId: deployMetadata.name,
|
||||
} as const),
|
||||
);
|
||||
return result ? [result] : [];
|
||||
}
|
||||
|
||||
async function getTransactionsForMaketplaceV3(options: {
|
||||
chain: Chain;
|
||||
client: ThirdwebClient;
|
||||
}): Promise<DeployTransactionResult[]> {
|
||||
const { chain, client } = options;
|
||||
const WETHAdress = await computePublishedContractAddress({
|
||||
chain,
|
||||
client,
|
||||
contractId: "WETH9",
|
||||
});
|
||||
const extensions: (DeployTransactionResult | null)[] = await Promise.all([
|
||||
getDeployedInfraContract({
|
||||
chain,
|
||||
client,
|
||||
contractId: "WETH9",
|
||||
}).then((c) =>
|
||||
c ? null : ({ type: "infra", contractId: "WETH9" } as const),
|
||||
),
|
||||
getDeployedInfraContract({
|
||||
chain,
|
||||
client,
|
||||
contractId: "DirectListingsLogic",
|
||||
constructorParams: { _nativeTokenWrapper: WETHAdress },
|
||||
}).then((c) =>
|
||||
c
|
||||
? null
|
||||
: ({ type: "extension", contractId: "DirectListingsLogic" } as const),
|
||||
),
|
||||
getDeployedInfraContract({
|
||||
chain,
|
||||
client,
|
||||
contractId: "EnglishAuctionsLogic",
|
||||
constructorParams: { _nativeTokenWrapper: WETHAdress },
|
||||
}).then((c) =>
|
||||
c
|
||||
? null
|
||||
: ({ type: "extension", contractId: "EnglishAuctionsLogic" } as const),
|
||||
),
|
||||
getDeployedInfraContract({
|
||||
chain,
|
||||
client,
|
||||
contractId: "OffersLogic",
|
||||
}).then((c) =>
|
||||
c ? null : ({ type: "extension", contractId: "OffersLogic" } as const),
|
||||
),
|
||||
]);
|
||||
// hacky assumption: if we need to deploy any of the extensions, we also need to deploy the implementation
|
||||
const transactions = extensions.filter((e) => e !== null);
|
||||
if (transactions.length) {
|
||||
transactions.push({ type: "implementation", contractId: "MarketplaceV3" });
|
||||
}
|
||||
return transactions;
|
||||
}
|
||||
|
||||
export async function getAllDefaultConstructorParamsForImplementation(args: {
|
||||
chain: Chain;
|
||||
client: ThirdwebClient;
|
||||
}) {
|
||||
const { chain, client } = args;
|
||||
const [forwarder, weth] = await Promise.all([
|
||||
computePublishedContractAddress({
|
||||
chain,
|
||||
client,
|
||||
contractId: "Forwarder",
|
||||
}),
|
||||
computePublishedContractAddress({
|
||||
chain,
|
||||
client,
|
||||
contractId: "WETH9",
|
||||
}),
|
||||
]);
|
||||
return {
|
||||
trustedForwarder: forwarder,
|
||||
nativeTokenWrapper: weth,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -138,6 +138,7 @@ export async function getDefaultGasOverrides(
|
||||
async function getDynamicFeeData(
|
||||
client: ThirdwebClient,
|
||||
chain: Chain,
|
||||
percentMultiplier = 10,
|
||||
): Promise<FeeData> {
|
||||
let maxFeePerGas: null | bigint = null;
|
||||
let maxPriorityFeePerGas_: null | bigint = null;
|
||||
@@ -173,7 +174,10 @@ async function getDynamicFeeData(
|
||||
}
|
||||
|
||||
// add 10% tip to maxPriorityFeePerGas for faster processing
|
||||
maxPriorityFeePerGas_ = getPreferredPriorityFee(maxPriorityFeePerGas_);
|
||||
maxPriorityFeePerGas_ = getPreferredPriorityFee(
|
||||
maxPriorityFeePerGas_,
|
||||
percentMultiplier,
|
||||
);
|
||||
// eip-1559 formula, doubling the base fee ensures that the tx can be included in the next 6 blocks no matter how busy the network is
|
||||
// good article on the subject: https://www.blocknative.com/blog/eip-1559-fees
|
||||
maxFeePerGas = baseBlockFee * 2n + maxPriorityFeePerGas_;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { AbiConstructor, AbiFunction } from "abitype";
|
||||
import { parseAbiParams } from "../contract/parse-abi-params.js";
|
||||
|
||||
export function normalizeFunctionParams(
|
||||
abiFunction: AbiFunction | AbiConstructor | undefined,
|
||||
@@ -7,23 +8,27 @@ export function normalizeFunctionParams(
|
||||
if (!abiFunction) {
|
||||
return [];
|
||||
}
|
||||
if (abiFunction.inputs?.length !== Object.keys(params).length) {
|
||||
throw new Error(
|
||||
`Expected ${abiFunction.inputs?.length || 0} parameters, but got ${Object.keys(params).length}`,
|
||||
);
|
||||
}
|
||||
return abiFunction.inputs.map((input, index) => {
|
||||
const value = input.name;
|
||||
if (value === undefined) {
|
||||
throw new Error(
|
||||
`Missing named parameter for ${"name" in abiFunction ? abiFunction.name : "constructor"} at index ${index}`,
|
||||
);
|
||||
}
|
||||
const valueWithoutUnderscore = value.replace(/^_+/, "");
|
||||
const normalizedValue = params[value] ?? params[valueWithoutUnderscore]; // handle _name, _symbol, etc passed without underscore in the keys
|
||||
if (normalizedValue === undefined) {
|
||||
throw new Error(`Missing value for parameter ${value} at index ${index}`);
|
||||
}
|
||||
return normalizedValue;
|
||||
});
|
||||
|
||||
return parseAbiParams(
|
||||
abiFunction.inputs.map((i) => i.type),
|
||||
abiFunction.inputs.map((input, index) => {
|
||||
const value = input.name;
|
||||
if (value === undefined) {
|
||||
throw new Error(
|
||||
`Missing named parameter for ${"name" in abiFunction ? abiFunction.name : "constructor"} at index ${index}`,
|
||||
);
|
||||
}
|
||||
const valueWithoutUnderscore = value.replace(/^_+/, "");
|
||||
const normalizedValue =
|
||||
valueWithoutUnderscore in params
|
||||
? params[valueWithoutUnderscore]
|
||||
: params[value]; // handle _name, _symbol, etc passed without underscore in the keys
|
||||
if (normalizedValue === undefined) {
|
||||
throw new Error(
|
||||
`Missing value for parameter ${value} at index ${index}`,
|
||||
);
|
||||
}
|
||||
return normalizedValue;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ export async function fetchDeployMetadata(
|
||||
return {
|
||||
...rawMeta,
|
||||
...parsedMeta,
|
||||
version: rawMeta.version,
|
||||
bytecode: deployBytecode,
|
||||
};
|
||||
}
|
||||
@@ -69,7 +70,7 @@ async function fetchAndParseCompilerMetadata(
|
||||
`Could not resolve metadata for contract at ${options.uri}`,
|
||||
);
|
||||
}
|
||||
return formatCompilerMetadata(metadata);
|
||||
return { ...metadata, ...formatCompilerMetadata(metadata) };
|
||||
}
|
||||
|
||||
// types
|
||||
@@ -87,7 +88,37 @@ type RawCompilerMetadata = {
|
||||
type ParsedCompilerMetadata = {
|
||||
name: string;
|
||||
abi: Abi;
|
||||
metadata: Record<string, unknown>;
|
||||
metadata: {
|
||||
compiler: {
|
||||
version: string;
|
||||
};
|
||||
language: string;
|
||||
output: {
|
||||
abi: Abi;
|
||||
devdoc: Record<string, unknown>;
|
||||
userdoc: Record<string, unknown>;
|
||||
};
|
||||
settings: {
|
||||
compilationTarget: Record<string, unknown>;
|
||||
evmVersion: string;
|
||||
libraries: Record<string, string>;
|
||||
optimizer: Record<string, unknown>;
|
||||
remappings: string[];
|
||||
};
|
||||
sources: Record<
|
||||
string,
|
||||
{ keccak256: string } & (
|
||||
| {
|
||||
content: string;
|
||||
}
|
||||
| {
|
||||
urls: string[];
|
||||
license?: string;
|
||||
}
|
||||
)
|
||||
>;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
info: {
|
||||
title?: string;
|
||||
author?: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { type Hex, encodePacked } from "viem";
|
||||
import { encodePacked } from "viem/utils";
|
||||
import { ensureBytecodePrefix } from "../bytecode/prefix.js";
|
||||
import { uint8ArrayToHex } from "../encoding/hex.js";
|
||||
import { type Hex, uint8ArrayToHex } from "../encoding/hex.js";
|
||||
import { getSaltHash } from "./get-salt-hash.js";
|
||||
import { keccakId } from "./keccak-id.js";
|
||||
|
||||
|
||||
@@ -79,6 +79,9 @@ export function parseAbiParams(
|
||||
return value;
|
||||
}
|
||||
if (type.startsWith("uint") || type.startsWith("int")) {
|
||||
if (typeof value === "bigint") {
|
||||
return value;
|
||||
}
|
||||
if (typeof value !== "string" && typeof value !== "number") {
|
||||
throw new Error(`Cannot convert type ${typeof value} to BigInt`);
|
||||
}
|
||||
|
||||
Generated
+2
-2
@@ -2222,7 +2222,7 @@ importers:
|
||||
version: 3.592.0
|
||||
'@aws-sdk/credential-providers':
|
||||
specifier: 3.592.0
|
||||
version: 3.592.0(@aws-sdk/[email protected](@aws-sdk/client-sts@3.645.0))
|
||||
version: 3.592.0(@aws-sdk/[email protected](@aws-sdk/client-sts@3.592.0))
|
||||
'@coinbase/wallet-mobile-sdk':
|
||||
specifier: ^1
|
||||
version: 1.0.13([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))([email protected])([email protected]))([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected])([email protected]))([email protected])
|
||||
@@ -2377,7 +2377,7 @@ importers:
|
||||
version: 3.592.0
|
||||
'@aws-sdk/credential-providers':
|
||||
specifier: 3.592.0
|
||||
version: 3.592.0(@aws-sdk/[email protected](@aws-sdk/client-sts@3.592.0))
|
||||
version: 3.592.0(@aws-sdk/[email protected](@aws-sdk/client-sts@3.645.0))
|
||||
'@chromatic-com/storybook':
|
||||
specifier: ^1.5.0
|
||||
version: 1.8.0([email protected])
|
||||
|
||||
Reference in New Issue
Block a user