update: added multi-file support

feat/proxy-contracts
Victa Kwok Wai Phu 3 years ago
parent 7b152bcb5e
commit 92d8910b97
  1. 2
      .env
  2. 44253
      package-lock.json
  3. 84
      src/api/explorerV1.ts
  4. 41
      src/pages/AddressPage/ContractDetails/index.tsx
  5. 75
      src/pages/VerifyContract/VerifyContract.tsx

@ -3,6 +3,6 @@ REACT_APP_RPC_URL_SHARD1=https://api.s1.t.hmny.io/
REACT_APP_RPC_URL_SHARD2=https://api.s2.t.hmny.io/ REACT_APP_RPC_URL_SHARD2=https://api.s2.t.hmny.io/
REACT_APP_RPC_URL_SHARD3=https://api.s3.t.hmny.io/ REACT_APP_RPC_URL_SHARD3=https://api.s3.t.hmny.io/
REACT_APP_AVAILABLE_SHARDS=0,1,2,3 REACT_APP_AVAILABLE_SHARDS=0,1,2,3
REACT_APP_EXPLORER_V1_API_URL=https://ctrver.t.hmny.io/ REACT_APP_EXPLORER_V1_API_URL=http://localhost:8080/
REACT_APP_INDEXER_IPFS_GATEWAY=https://ipfs.io/ipfs/ REACT_APP_INDEXER_IPFS_GATEWAY=https://ipfs.io/ipfs/
REACT_APP_PROD_ADDRESS=https://ws.explorer-v2-api.hmny.io REACT_APP_PROD_ADDRESS=https://ws.explorer-v2-api.hmny.io

