Skip to content

Commit 8b0fd13

Browse files
committed
[Dashboard] Sources page to v5 (#4201)
## Problem solved Short description of the bug fixed or feature added <!-- start pr-codex --> --- ## PR-Codex overview The focus of this PR is to update contract-related components and hooks, resolve ABI types, and improve UX in the dashboard. ### Detailed summary - Updated components and hooks related to contracts - Resolved ABI types with `abitype` - Improved UX in the dashboard by updating sources display > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 20bd3aa commit 8b0fd13

File tree

8 files changed

+99
-118
lines changed

8 files changed

+99
-118
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { useQuery } from "@tanstack/react-query";
2+
import type { Abi } from "abitype";
3+
import type { ThirdwebContract } from "thirdweb";
4+
import { resolveContractAbi } from "thirdweb/contract";
5+
import invariant from "tiny-invariant";
6+
7+
export function useResolveContractAbi(contract?: ThirdwebContract) {
8+
return useQuery({
9+
queryKey: [
10+
"full-contract-abi",
11+
contract?.chain.id || "",
12+
contract?.address || "",
13+
],
14+
queryFn: async () => {
15+
invariant(contract, "contract is required");
16+
const abi = await resolveContractAbi<Abi>(contract);
17+
return abi;
18+
},
19+
enabled: !!contract,
20+
});
21+
}

apps/dashboard/src/components/contract-components/shared/sources-accordion.tsx

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
AccordionItem,
66
AccordionPanel,
77
} from "@chakra-ui/react";
8-
import type { Abi } from "@thirdweb-dev/sdk";
8+
import type { Abi } from "abitype";
99
import { CodeBlock, Heading } from "tw-components";
1010
import type { SourceFile } from "../types";
1111

@@ -20,61 +20,62 @@ export const SourcesAccordion: React.FC<SourcesAccordionProps> = ({
2020
}) => {
2121
return (
2222
<Accordion allowMultiple defaultIndex={[]}>
23-
{sources.map((signature) => (
23+
{/* ABI Accordion is put at the top for better UX */}
24+
{abi && (
2425
<AccordionItem
2526
gap={4}
2627
flexDirection="column"
27-
key={signature.filename}
2828
borderColor="borderColor"
2929
_first={{ borderTopWidth: 0 }}
3030
_last={{ borderBottomWidth: 0 }}
3131
>
3232
{({ isExpanded }) => (
3333
<>
3434
<AccordionButton justifyContent="space-between" py={2}>
35-
<Heading size="label.md">{signature.filename}</Heading>
35+
<Heading size="label.md">ABI</Heading>
3636
<AccordionIcon />
3737
</AccordionButton>
3838
<AccordionPanel>
3939
{isExpanded && (
4040
<CodeBlock
41+
code={JSON.stringify(abi, null, 2)}
42+
language="json"
4143
overflow="auto"
42-
code={signature.source.trim()}
43-
language="solidity"
4444
/>
4545
)}
4646
</AccordionPanel>
4747
</>
4848
)}
4949
</AccordionItem>
50-
))}
51-
{abi && (
50+
)}
51+
{sources.map((signature) => (
5252
<AccordionItem
5353
gap={4}
5454
flexDirection="column"
55+
key={signature.filename}
5556
borderColor="borderColor"
5657
_first={{ borderTopWidth: 0 }}
5758
_last={{ borderBottomWidth: 0 }}
5859
>
5960
{({ isExpanded }) => (
6061
<>
6162
<AccordionButton justifyContent="space-between" py={2}>
62-
<Heading size="label.md">ABI</Heading>
63+
<Heading size="label.md">{signature.filename}</Heading>
6364
<AccordionIcon />
6465
</AccordionButton>
6566
<AccordionPanel>
6667
{isExpanded && (
6768
<CodeBlock
68-
code={JSON.stringify(abi, null, 2)}
69-
language="json"
7069
overflow="auto"
70+
code={signature.source.trim()}
71+
language="solidity"
7172
/>
7273
)}
7374
</AccordionPanel>
7475
</>
7576
)}
7677
</AccordionItem>
77-
)}
78+
))}
7879
</Accordion>
7980
);
8081
};

apps/dashboard/src/components/contract-components/shared/sources-panel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Flex } from "@chakra-ui/react";
2-
import type { Abi } from "@thirdweb-dev/sdk";
2+
import type { Abi } from "abitype";
33
import { Link, Text } from "tw-components";
44
import type { SourceFile } from "../types";
55
import { SourcesAccordion } from "./sources-accordion";

