Skip to content

Commit 371f7e3

Browse files
committed
support for multiple verifiers
1 parent 9f1cef2 commit 371f7e3

18 files changed

Lines changed: 466 additions & 199 deletions

src/App.tsx

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { useLoadContractProof } from "./lib/useLoadContractProof";
44
import ContractSourceCode from "./components/ContractSourceCode";
55
import { useOverride } from "./lib/useOverride";
66
import { useFileStore } from "./lib/useFileStore";
7-
import { useResetState } from "./lib/useResetState";
87
import { styled } from "@mui/system";
98
import { Backdrop, Box, Skeleton, useMediaQuery, useTheme } from "@mui/material";
109
import { useContractAddress } from "./lib/useContractAddress";
@@ -21,10 +20,14 @@ import { VerificationInfoBlock } from "./components/VerificationInfoBlock";
2120
import { CenteringBox } from "./components/Common.styled";
2221
import { useAddressHistory } from "./lib/useAddressHistory";
2322
import { LatestVerifiedContracts } from "./components/LatestVerifiedContracts";
24-
import { useInitializeGetters } from "./lib/getter/useGetters";
2523
import { TestnetBar, useIsTestnet } from "./components/TestnetBar";
2624
import { useRemoteConfig } from "./lib/useRemoteConfig";
2725
import { useCompilerSettingsStore } from "./lib/useCompilerSettingsStore";
26+
import { useCustomGetter } from "./lib/getter/useCustomGetter";
27+
import { usePublishStore } from "./lib/usePublishSteps";
28+
import { usePreload } from "./lib/usePreload";
29+
import { useSubmitSources } from "./lib/useSubmitSources";
30+
import { VerifierListBlock } from "./components/VerifierListBlock";
2831

