Skip to content

Commit d7a0613

Browse files
committed
[TOOL-4897] Dashboard: Add Contract creator badge in Coin and NFT asset page (#7458)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## PR-Codex overview This PR introduces the `contractCreator` property to multiple components, allowing the display of the contract creator's address in the UI. It also adds a new function, `getContractCreator`, to retrieve the contract creator's information based on the contract's role. ### Detailed summary - Added `contractCreator` prop to `NFTPublicPageLayout`, `ContractHeaderUI`, and `ContractCreatorBadge`. - Implemented `getContractCreator` function to fetch the contract creator using `ThirdwebContract`. - Updated `NFTPublicPage` and `ERC20PublicPage` to call `getContractCreator` and pass the result to the UI. - Enhanced storybook stories to include `contractCreator` in various scenarios. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added display of contract creator information on ERC20 and NFT public pages. * Introduced a badge showing the contract creator's address in the contract header section. * **Enhancements** * Contract headers now conditionally show the creator badge if creator data is available. * Storybook stories for contract headers updated to include contract creator scenarios for improved UI previews. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent dbc0316 commit d7a0613

File tree

7 files changed

+118
-21
lines changed

7 files changed

+118
-21
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import type { ThirdwebContract } from "thirdweb";
2+
import { isOwnerSupported, owner } from "thirdweb/extensions/common";
3+
import {
4+
getRoleMember,
5+
isGetRoleAdminSupported,
6+
} from "thirdweb/extensions/permissions";
7+
8+
export async function getContractCreator(
9+
contract: ThirdwebContract,
10+
functionSelectors: string[],
11+
) {
12+
if (isOwnerSupported(functionSelectors)) {
13+
return owner({
14+
contract,
15+
});
16+
}
17+
18+
if (isGetRoleAdminSupported(functionSelectors)) {
19+
return getRoleMember({
20+
contract,
21+
index: BigInt(0),
22+
role: "admin",
23+
});
24+
}
25+
26+
return null;
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { ThirdwebContract } from "thirdweb";
2+
import { WalletAddress } from "@/components/blocks/wallet-address";
3+
4+
export function ContractCreatorBadge(props: {
5+
contractCreator: string;
6+
clientContract: ThirdwebContract;
7+
}) {
8+
return (
9+
<div className="flex items-center gap-1.5 bg-card rounded-full px-2.5 py-1.5 border hover:bg-accent">
10+
<span className="text-xs text-foreground">By</span>
11+
<WalletAddress
12+
address={props.contractCreator}
13+
className="py-0 text-xs h-auto !no-underline"
14+
client={props.clientContract.client}
15+
iconClassName="size-3.5 hidden"
16+
/>
17+
</div>
18+
);
19+
}

apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/_components/ContractHeader.stories.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,25 @@ export const WithImageAndMultipleSocialUrls: Story = {
8888
args: {
8989
chainMetadata: ethereumChainMetadata,
9090
clientContract: mockContract,
91+
contractCreator: null,
92+
image: mockTokenImage,
93+
name: "Sample Token",
94+
socialUrls: {
95+
discord: mockSocialUrls.discord,
96+
github: mockSocialUrls.github,
97+
telegram: mockSocialUrls.telegram,
98+
twitter: mockSocialUrls.twitter,
99+
website: mockSocialUrls.website,
100+
},
101+
symbol: "SMPL",
102+
},
103+
};
104+
105+
export const WithContractCreator: Story = {
106+
args: {
107+
chainMetadata: ethereumChainMetadata,
108+
clientContract: mockContract,
109+
contractCreator: "0x1234567890123456789012345678901234567890",
91110
image: mockTokenImage,
92111
name: "Sample Token",
93112
socialUrls: {
@@ -105,6 +124,7 @@ export const WithBrokenImageAndSingleSocialUrl: Story = {
105124
args: {
106125
chainMetadata: ethereumChainMetadata,
107126
clientContract: mockContract,
127+
contractCreator: null,
108128
image: "broken-image.png",
109129
name: "Sample Token",
110130
socialUrls: {
@@ -118,6 +138,7 @@ export const WithoutImageAndNoSocialUrls: Story = {
118138
args: {
119139
chainMetadata: ethereumChainMetadata,
120140
clientContract: mockContract,
141+
contractCreator: null,
121142
image: undefined,
122143
name: "Sample Token",
123144
socialUrls: {},
@@ -129,6 +150,7 @@ export const LongNameAndLotsOfSocialUrls: Story = {
129150
args: {
130151
chainMetadata: ethereumChainMetadata,
131152
clientContract: mockContract,
153+
contractCreator: null,
132154
image: "https://thirdweb.com/chain-icons/ethereum.svg",
133155
name: "This is a very long token name that should wrap to multiple lines",
134156
socialUrls: {
@@ -148,6 +170,7 @@ export const AllSocialUrls: Story = {
148170
args: {
149171
chainMetadata: ethereumChainMetadata,
150172
clientContract: mockContract,
173+
contractCreator: null,
151174
image: "https://thirdweb.com/chain-icons/ethereum.svg",
152175
name: "Sample Token",
153176
socialUrls: {
@@ -171,6 +194,7 @@ export const InvalidSocialUrls: Story = {
171194
args: {
172195
chainMetadata: ethereumChainMetadata,
173196
clientContract: mockContract,
197+
contractCreator: null,
174198
image: "https://thirdweb.com/chain-icons/ethereum.svg",
175199
name: "Sample Token",
176200
socialUrls: {
@@ -188,6 +212,7 @@ export const SomeSocialUrls: Story = {
188212
args: {
189213
chainMetadata: ethereumChainMetadata,
190214
clientContract: mockContract,
215+
contractCreator: null,
191216
image: "https://thirdweb.com/chain-icons/ethereum.svg",
192217
name: "Sample Token",
193218
socialUrls: {

apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/_components/ContractHeader.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { YoutubeIcon } from "@/icons/brand-icons/YoutubeIcon";
1919
import { ChainIconClient } from "@/icons/ChainIcon";
2020
import { cn } from "@/lib/utils";
2121
import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler";
22+
import { ContractCreatorBadge } from "./ContractCreatorBadge";
2223

2324
const platformToIcons: Record<string, React.FC<{ className?: string }>> = {
2425
discord: DiscordIcon,
@@ -42,6 +43,7 @@ export function ContractHeaderUI(props: {
4243
clientContract: ThirdwebContract;
4344
socialUrls: object;
4445
imageClassName?: string;
46+
contractCreator: string | null;
4547
}) {
4648
const socialUrls = useMemo(() => {
4749
const socialUrlsValue: { name: string; href: string }[] = [];
@@ -147,6 +149,13 @@ export function ContractHeaderUI(props: {
147149

148150
{/* bottom row */}
149151
<div className="flex flex-row flex-wrap items-center gap-2">
152+
{props.contractCreator && (
153+
<ContractCreatorBadge
154+
clientContract={props.clientContract}
155+
contractCreator={props.contractCreator}
156+
/>
157+
)}
158+
150159
<ToolTipLabel
151160
contentClassName="max-w-[300px]"
152161
label={

apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import type { ThirdwebContract } from "thirdweb";
22
import type { ChainMetadata } from "thirdweb/chains";
33
import { getContractMetadata } from "thirdweb/extensions/common";
44
import { decimals, getActiveClaimCondition } from "thirdweb/extensions/erc20";
5+
import { resolveFunctionSelectors } from "@/lib/selectors";
6+
import { getContractCreator } from "../_components/getContractCreator";
57
import { PageHeader } from "../_components/PageHeader";
68
import { ContractHeaderUI } from "./_components/ContractHeader";
79
import { TokenDropClaim } from "./_components/claim-tokens/claim-tokens-ui";
@@ -16,25 +18,33 @@ export async function ERC20PublicPage(props: {
1618
clientContract: ThirdwebContract;
1719
chainMetadata: ChainMetadata;
1820
}) {
19-
const [contractMetadata, activeClaimCondition, tokenDecimals] =
20-
await Promise.all([
21-
getContractMetadata({
22-
contract: props.serverContract,
23-
}),
24-
getActiveClaimConditionWithErrorHandler(props.serverContract),
25-
decimals({
26-
contract: props.serverContract,
27-
}),
28-
]);
21+
const [
22+
contractMetadata,
23+
activeClaimCondition,
24+
tokenDecimals,
25+
functionSelectors,
26+
] = await Promise.all([
27+
getContractMetadata({
28+
contract: props.serverContract,
29+
}),
30+
getActiveClaimConditionWithErrorHandler(props.serverContract),
31+
decimals({
32+
contract: props.serverContract,
33+
}),
34+
resolveFunctionSelectors(props.serverContract),
35+
]);
2936

30-
const claimConditionCurrencyMeta = activeClaimCondition
31-
? await getCurrencyMeta({
32-
chain: props.serverContract.chain,
33-
chainMetadata: props.chainMetadata,
34-
client: props.serverContract.client,
35-
currencyAddress: activeClaimCondition.currency,
36-
}).catch(() => undefined)
37-
: undefined;
37+
const [contractCreator, claimConditionCurrencyMeta] = await Promise.all([
38+
getContractCreator(props.serverContract, functionSelectors),
39+
activeClaimCondition
40+
? getCurrencyMeta({
41+
chain: props.serverContract.chain,
42+
chainMetadata: props.chainMetadata,
43+
client: props.serverContract.client,
44+
currencyAddress: activeClaimCondition.currency,
45+
}).catch(() => undefined)
46+
: undefined,
47+
]);
3848

3949
const buyEmbed = (
4050
<BuyEmbed
@@ -62,6 +72,7 @@ export async function ERC20PublicPage(props: {
6272
<ContractHeaderUI
6373
chainMetadata={props.chainMetadata}
6474
clientContract={props.clientContract}
75+
contractCreator={contractCreator}
6576
image={contractMetadata.image}
6677
name={contractMetadata.name}
6778
socialUrls={

apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page-layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export function NFTPublicPageLayout(props: {
1212
[key: string]: unknown;
1313
};
1414
children: React.ReactNode;
15+
contractCreator: string | null;
1516
}) {
1617
return (
1718
<div className="flex grow flex-col">
@@ -20,6 +21,7 @@ export function NFTPublicPageLayout(props: {
2021
<ContractHeaderUI
2122
chainMetadata={props.chainMetadata}
2223
clientContract={props.clientContract}
24+
contractCreator={props.contractCreator}
2325
image={
2426
typeof props.contractMetadata.image === "string"
2527
? props.contractMetadata.image

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { ResponsiveLayout } from "@/components/blocks/Responsive";
66
import { Skeleton } from "@/components/ui/skeleton";
77
import { resolveFunctionSelectors } from "@/lib/selectors";
88
import { cn } from "@/lib/utils";
9+
import { getContractCreator } from "../_components/getContractCreator";
910
import { NFTPublicPageLayout } from "./nft-page-layout";
1011
import {
1112
BuyNFTDropCardServer,
@@ -35,15 +36,17 @@ export async function NFTPublicPage(props: {
3536
}),
3637
]);
3738

38-
const nftDropClaimParams =
39+
const [contractCreator, nftDropClaimParams] = await Promise.all([
40+
getContractCreator(props.serverContract, functionSelectors),
3941
props.type === "erc721"
40-
? await getNFTDropClaimParams({
42+
? getNFTDropClaimParams({
4143
chainMetadata: props.chainMetadata,
4244
functionSelectors,
4345
serverContract: props.serverContract,
4446
totalNFTCount,
4547
})
46-
: undefined;
48+
: undefined,
49+
]);
4750

4851
const _isTokenByIndexSupported =
4952
props.type === "erc721" && isTokenByIndexSupported(functionSelectors);
@@ -101,6 +104,7 @@ export async function NFTPublicPage(props: {
101104
<NFTPublicPageLayout
102105
chainMetadata={props.chainMetadata}
103106
clientContract={props.clientContract}
107+
contractCreator={contractCreator}
104108
contractMetadata={contractMetadata}
105109
>
106110
<ResponsiveLayout

0 commit comments

Comments
 (0)