apps/dashboard/src/components/contract-functions/contract-functions.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ export const ContractFunctionsOverview: React.FC<ContractFunctionsOverview> = ({
104104
)}
105105
{(sources || abi) && (
106106
<TabPanel>
107+
{/* @ts-expect-error This won't cause any error and will auto-resolve itself once we have fully migrated to v5 */}
107108
<SourcesPanel sources={sources} abi={abi} />
108109
</TabPanel>
109110
)}

apps/dashboard/src/components/engine/contract-subscription/add-contract-subscription-button.tsx

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import { thirdwebClient } from "@/constants/client";
12
import {
23
type AddContractSubscriptionInput,
34
useEngineAddContractSubscription,
45
} from "@3rdweb-sdk/react/hooks/useEngine";
6+
import { useResolveContractAbi } from "@3rdweb-sdk/react/hooks/useResolveContractAbi";
57
import {
68
CheckboxGroup,
79
Collapse,
@@ -25,11 +27,12 @@ import {
2527
} from "@chakra-ui/react";
2628
import { NetworkDropdown } from "components/contract-components/contract-publish-form/NetworkDropdown";
2729
import { useTrack } from "hooks/analytics/useTrack";
28-
import { useContractAbiItems } from "hooks/useContractAbiItems";
2930
import { useTxNotifications } from "hooks/useTxNotifications";
31+
import { useV5DashboardChain } from "lib/v5-adapter";
3032
import { type Dispatch, type SetStateAction, useMemo, useState } from "react";
3133
import { type UseFormReturn, useForm } from "react-hook-form";
3234
import { AiOutlinePlusCircle } from "react-icons/ai";
35+
import { getContract } from "thirdweb";
3336
import {
3437
Button,
3538
Card,
@@ -421,32 +424,66 @@ const FilterSelector = ({
421424
filter: string[];
422425
setFilter: (value: string[]) => void;
423426
}) => {
424-
const abiItemsQuery = useContractAbiItems(
425-
form.getValues("chainId"),
426-
form.getValues("contractAddress") as `0x${string}`,
427-
);
427+
const chain = useV5DashboardChain(form.getValues("chainId"));
428+
const contract = chain
429+
? getContract({
430+
address: form.getValues("contractAddress"),
431+
chain,
432+
client: thirdwebClient,
433+
})
434+
: undefined;
435+
436+
const abiQuery = useResolveContractAbi(contract);
437+
438+
const abiItems = useMemo(() => {
439+
if (!abiQuery.data) {
440+
return {
441+
readFunctions: [],
442+
writeFunctions: [],
443+
events: [],
444+
};
445+
}
446+
const readFunctions: string[] = [];
447+
const writeFunctions: string[] = [];
448+
const events: string[] = [];
449+
for (const abiItem of abiQuery.data) {
450+
if (abiItem.type === "function") {
451+
if (
452+
abiItem.stateMutability === "pure" ||
453+
abiItem.stateMutability === "view"
454+
) {
455+
readFunctions.push(abiItem.name);
456+
} else {
457+
writeFunctions.push(abiItem.name);
458+
}
459+
} else if (abiItem.type === "event") {
460+
events.push(abiItem.name);
461+
}
462+
}
463+
return {
464+
readFunctions: [...new Set(readFunctions)].sort(),
465+
writeFunctions: [...new Set(writeFunctions)].sort(),
466+
events: [...new Set(events)].sort(),
467+
};
468+
}, [abiQuery.data]);
428469

429470
const filterNames = useMemo(() => {
430471
switch (abiItemType) {
431472
case "function": {
432-
return abiItemsQuery.data.writeFunctions;
473+
return abiItems.writeFunctions;
433474
}
434475
case "event": {
435-
return abiItemsQuery.data.events;
476+
return abiItems.events;
436477
}
437478
default: {
438479
return [];
439480
}
440481
}
441-
}, [
442-
abiItemType,
443-
abiItemsQuery.data.events,
444-
abiItemsQuery.data.writeFunctions,
445-
]);
482+
}, [abiItemType, abiItems.events, abiItems.writeFunctions]);
446483

447484
return (
448485
<Card>
449-
{abiItemsQuery.isLoading ? (
486+
{abiQuery.isLoading ? (
450487
<Spinner size="sm" />
451488
) : filterNames.length === 0 ? (
452489
<Text>

apps/dashboard/src/contract-ui/hooks/useRouteConfig.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,9 @@ export function useContractRouteConfig(
482482
{
483483
title: "Sources",
484484
path: "sources",
485-
component: LazyContractSourcesPage,
485+
component: () => (
486+
<>{contract && <LazyContractSourcesPage contract={contract} />}</>
487+
),
486488
isDefault: true,
487489
},
488490
];

apps/dashboard/src/contract-ui/tabs/sources/page.tsx

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { thirdwebClient } from "@/constants/client";
21
import { useDashboardEVMChainId } from "@3rdweb-sdk/react";
32
import { useQueryWithNetwork } from "@3rdweb-sdk/react/hooks/query/useQueryWithNetwork";
3+
import { useResolveContractAbi } from "@3rdweb-sdk/react/hooks/useResolveContractAbi";
44
import {
55
Divider,
66
Flex,
@@ -15,20 +15,17 @@ import {
1515
useDisclosure,
1616
} from "@chakra-ui/react";
1717
import { useMutation, useQueryClient } from "@tanstack/react-query";
18-
import { useContract } from "@thirdweb-dev/react";
19-
import type { Abi } from "@thirdweb-dev/sdk";
2018
import { SourcesPanel } from "components/contract-components/shared/sources-panel";
2119
import { useContractSources } from "contract-ui/hooks/useContractSources";
22-
import { useV5DashboardChain } from "lib/v5-adapter";
2320
import { useMemo, useState } from "react";
2421
import { FiCheckCircle, FiXCircle } from "react-icons/fi";
2522
import { toast } from "sonner";
26-
import { getContract } from "thirdweb";
23+
import type { ThirdwebContract } from "thirdweb";
2724
import { Badge, Button, Card, Heading } from "tw-components";
2825
import { useDashboardRouter } from "../../../@/lib/DashboardRouter";
2926

3027
interface ContractSourcesPageProps {
31-
contractAddress?: string;
28+
contract: ThirdwebContract;
3229
}
3330

3431
type ContractParams = {
@@ -179,7 +176,7 @@ const VerifyContractModal: React.FC<
179176
};
180177

181178
export const ContractSourcesPage: React.FC<ContractSourcesPageProps> = ({
182-
contractAddress,
179+
contract,
183180
}) => {
184181
const [resetSignal, setResetSignal] = useState(0);
185182

@@ -191,18 +188,8 @@ export const ContractSourcesPage: React.FC<ContractSourcesPageProps> = ({
191188
setResetSignal((prev: number) => prev + 1);
192189
};
193190

194-
const { contract } = useContract(contractAddress);
195-
const chain = useV5DashboardChain(contract?.chainId);
196-
const contractV5 =
197-
contract && chain
198-
? getContract({
199-
address: contract.getAddress(),
200-
chain,
201-
client: thirdwebClient,
202-
})
203-
: undefined;
204-
const contractSourcesQuery = useContractSources(contractV5);
205-
const abi = useMemo(() => contract?.abi as Abi, [contract]);
191+
const contractSourcesQuery = useContractSources(contract);
192+
const abiQuery = useResolveContractAbi(contract);
206193

207194
// clean up the source filenames and filter out libraries
208195
const sources = useMemo(() => {
@@ -220,10 +207,6 @@ export const ContractSourcesPage: React.FC<ContractSourcesPageProps> = ({
220207
.reverse();
221208
}, [contractSourcesQuery.data]);
222209

223-
if (!contractAddress || !contract) {
224-
return <div>No contract address provided</div>;
225-
}
226-
227210
if (!contractSourcesQuery || contractSourcesQuery?.isLoading) {
228211
return (
229212
<Flex direction="row" align="center" gap={2}>
@@ -238,7 +221,7 @@ export const ContractSourcesPage: React.FC<ContractSourcesPageProps> = ({
238221
<VerifyContractModal
239222
isOpen={isOpen}
240223
onClose={() => handleClose()}
241-
contractAddress={contractAddress}
224+
contractAddress={contract.address}
242225
resetSignal={resetSignal}
243226
/>
244227

@@ -248,15 +231,15 @@ export const ContractSourcesPage: React.FC<ContractSourcesPageProps> = ({
248231
Sources
249232
</Heading>
250233
<RefreshContractMetadataButton
251-
chainId={contract.chainId}
252-
contractAddress={contract.getAddress()}
234+
chainId={contract.chain.id}
235+
contractAddress={contract.address}
253236
/>
254237
<Button variant="solid" colorScheme="purple" onClick={onOpen}>
255238
Verify contract
256239
</Button>
257240
</Flex>
258241
<Card p={0}>
259-
<SourcesPanel sources={sources} abi={abi} />
242+
<SourcesPanel sources={sources} abi={abiQuery.data} />
260243
</Card>
261244
</Flex>
262245
</>

apps/dashboard/src/hooks/useContractAbiItems.ts

Lines changed: 0 additions & 64 deletions
This file was deleted.

0 commit comments

Comments
 (0)