From 8702a49cc7d5b0bde62cdc8b12fad91070ca00ad Mon Sep 17 00:00:00 2001 From: Manan Tank Date: Mon, 16 Jun 2025 20:27:39 +0530 Subject: [PATCH 1/9] [TOOL-4689] Dashboard: Integrate ERC20Asset contract in token creation flow --- .../tables/contract-table.tsx | 1 + apps/dashboard/src/@/components/ui/tabs.tsx | 8 +- .../src/@/hooks/project-contracts.ts | 2 +- .../tokens/create/_common/step-card.tsx | 2 +- .../nft-collection-info-fieldset.tsx | 3 +- .../tokens/create/nft/create-nft-page-ui.tsx | 11 +- .../tokens/create/token/_common/form.ts | 29 +- .../create/token/create-token-page-impl.tsx | 231 +++-------- .../create/token/create-token-page.client.tsx | 31 +- .../token/create-token-page.stories.tsx | 17 +- .../token/distribution/token-airdrop.tsx | 4 +- .../create/token/distribution/token-sale.tsx | 377 ++++++++++++++---- .../create/token/launch/launch-token.tsx | 14 - .../token/token-info/token-info-fieldset.tsx | 3 +- 14 files changed, 418 insertions(+), 315 deletions(-) diff --git a/apps/dashboard/src/@/components/contract-components/tables/contract-table.tsx b/apps/dashboard/src/@/components/contract-components/tables/contract-table.tsx index 27ba135d3fd..e608e879c03 100644 --- a/apps/dashboard/src/@/components/contract-components/tables/contract-table.tsx +++ b/apps/dashboard/src/@/components/contract-components/tables/contract-table.tsx @@ -300,6 +300,7 @@ const contractTypeToAssetTypeRecord: Record = { DropERC20: "Coin", DropERC721: "NFT Collection", DropERC1155: "NFT Collection", + ERC20Asset: "Coin", }; const NetworkFilterCell = React.memo(function NetworkFilterCell({ diff --git a/apps/dashboard/src/@/components/ui/tabs.tsx b/apps/dashboard/src/@/components/ui/tabs.tsx index 47acea12b5d..a2239f181ef 100644 --- a/apps/dashboard/src/@/components/ui/tabs.tsx +++ b/apps/dashboard/src/@/components/ui/tabs.tsx @@ -98,6 +98,7 @@ export function TabButtons(props: { shadowColor?: string; tabIconClassName?: string; hideBottomLine?: boolean; + bottomLineClassName?: string; }) { const { containerRef, lineRef, activeTabRef } = useUnderline(); @@ -106,7 +107,12 @@ export function TabButtons(props: {
{/* Bottom line */} {!props.hideBottomLine && ( -
+
)} { const res = await apiServerProxy({ body: JSON.stringify({ diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/_common/step-card.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/_common/step-card.tsx index fa14752c80f..316ee8b824f 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/_common/step-card.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/_common/step-card.tsx @@ -35,7 +35,7 @@ export function StepCard(props: { {props.children} {(props.prevButton || props.nextButton) && ( -
+
{props.prevButton && ( +
+ ); +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/claim-rewards/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/claim-rewards/page.tsx new file mode 100644 index 00000000000..5cd5d7de07e --- /dev/null +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/claim-rewards/page.tsx @@ -0,0 +1,91 @@ +import { notFound, redirect } from "next/navigation"; +import { getContract } from "thirdweb"; +import { + getDeployedEntrypointERC20, + getRewardLocker, + v3PositionManager as getV3PositionManager, +} from "thirdweb/assets"; +import { getProject } from "@/api/projects"; +import { getContractPageParamsInfo } from "../../../../../../../(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/getContractFromParams"; +import type { ProjectContractPageParams } from "../types"; +import { ClaimRewardsPage } from "./components/claim-rewards-page"; +import { getValidReward } from "./utils/rewards"; + +export default async function Page(props: { + params: Promise; +}) { + const params = await props.params; + const project = await getProject(params.team_slug, params.project_slug); + + if (!project) { + notFound(); + } + + const info = await getContractPageParamsInfo({ + chainIdOrSlug: params.chainIdOrSlug, + contractAddress: params.contractAddress, + teamId: project.teamId, + }); + + if (!info) { + notFound(); + } + + const assetContractClient = info.clientContract; + + const entrypointContractClient = await getDeployedEntrypointERC20({ + chain: assetContractClient.chain, + client: assetContractClient.client, + }); + + const reward = await getValidReward({ + assetContract: assetContractClient, + entrypointContract: entrypointContractClient, + }); + + const rewardLocker = await getRewardLocker({ + contract: entrypointContractClient, + }).catch(() => null); + + if (!reward || !rewardLocker) { + redirect( + `/team/${params.team_slug}/${params.project_slug}/contract/${params.chainIdOrSlug}/${params.contractAddress}`, + ); + } + + const rewardLockerContractClient = getContract({ + address: rewardLocker, + chain: assetContractClient.chain, + client: assetContractClient.client, + }); + + const v3PositionManager = await getV3PositionManager({ + contract: rewardLockerContractClient, + }).catch(() => null); + + // const v4PositionManager = await getV4PositionManager({ + // contract: rewardLockerContractClient, + // }).catch(() => null); + + if (!v3PositionManager || v3PositionManager !== reward.positionManager) { + redirect( + `/team/${params.team_slug}/${params.project_slug}/contract/${params.chainIdOrSlug}/${params.contractAddress}`, + ); + } + + const v3PositionManagerContract = getContract({ + address: reward.positionManager, + chain: assetContractClient.chain, + client: assetContractClient.client, + }); + + return ( + + ); +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/claim-rewards/utils/rewards.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/claim-rewards/utils/rewards.ts new file mode 100644 index 00000000000..7c6283d0100 --- /dev/null +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/claim-rewards/utils/rewards.ts @@ -0,0 +1,28 @@ +import { type ThirdwebContract, ZERO_ADDRESS } from "thirdweb"; +import { getReward } from "thirdweb/assets"; + +export async function getValidReward(params: { + assetContract: ThirdwebContract; + entrypointContract: ThirdwebContract; +}) { + try { + const reward = await getReward({ + contract: params.entrypointContract, + asset: params.assetContract.address, + }); + + if ( + reward.positionManager === ZERO_ADDRESS || + reward.recipient === ZERO_ADDRESS || + reward.referrer === ZERO_ADDRESS || + reward.referrerBps === 0 || + reward.tokenId === BigInt(0) + ) { + return null; + } + + return reward; + } catch { + return null; + } +} diff --git a/packages/thirdweb/src/exports/assets.ts b/packages/thirdweb/src/exports/assets.ts index a2cbef1ae7a..71967483f99 100644 --- a/packages/thirdweb/src/exports/assets.ts +++ b/packages/thirdweb/src/exports/assets.ts @@ -18,5 +18,10 @@ export type { PoolConfig, TokenParams, } from "../assets/types.js"; -export { getInitBytecodeWithSalt } from "../utils/any-evm/get-init-bytecode-with-salt.js"; export { getReward } from "../extensions/assets/__generated__/ERC20AssetEntrypoint/read/getReward.js"; +export { getRewardLocker } from "../extensions/assets/__generated__/ERC20AssetEntrypoint/read/getRewardLocker.js"; +export { claimReward } from "../extensions/assets/__generated__/ERC20AssetEntrypoint/write/claimReward.js"; +export { positions } from "../extensions/assets/__generated__/RewardLocker/read/positions.js"; +export { v3PositionManager } from "../extensions/assets/__generated__/RewardLocker/read/v3PositionManager.js"; +export { v4PositionManager } from "../extensions/assets/__generated__/RewardLocker/read/v4PositionManager.js"; +export { getInitBytecodeWithSalt } from "../utils/any-evm/get-init-bytecode-with-salt.js";