otterscan/src/sourcify/useSourcify.ts

276 lines
6.5 KiB
TypeScript
Raw Normal View History

2021-09-15 17:43:36 +00:00
import { useState, useEffect, useMemo } from "react";
import { Interface } from "@ethersproject/abi";
2021-12-14 00:03:13 +00:00
import { ErrorDescription } from "@ethersproject/abi/lib/interface";
2021-11-25 18:50:59 +00:00
import { ChecksummedAddress, TransactionData } from "../types";
import { sourcifyMetadata, SourcifySource, sourcifySourceFile } from "../url";
2021-09-06 00:08:06 +00:00
export type UserMethod = {
notice?: string | undefined;
};
export type UserEvent = {
notice?: string | undefined;
};
2021-12-14 00:03:13 +00:00
export type UserError = [
{
notice?: string | undefined;
}
];
export type UserDoc = {
kind: "user";
version?: number | undefined;
notice?: string | undefined;
methods: Record<string, UserMethod>;
events: Record<string, UserEvent>;
2021-12-14 00:03:13 +00:00
errors?: Record<string, UserError> | undefined;
};
export type DevMethod = {
params?: Record<string, string>;
returns?: Record<string, string>;
};
2021-12-14 00:03:13 +00:00
export type DevError = [
{
params?: Record<string, string>;
}
];
export type DevDoc = {
kind: "dev";
version?: number | undefined;
methods: Record<string, DevMethod>;
2021-12-14 00:03:13 +00:00
errors?: Record<string, DevError> | undefined;
};
2021-09-06 00:08:06 +00:00
export type Metadata = {
version: string;
language: string;
compiler: {
version: string;
keccak256?: string | undefined;
};
sources: {
[filename: string]: {
keccak256: string;
content?: string | undefined;
urls?: string[];
license?: string;
};
};
settings: {
remappings: string[];
optimizer?: {
enabled: boolean;
runs: number;
};
compilationTarget: {
[filename: string]: string;
};
libraries: {
[filename: string]: string;
};
};
output: {
abi: any[];
userdoc?: UserDoc | undefined;
devdoc?: DevDoc | undefined;
2021-09-06 00:08:06 +00:00
};
};
const fetchSourcifyMetadata = async (
address: ChecksummedAddress,
chainId: number,
source: SourcifySource,
abortController: AbortController
): Promise<Metadata | null> => {
try {
const metadataURL = sourcifyMetadata(address, chainId, source);
const result = await fetch(metadataURL, {
signal: abortController.signal,
});
if (result.ok) {
return await result.json();
}
return null;
} catch (err) {
console.error(err);
return null;
}
};
// TODO: replace every occurrence with the multiple version one
2021-09-06 00:08:06 +00:00
export const useSourcify = (
address: ChecksummedAddress | undefined,
2021-09-06 21:32:11 +00:00
chainId: number | undefined,
source: SourcifySource
): Metadata | null | undefined => {
2021-09-06 00:08:06 +00:00
const [rawMetadata, setRawMetadata] = useState<Metadata | null | undefined>();
useEffect(() => {
if (!address || chainId === undefined) {
2021-09-06 00:08:06 +00:00
return;
}
setRawMetadata(undefined);
2021-09-14 01:24:22 +00:00
const abortController = new AbortController();
2021-09-06 00:08:06 +00:00
const fetchMetadata = async () => {
const _metadata = await fetchSourcifyMetadata(
address,
chainId,
source,
abortController
);
setRawMetadata(_metadata);
2021-09-06 00:08:06 +00:00
};
fetchMetadata();
2021-09-14 01:24:22 +00:00
return () => {
abortController.abort();
};
}, [address, chainId, source]);
2021-09-06 00:08:06 +00:00
return rawMetadata;
};
2021-09-06 05:09:12 +00:00
export const useMultipleMetadata = (
baseMetadatas: Record<string, Metadata | null> | undefined,
addresses: ChecksummedAddress[] | undefined,
chainId: number | undefined,
source: SourcifySource
): Record<ChecksummedAddress, Metadata | null | undefined> => {
const [rawMetadata, setRawMetadata] = useState<
Record<string, Metadata | null | undefined>
>({});
useEffect(() => {
if (addresses === undefined || chainId === undefined) {
return;
}
setRawMetadata({});
const abortController = new AbortController();
const fetchMetadata = async (_addresses: string[]) => {
const fetchers: Promise<Metadata | null>[] = [];
for (const address of _addresses) {
fetchers.push(
fetchSourcifyMetadata(address, chainId, source, abortController)
);
}
const results = await Promise.all(fetchers);
if (abortController.signal.aborted) {
return;
}
let metadatas: Record<string, Metadata | null> = {};
if (baseMetadatas) {
metadatas = { ...baseMetadatas };
}
for (let i = 0; i < results.length; i++) {
metadatas[_addresses[i]] = results[i];
}
setRawMetadata(metadatas);
};
const filtered = addresses.filter((a) => baseMetadatas?.[a] === undefined);
fetchMetadata(filtered);
return () => {
abortController.abort();
};
}, [baseMetadatas, addresses, chainId, source]);
return rawMetadata;
};
2021-09-06 05:09:12 +00:00
export const useContract = (
checksummedAddress: string,
networkId: number,
filename: string,
2021-09-06 21:32:11 +00:00
source: any,
sourcifySource: SourcifySource
2021-09-06 05:09:12 +00:00
) => {
const [content, setContent] = useState<string>(source.content);
useEffect(() => {
if (source.content) {
return;
}
2021-09-14 01:24:22 +00:00
const abortController = new AbortController();
2021-09-06 05:09:12 +00:00
const readContent = async () => {
2021-09-07 03:44:28 +00:00
const normalizedFilename = filename.replaceAll(/[@:]/g, "_");
2021-09-06 05:09:12 +00:00
const url = sourcifySourceFile(
checksummedAddress,
networkId,
2021-09-06 21:32:11 +00:00
normalizedFilename,
sourcifySource
2021-09-06 05:09:12 +00:00
);
2021-09-14 01:24:22 +00:00
const res = await fetch(url, { signal: abortController.signal });
2021-09-06 05:09:12 +00:00
if (res.ok) {
const _content = await res.text();
setContent(_content);
}
};
readContent();
2021-09-14 01:24:22 +00:00
return () => {
abortController.abort();
};
}, [checksummedAddress, networkId, filename, source.content, sourcifySource]);
2021-09-06 05:09:12 +00:00
return content;
};
2021-09-15 17:43:36 +00:00
export const useTransactionDescription = (
metadata: Metadata | null | undefined,
txData: TransactionData | null | undefined
) => {
const txDesc = useMemo(() => {
2021-09-18 18:40:19 +00:00
if (metadata === null) {
return null;
}
2021-09-15 17:43:36 +00:00
if (!metadata || !txData) {
return undefined;
}
const abi = metadata.output.abi;
const intf = new Interface(abi as any);
2021-09-22 06:02:21 +00:00
try {
return intf.parseTransaction({
data: txData.data,
value: txData.value,
});
} catch (err) {
console.warn("Couldn't find function signature", err);
return null;
}
2021-09-15 17:43:36 +00:00
}, [metadata, txData]);
return txDesc;
};
2021-12-14 00:03:13 +00:00
export const useError = (
metadata: Metadata | null | undefined,
output: string | null | undefined
): ErrorDescription | null | undefined => {
const err = useMemo(() => {
if (!metadata || !output) {
return undefined;
}
const abi = metadata.output.abi;
const intf = new Interface(abi as any);
try {
return intf.parseError(output);
} catch (err) {
console.warn("Couldn't find error signature", err);
return null;
}
}, [metadata, output]);
return err;
};