44253
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -14,6 +14,8 @@ export interface IVerifyContractData {
isLoading: boolean; isLoading: boolean;
argsLoading: boolean; argsLoading: boolean;
error: string; error: string;
tab: string;
fileList?: File[];
} }
export interface IVerifyContractDataSendData { export interface IVerifyContractDataSendData {
@ -26,32 +28,76 @@ export interface IVerifyContractDataSendData {
constructorArguments: string; constructorArguments: string;
chainType: string; chainType: string;
contractName: string; contractName: string;
fileList?: File[],
tab: string,
} }
export const verifyContractCode = async (data: IVerifyContractDataSendData) => { export const verifyContractCode = async (data: IVerifyContractDataSendData) => {
const response = await fetch(
`${process.env.REACT_APP_EXPLORER_V1_API_URL}codeVerification`, if (data.tab === "Multiple Source Files") {
{ const formData = new FormData();
method: "POST", data.fileList?.forEach(file=>{
mode: "cors", formData.append(file.name, file);
cache: "no-cache", });
credentials: "same-origin",
headers: { for (const [k, v] of Object.entries(data)) {
"Content-Type": "application/json", if (k === "fileList") continue;
},
redirect: "follow", if (k === "libraries") {
referrerPolicy: "no-referrer", formData.append(k, v.join(","));
body: JSON.stringify(data), }
else {
formData.append(k, v);
}
} }
);
const body = await response.json(); const response = await fetch(
`${process.env.REACT_APP_EXPLORER_V1_API_URL}codeVerification`,
{
method: "POST",
mode: "cors",
cache: "no-cache",
credentials: "same-origin",
redirect: "follow",
referrerPolicy: "no-referrer",
body: formData,
}
);
if (response.status !== 200) { const body = await response.json();
throw new Error(body?.message);
if (response.status !== 200) {
throw new Error(body?.message);
}
return body;
} }
else {
const response = await fetch(
`${process.env.REACT_APP_EXPLORER_V1_API_URL}codeVerification`,
{
method: "POST",
mode: "cors",
cache: "no-cache",
credentials: "same-origin",
headers: {
"Content-Type": "application/json",
},
redirect: "follow",
referrerPolicy: "no-referrer",
body: JSON.stringify(data),
}
);
return body; const body = await response.json();
if (response.status !== 200) {
throw new Error(body?.message);
}
return body;
}
}; };
export const loadSourceCode = async (address: string): Promise<ISourceCode> => { export const loadSourceCode = async (address: string): Promise<ISourceCode> => {
@ -70,6 +116,7 @@ export const loadSourceCode = async (address: string): Promise<ISourceCode> => {
); );
const body = await response.json(); const body = await response.json();
console.log(body);
if (response.status !== 200) { if (response.status !== 200) {
throw new Error(body); throw new Error(body);
@ -84,6 +131,7 @@ export interface ISourceCode {
optimizer: string; optimizer: string;
optimizerTimes: string; optimizerTimes: string;
sourceCode: string; sourceCode: string;
supporting: any;
libraries: string[]; libraries: string[];
constructorArguments: string; constructorArguments: string;
chainType: string; chainType: string;

@ -119,9 +119,11 @@ enum V_TABS {
CODE = "Code", CODE = "Code",
READ = "Read Contract", READ = "Read Contract",
WRITE = "Write Contract", WRITE = "Write Contract",
READ_PROXY = "Read as Proxy",
WRITE_PROXY = "Write as Proxy",
} }
const TabBox = styled(Box)<{ selected: boolean }>` const TabBox = styled(Box) <{ selected: boolean }>`
border: 1px solid ${(props) => props.theme.global.colors.border}; border: 1px solid ${(props) => props.theme.global.colors.border};
background: ${(props) => background: ${(props) =>
props.selected ? props.theme.global.colors.backgroundBack : "transparent"}; props.selected ? props.theme.global.colors.backgroundBack : "transparent"};
@ -164,7 +166,7 @@ export const VerifiedContractDetails = (props: {
try { try {
abiString = JSON.stringify(props.sourceCode.abi, null, 4); abiString = JSON.stringify(props.sourceCode.abi, null, 4);
} catch {} } catch { }
return ( return (
<Box direction="column"> <Box direction="column">
@ -206,19 +208,32 @@ export const VerifiedContractDetails = (props: {
value={ value={
props.sourceCode.optimizer || props.sourceCode.optimizer ||
"No" + "No" +
(Number(props.sourceCode.optimizerTimes) (Number(props.sourceCode.optimizerTimes)
? ` with ${props.sourceCode.optimizerTimes} runs` ? ` with ${props.sourceCode.optimizerTimes} runs`
: "") : "")
}
/>
<Item
label="Contract Source Code Verified"
value={
<StyledTextArea readOnly={true} rows={15} cols={100}>
{props.sourceCode.sourceCode || ""}
</StyledTextArea>
} }
/> />
{props.sourceCode.sourceCode &&
<Item
label="Contract Source Code Verified"
value={
<StyledTextArea readOnly={true} rows={15} cols={100} value={props.sourceCode.sourceCode || ""}>
</StyledTextArea>
}
/>}
{props.sourceCode.supporting?.sources
&&
Object.keys(props.sourceCode.supporting?.sources).map((source: string, i: number) => {
return <Item
key={i}
label={`Verified ${source}`}
value={
<StyledTextArea readOnly={true} rows={15} cols={100} value={props.sourceCode.supporting.sources[source].source || ""}>
</StyledTextArea>
}
/>
})}
<Item <Item
label="ABI" label="ABI"
value={ value={

@ -1,5 +1,6 @@
import { import {
Box, Box,
FileInput,
Heading, Heading,
Select, Select,
Spinner, Spinner,
@ -30,11 +31,39 @@ const Wrapper = styled(Box)`
export function uniqid(prefix = "", random = false) { export function uniqid(prefix = "", random = false) {
const sec = Date.now() * 1000 + Math.random() * 1000; const sec = Date.now() * 1000 + Math.random() * 1000;
const id = sec.toString(16).replace(/\./g, "").padEnd(14, "0"); const id = sec.toString(16).replace(/\./g, "").padEnd(14, "0");
return `${prefix}${id}${ return `${prefix}${id}${random ? `.${Math.trunc(Math.random() * 100000000)}` : ""
random ? `.${Math.trunc(Math.random() * 100000000)}` : "" }`;
}`;
} }
enum V_TABS {
SINGLE = "Single Source File",
MULTI = "Multiple Source Files",
}
const TabBox = styled(Box) <{ selected: boolean }>`
border: 1px solid ${(props) => props.theme.global.colors.border};
background: ${(props) =>
props.selected ? props.theme.global.colors.backgroundBack : "transparent"};
padding: 7px 12px 6px 12px;
border-radius: 4px;
margin: 5px 10px;
`;
const TabButton = (props: {
text: string;
onClick: () => void;
selected: boolean;
}) => {
return (
<TabBox onClick={props.onClick} selected={props.selected}>
<Text size="small" color={"minorText"}>
{props.text}
</Text>
</TabBox>
);
};
class VerifyContractBase extends React.Component< class VerifyContractBase extends React.Component<
{ {
isLessTablet: boolean; isLessTablet: boolean;
@ -56,6 +85,7 @@ class VerifyContractBase extends React.Component<
argsLoading: false, argsLoading: false,
statusText: "", statusText: "",
error: "", error: "",
tab: V_TABS.SINGLE,
}; };
getBytecode = async () => { getBytecode = async () => {
@ -224,8 +254,20 @@ class VerifyContractBase extends React.Component<
</Box> </Box>
</Field> </Field>
</Box> </Box>
<Box direction="row" align="center" margin={{ top: "medium" }}>
<TabButton
text={V_TABS.SINGLE}
onClick={() => this.setState({ tab: V_TABS.SINGLE })}
selected={this.state.tab === V_TABS.SINGLE}
/>
<TabButton
text={V_TABS.MULTI}
onClick={() => this.setState({ tab: V_TABS.MULTI })}
selected={this.state.tab === V_TABS.MULTI}
/>
<Field margin={"small"}> </Box>
{this.state.tab === V_TABS.SINGLE && <Field margin={"small"}>
<Text>Enter the Solidity Contract Code below</Text> <Text>Enter the Solidity Contract Code below</Text>
<TextArea <TextArea
style={{ minHeight: "300px" }} style={{ minHeight: "300px" }}
@ -237,7 +279,30 @@ class VerifyContractBase extends React.Component<
}} }}
disabled={isLoading} disabled={isLoading}
/> />
</Field> </Field>}
{
this.state.tab === V_TABS.MULTI &&
<Field margin={"small"}>
<Text>Select multiple solidity source files</Text>
<FileInput
name="file"
max="100000"
multiple
onChange={event => {
if (!event.target.files) return;
const fileList = event.target.files;
const files = [];
for (let i = 0; i < fileList?.length; i += 1) {
const file = fileList[i];
files.push(file);
}
console.log(files);
this.setState({fileList:files});
}}
/>
</Field>
}
<Field margin={"small"}> <Field margin={"small"}>
<Box direction="row" justify="between"> <Box direction="row" justify="between">

Loading…
Cancel
Save