2021-11-06 18:54:34 +00:00
|
|
|
import { useState, useEffect, useContext, useMemo } from "react";
|
|
|
|
import {
|
|
|
|
Fragment,
|
|
|
|
Interface,
|
|
|
|
TransactionDescription,
|
|
|
|
} from "@ethersproject/abi";
|
2021-12-16 21:35:49 +00:00
|
|
|
import { BigNumberish } from "@ethersproject/bignumber";
|
2021-12-18 10:09:10 +00:00
|
|
|
import useSWRImmutable from "swr/immutable";
|
2021-07-12 22:13:12 +00:00
|
|
|
import { RuntimeContext } from "./useRuntime";
|
|
|
|
import { fourBytesURL } from "./url";
|
|
|
|
|
2021-09-25 09:51:50 +00:00
|
|
|
export type FourBytesEntry = {
|
|
|
|
name: string;
|
2021-09-27 18:47:55 +00:00
|
|
|
signature: string | undefined;
|
2021-09-25 09:51:50 +00:00
|
|
|
};
|
|
|
|
|
2021-10-28 20:21:01 +00:00
|
|
|
export type FourBytesMap = Record<string, FourBytesEntry | null | undefined>;
|
|
|
|
|
2021-12-16 22:08:04 +00:00
|
|
|
/**
|
|
|
|
* Given a hex input data; extract the method selector
|
|
|
|
*
|
|
|
|
* @param rawInput Raw tx input including the "0x"
|
|
|
|
* @returns the first 4 bytes, including the "0x" or null if the input
|
2021-12-16 22:22:13 +00:00
|
|
|
* contains an invalid selector, e.g., txs with 0x00 data; simple transfers (0x)
|
|
|
|
* return null as well as it is not a method selector
|
2021-12-16 22:08:04 +00:00
|
|
|
*/
|
2021-10-28 20:39:10 +00:00
|
|
|
export const extract4Bytes = (rawInput: string): string | null => {
|
|
|
|
if (rawInput.length < 10) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return rawInput.slice(0, 10);
|
|
|
|
};
|
|
|
|
|
2021-10-28 20:21:01 +00:00
|
|
|
const fetch4Bytes = async (
|
|
|
|
assetsURLPrefix: string,
|
|
|
|
fourBytes: string
|
|
|
|
): Promise<FourBytesEntry | null> => {
|
|
|
|
const signatureURL = fourBytesURL(assetsURLPrefix, fourBytes);
|
|
|
|
|
|
|
|
try {
|
|
|
|
const res = await fetch(signatureURL);
|
|
|
|
if (!res.ok) {
|
|
|
|
console.error(`Signature does not exist in 4bytes DB: ${fourBytes}`);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get only the first occurrence, for now ignore alternative param names
|
|
|
|
const sigs = await res.text();
|
|
|
|
const sig = sigs.split(";")[0];
|
|
|
|
const cut = sig.indexOf("(");
|
|
|
|
const method = sig.slice(0, cut);
|
|
|
|
|
|
|
|
const entry: FourBytesEntry = {
|
|
|
|
name: method,
|
|
|
|
signature: sig,
|
|
|
|
};
|
|
|
|
return entry;
|
|
|
|
} catch (err) {
|
|
|
|
console.error(`Couldn't fetch signature URL ${signatureURL}`, err);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-12-16 22:08:04 +00:00
|
|
|
// TODO: migrate to swr and merge with use4Bytes
|
2021-10-28 20:21:01 +00:00
|
|
|
export const useBatch4Bytes = (
|
|
|
|
rawFourByteSigs: string[] | undefined
|
|
|
|
): FourBytesMap => {
|
|
|
|
const runtime = useContext(RuntimeContext);
|
|
|
|
const assetsURLPrefix = runtime.config?.assetsURLPrefix;
|
|
|
|
|
|
|
|
const [fourBytesMap, setFourBytesMap] = useState<FourBytesMap>({});
|
|
|
|
useEffect(() => {
|
2021-11-22 18:57:28 +00:00
|
|
|
if (!rawFourByteSigs || assetsURLPrefix === undefined) {
|
2021-10-28 20:21:01 +00:00
|
|
|
setFourBytesMap({});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const loadSigs = async () => {
|
|
|
|
const promises = rawFourByteSigs.map((s) =>
|
|
|
|
fetch4Bytes(assetsURLPrefix, s.slice(2))
|
|
|
|
);
|
|
|
|
const results = await Promise.all(promises);
|
|
|
|
|
|
|
|
const _fourBytesMap: Record<string, FourBytesEntry | null> = {};
|
|
|
|
for (let i = 0; i < rawFourByteSigs.length; i++) {
|
|
|
|
_fourBytesMap[rawFourByteSigs[i]] = results[i];
|
|
|
|
}
|
|
|
|
setFourBytesMap(_fourBytesMap);
|
|
|
|
};
|
|
|
|
loadSigs();
|
|
|
|
}, [assetsURLPrefix, rawFourByteSigs]);
|
|
|
|
|
|
|
|
return fourBytesMap;
|
|
|
|
};
|
|
|
|
|
2021-09-25 09:51:50 +00:00
|
|
|
/**
|
|
|
|
* Extract 4bytes DB info
|
|
|
|
*
|
|
|
|
* @param rawFourBytes an hex string containing the 4bytes signature in the "0xXXXXXXXX" format.
|
|
|
|
*/
|
2021-09-27 04:09:56 +00:00
|
|
|
export const use4Bytes = (
|
2021-12-16 22:08:04 +00:00
|
|
|
rawFourBytes: string | null
|
2021-09-25 09:51:50 +00:00
|
|
|
): FourBytesEntry | null | undefined => {
|
2021-12-16 22:08:04 +00:00
|
|
|
if (rawFourBytes !== null && !rawFourBytes.startsWith("0x")) {
|
2021-12-16 21:35:49 +00:00
|
|
|
throw new Error(
|
|
|
|
`rawFourBytes must contain a bytes hex string starting with 0x; received value: "${rawFourBytes}"`
|
|
|
|
);
|
2021-09-25 09:51:50 +00:00
|
|
|
}
|
|
|
|
|
2021-12-16 21:35:49 +00:00
|
|
|
const { config } = useContext(RuntimeContext);
|
|
|
|
const assetsURLPrefix = config?.assetsURLPrefix;
|
2021-09-25 09:51:50 +00:00
|
|
|
|
2021-12-16 22:08:04 +00:00
|
|
|
const fourBytesFetcher = (key: string | null) => {
|
|
|
|
if (key === null || key === "0x") {
|
2021-12-16 21:35:49 +00:00
|
|
|
return undefined;
|
2021-09-25 09:51:50 +00:00
|
|
|
}
|
|
|
|
|
2021-12-16 21:35:49 +00:00
|
|
|
// Handle simple transfers with invalid selector like tx:
|
|
|
|
// 0x8bcbdcc1589b5c34c1e55909c8269a411f0267a4fed59a73dd4348cc71addbb9,
|
|
|
|
// which contains 0x00 as data
|
|
|
|
if (key.length !== 10) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
2021-12-18 10:31:03 +00:00
|
|
|
return fetch4Bytes(assetsURLPrefix!, key.slice(2));
|
2021-12-16 21:35:49 +00:00
|
|
|
};
|
2021-09-25 09:51:50 +00:00
|
|
|
|
2021-12-18 10:09:10 +00:00
|
|
|
const { data, error } = useSWRImmutable<FourBytesEntry | null | undefined>(
|
2021-12-18 10:31:03 +00:00
|
|
|
assetsURLPrefix !== undefined ? rawFourBytes : null,
|
2021-12-16 21:35:49 +00:00
|
|
|
fourBytesFetcher
|
|
|
|
);
|
2021-12-18 10:31:03 +00:00
|
|
|
return error ? undefined : data;
|
2021-09-25 09:51:50 +00:00
|
|
|
};
|
2021-11-06 18:54:34 +00:00
|
|
|
|
2021-12-16 22:12:55 +00:00
|
|
|
export const useMethodSelector = (data: string): [boolean, string, string] => {
|
|
|
|
const rawFourBytes = extract4Bytes(data);
|
|
|
|
const fourBytesEntry = use4Bytes(rawFourBytes);
|
|
|
|
const isSimpleTransfer = data === "0x";
|
|
|
|
const methodName = isSimpleTransfer
|
|
|
|
? "transfer"
|
|
|
|
: fourBytesEntry?.name ?? rawFourBytes ?? "-";
|
|
|
|
const methodTitle = isSimpleTransfer
|
|
|
|
? "ETH Transfer"
|
|
|
|
: methodName === rawFourBytes
|
|
|
|
? methodName
|
|
|
|
: `${methodName} [${rawFourBytes}]`;
|
|
|
|
|
|
|
|
return [isSimpleTransfer, methodName, methodTitle];
|
|
|
|
};
|
|
|
|
|
2021-11-06 18:54:34 +00:00
|
|
|
export const useTransactionDescription = (
|
|
|
|
fourBytesEntry: FourBytesEntry | null | undefined,
|
|
|
|
data: string | undefined,
|
|
|
|
value: BigNumberish | undefined
|
|
|
|
): TransactionDescription | null | undefined => {
|
|
|
|
const txDesc = useMemo(() => {
|
|
|
|
if (!fourBytesEntry) {
|
|
|
|
return fourBytesEntry;
|
|
|
|
}
|
2021-11-07 20:35:17 +00:00
|
|
|
if (
|
|
|
|
!fourBytesEntry.signature ||
|
|
|
|
data === undefined ||
|
|
|
|
value === undefined
|
|
|
|
) {
|
2021-11-06 18:54:34 +00:00
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
const sig = fourBytesEntry?.signature;
|
|
|
|
const functionFragment = Fragment.fromString(`function ${sig}`);
|
|
|
|
const intf = new Interface([functionFragment]);
|
|
|
|
return intf.parseTransaction({ data, value });
|
|
|
|
}, [fourBytesEntry, data, value]);
|
|
|
|
|
|
|
|
return txDesc;
|
|
|
|
};
|