2932
export const ContentBox = styled(Box)({
3033
maxWidth: 1160,
@@ -57,16 +60,32 @@ function App() {
5760
const theme = useTheme();
5861
const canOverride = useOverride();
5962
const { contractAddress, isAddressEmpty } = useContractAddress();
60-
const { hasFiles } = useFileStore();
63+
const { hasFiles, reset: resetFileStore } = useFileStore();
64+
const { reset: resetPublishStore } = usePublishStore();
65+
const { isPreloaded, clearPreloaded } = usePreload();
66+
const { invalidate: invalidateSubmitSources } = useSubmitSources();
6167
const scrollToRef = useRef();
6268
const headerSpacings = useMediaQuery(theme.breakpoints.down("lg"));
6369
const isSmallScreen = useMediaQuery(theme.breakpoints.down("md"));
6470
const showSkeleton = !error && isLoading && contractAddress;
6571
const isTestnet = useIsTestnet();
6672

6773
useAddressHistory();
68-
useResetState();
69-
useInitializeGetters();
74+
75+
useEffect(() => {
76+
if (!isPreloaded) {
77+
resetFileStore();
78+
} else {
79+
clearPreloaded();
80+
}
81+
resetPublishStore();
82+
invalidateSubmitSources();
83+
}, [contractAddress]);
84+
85+
const { clear: clearCustomGetter } = useCustomGetter();
86+
useEffect(() => {
87+
clearCustomGetter();
88+
}, [contractAddress]);
7089

7190
useEffect(() => {
7291
window.scrollTo({ behavior: "auto", top: scrollToRef.current?.["offsetTop"] });
@@ -180,6 +199,9 @@ function App() {
180199
)}
181200
</>
182201
)}
202+
<OverflowingBox sx={{ padding: 0 }} mb={5}>
203+
<VerifierListBlock />
204+
</OverflowingBox>
183205
{proofData && <Footer />}
184206
</ContentBox>
185207
{!proofData && (

src/components/ContractSourceCode.tsx

Lines changed: 179 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import "./ContractSourceCode.css";
22
import { Box, Tabs, Tab, IconButton, useMediaQuery } from "@mui/material";
3-
import React, { useCallback, useEffect, useState } from "react";
3+
import React, { useCallback, useEffect, useMemo, useState } from "react";
44
import { VerifiedSourceCode } from "./VerifiedSourceCode";
55
import { DisassembledSourceCode } from "./DisassembledSourceCode";
6-
import { useLoadContractProof } from "../lib/useLoadContractProof";
76
import { CenteringBox, IconBox, TitleBox, TitleText } from "./Common.styled";
87
import verified from "../assets/verified-light.svg";
98
import { styled } from "@mui/system";
@@ -13,12 +12,14 @@ import copy from "../assets/copy.svg";
1312
import { downloadSources } from "../lib/downloadSources";
1413
import useNotification from "../lib/useNotification";
1514
import { Getters } from "./Getters";
16-
import { useGetters } from "../lib/getter/useGetters";
17-
18-
enum CODE {
19-
DISASSEMBLED,
20-
SOURCE,
21-
}
15+
import { useLoadVerifierRegistryInfo } from "../lib/useLoadVerifierRegistryInfo";
16+
import { useContractAddress } from "../lib/useContractAddress";
17+
import { useLoadContractInfo } from "../lib/useLoadContractInfo";
18+
import { useQueries } from "@tanstack/react-query";
19+
import { loadProofData } from "../lib/useLoadContractProof";
20+
import { useIsTestnet } from "./TestnetBar";
21+
import { useSyncGetters } from "../lib/getter/useGetters";
22+
import { SourcesData } from "@ton-community/contract-verifier-sdk";
2223

2324
const TitleWrapper = styled(CenteringBox)({
2425
justifyContent: "space-between",
@@ -48,33 +49,122 @@ const SourceCodeTabs = styled(Tabs)({
4849
},
4950
});
5051

52+
type ProofData = Partial<SourcesData> & { hasOnchainProof: boolean };
53+
54+
type DomIds = {
55+
containerId: string;
56+
filesId: string;
57+
contentId: string;
58+
};
59+
60+
type TabConfig =
61+
| { id: string; label: string; type: "disassembled" }
62+
| {
63+
id: string;
64+
label: string;
65+
type: "sources";
66+
proof: ProofData;
67+
domIds: DomIds;
68+
getterKey: string;
69+
}
70+
| {
71+
id: string;
72+
label: string;
73+
type: "getters";
74+
proof: ProofData;
75+
getterKey: string;
76+
};
77+
5178
function ContractSourceCode() {
52-
const { data: contractProof } = useLoadContractProof();
53-
const [value, setValue] = useState<number | undefined>(undefined);
79+
const { contractAddress } = useContractAddress();
80+
const { data: contractInfo } = useLoadContractInfo(contractAddress);
81+
const { data: verifierRegistry } = useLoadVerifierRegistryInfo();
82+
const [value, setValue] = useState(0);
5483
const isExtraSmallScreen = useMediaQuery("(max-width: 450px)");
5584
const modifiedCodeBlock = useMediaQuery("(max-width: 600px)");
5685
const { showNotification } = useNotification();
86+
const isTestnet = useIsTestnet();
5787

58-
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
59-
setValue(newValue);
60-
};
88+
const verifierEntries = useMemo(() => Object.entries(verifierRegistry ?? {}), [verifierRegistry]);
89+
90+
const proofQueries = useQueries({
91+
queries: verifierEntries.map(([id, config]) => ({
92+
queryKey: ["verifierProof", contractAddress, id, isTestnet],
93+
enabled: !!contractAddress && !!contractInfo?.codeCellToCompileBase64,
94+
queryFn: () => loadProofData(contractInfo!.codeCellToCompileBase64, config.name, isTestnet),
95+
})),
96+
});
97+
98+
const verifierProofs = useMemo(
99+
() =>
100+
verifierEntries.map(([id, config], index) => {
101+
const safeKey = `${contractAddress ?? "unknown"}-${id}`.replace(/[^a-zA-Z0-9]/g, "-");
102+
return {
103+
id,
104+
config,
105+
getterKey: `${contractAddress ?? "unknown"}::${id}`,
106+
domIds: {
107+
containerId: `${safeKey}-container`,
108+
filesId: `${safeKey}-files`,
109+
contentId: `${safeKey}-content`,
110+
},
111+
proof: proofQueries[index]?.data as ProofData | undefined,
112+
};
113+
}),
114+
[verifierEntries, proofQueries, contractAddress],
115+
);
61116

62-
const onCopy = useCallback(async (type: CODE) => {
63-
const element = document.querySelector(
64-
type === CODE.SOURCE
65-
? `#myVerifierContent > pre > code > .contract-verifier-code-content`
66-
: `pre > code > div.hljs.language-fift`,
67-
) as HTMLElement;
68-
navigator.clipboard.writeText(element?.innerText);
117+
const tabs = useMemo<TabConfig[]>(() => {
118+
const initialTabs: TabConfig[] = [
119+
{ id: "disassembled", label: "Disassembled", type: "disassembled" },
120+
];
121+
verifierProofs.forEach(({ id, config, proof, getterKey, domIds }) => {
122+
if (!proof?.hasOnchainProof) {
123+
return;
124+
}
125+
const labelSuffix = config.name || id;
126+
initialTabs.push({
127+
id: `sources-${id}`,
128+
label: `Sources (${labelSuffix})`,
129+
type: "sources",
130+
proof,
131+
domIds,
132+
getterKey,
133+
});
134+
initialTabs.push({
135+
id: `getters-${id}`,
136+
label: `Getters (${labelSuffix})`,
137+
type: "getters",
138+
proof,
139+
getterKey,
140+
});
141+
});
142+
return initialTabs;
143+
}, [verifierProofs]);
69144

70-
showNotification("Copied to clipboard!", "success");
71-
}, []);
145+
const hasAnyProof = tabs.some((tab) => tab.type === "sources");
72146

73147
useEffect(() => {
74-
setValue(!!contractProof?.hasOnchainProof ? 0 : 1);
75-
}, [contractProof?.hasOnchainProof]);
148+
const firstSourcesIndex = tabs.findIndex((tab) => tab.type === "sources");
149+
setValue(firstSourcesIndex !== -1 ? firstSourcesIndex : 0);
150+
}, [tabs.length, hasAnyProof]);
76151

77-
const { getters } = useGetters();
152+
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
153+
setValue(newValue);
154+
};
155+
156+
const handleCopy = useCallback(
157+
(selector: string) => {
158+
const element = document.querySelector(selector) as HTMLElement | null;
159+
if (!element) return;
160+
navigator.clipboard.writeText(element.innerText);
161+
showNotification("Copied to clipboard!", "success");
162+
},
163+
[showNotification],
164+
);
165+
166+
const activeTab = tabs[value] ?? tabs[0];
167+
const activeProof = activeTab && activeTab.type !== "disassembled" ? activeTab.proof : undefined;
78168

79169
return (
80170
<Box
@@ -91,11 +181,10 @@ function ContractSourceCode() {
91181
<img src={verified} alt="Block icon" width={41} height={41} />
92182
</IconBox>
93183
<TitleText>
94-
{!!contractProof?.hasOnchainProof && "Verified"} Source {isExtraSmallScreen && <br />}{" "}
95-
Code
184+
{hasAnyProof && "Verified"} Source {isExtraSmallScreen && <br />} Code
96185
</TitleText>
97186
</CenteringBox>
98-
{value === 0 && (
187+
{activeTab?.type === "sources" && (
99188
<Box
100189
sx={{
101190
alignSelf: "baseline",
@@ -110,7 +199,7 @@ function ContractSourceCode() {
110199
height={modifiedCodeBlock ? 30 : 37}
111200
width={modifiedCodeBlock ? 30 : 167}
112201
onClick={() => {
113-
contractProof?.files?.length && downloadSources(contractProof.files);
202+
activeProof?.files?.length && downloadSources(activeProof.files);
114203
}}>
115204
<img src={download} alt="Download icon" width={19} height={19} />
116205
{modifiedCodeBlock ? "" : "Download sources"}
@@ -121,40 +210,76 @@ function ContractSourceCode() {
121210
</TitleBox>
122211
<ContentBox p={3}>
123212
<SourceCodeTabs value={value} onChange={handleChange}>
124-
<Tab
125-
sx={{ textTransform: "none" }}
126-
disabled={!contractProof?.hasOnchainProof}
127-
label="Sources"
128-
/>
129-
<Tab sx={{ textTransform: "none" }} label="Disassembled" />
130-
<Tab sx={{ textTransform: "none" }} label={`Getters (${getters?.length ?? 0})`} />
213+
{tabs.map((tab, index) => (
214+
<Tab key={tab.id} value={index} sx={{ textTransform: "none" }} label={tab.label} />
215+
))}
131216
</SourceCodeTabs>
132-
<Box sx={{ display: value === 0 ? "block" : "none" }}>
133-
<VerifiedSourceCode button={<CopyButton onCopy={onCopy} copyText={CODE.SOURCE} />} />
134-
</Box>
135-
<Box sx={{ display: value === 1 ? "block" : "none" }}>
136-
<DisassembledSourceCode
137-
button={<CopyButton onCopy={onCopy} copyText={CODE.DISASSEMBLED} />}
138-
/>
139-
</Box>
140-
<Box sx={{ display: value === 2 ? "block" : "none" }}>
141-
<Getters />
142-
</Box>
217+
{tabs.map((tab, index) => {
218+
if (tab.type === "disassembled") {
219+
return (
220+
<Box key={tab.id} sx={{ display: value === index ? "block" : "none" }}>
221+
<DisassembledSourceCode
222+
button={
223+
<CopyButton onCopy={() => handleCopy(`pre > code > div.hljs.language-fift`)} />
224+
}
225+
/>
226+
</Box>
227+
);
228+
}
229+
if (tab.type === "sources") {
230+
return (
231+
<Box key={tab.id} sx={{ display: value === index ? "block" : "none" }}>
232+
<VerifiedSourceCode
233+
proofData={tab.proof}
234+
domIds={tab.domIds!}
235+
button={
236+
<CopyButton
237+
onCopy={() =>
238+
handleCopy(
239+
`#${tab.domIds!.contentId} > pre > code > .contract-verifier-code-content`,
240+
)
241+
}
242+
/>
243+
}
244+
/>
245+
</Box>
246+
);
247+
}
248+
return (
249+
<VerifierGettersPanel
250+
key={tab.id}
251+
getterKey={tab.getterKey}
252+
proof={tab.proof}
253+
isVisible={value === index}
254+
/>
255+
);
256+
})}
143257
</ContentBox>
144258
</Box>
145259
);
146260
}
147261

148-
const CopyButton = ({
149-
copyText,
150-
onCopy,
262+
function VerifierGettersPanel({
263+
getterKey,
264+
proof,
265+
isVisible,
151266
}: {
152-
copyText: CODE;
153-
onCopy: (type: CODE) => Promise<void>;
154-
}) => {
267+
getterKey: string;
268+
proof: ProofData;
269+
isVisible: boolean;
270+
}) {
271+
useSyncGetters(getterKey, proof?.files);
272+
return (
273+
<Box sx={{ display: isVisible ? "block" : "none" }}>
274+
<Getters getterKey={getterKey} />
275+
</Box>
276+
);
277+
}
278+
279+
const CopyButton = ({ onCopy }: { onCopy: () => void }) => {
155280
return (
156281
<CopyBox>
157-
<IconButton onClick={() => onCopy(copyText)}>
282+
<IconButton onClick={onCopy}>
158283
<img alt="Copy Icon" src={copy} width={16} height={16} />
159284
</IconButton>
160285
</CopyBox>

src/components/Getters.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,8 @@ function CustomGetterComponent({ getter }: { getter: CustomStateGetter }) {
252252
);
253253
}
254254

255-
export function Getters() {
256-
const { getters } = useGetters();
255+
export function Getters({ getterKey }: { getterKey: string }) {
256+
const { getters } = useGetters(getterKey);
257257
const customGetter = useCustomGetter();
258258

259259
return (

src/components/VerificationProofTable.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ export function VerificationProofTable() {
2626

2727
// TODO this supports a single verifier Id for now.
2828
// when we wish to support multiple verifiers, load contract proof would have to address that
29-
const verifierConfig = verifierRegistryInfo?.find((v) => v.name === "verifier.ton.org");
29+
const verifierConfig = Object.values(verifierRegistryInfo ?? {}).find(
30+
(v) => v.name === "verifier.ton.org",
31+
);
3032

3133
const onCopy = useCallback(async (value: string) => {
3234
navigator.clipboard.writeText(value);

0 commit comments

Comments
 (0)