Web: fix vaa parsing (#31)

* web: fix broken response objects & update parseVaa method

* Removed _parseVAAAlgorand

* onchain-data: replaced serum rpc and added more try/catch in case of failure

* update axios

* web: set proxy and fixed unique key in TokenDetails.tsx

Co-authored-by: Evan Gray <battledingo@gmail.com>
This commit is contained in:
ckeun 2022-11-17 17:30:11 -06:00 committed by GitHub
parent ff4f7f4ee7
commit e7c81723ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 649 additions and 11134 deletions

View File

@ -242,7 +242,12 @@ export async function getAlgoCustody(chainInfo, useAllowList = true) {
export async function grabAlgoCustodyData(chain, useAllowList) { export async function grabAlgoCustodyData(chain, useAllowList) {
const chainInfo = CHAIN_INFO_MAP[chain]; const chainInfo = CHAIN_INFO_MAP[chain];
const balances = await getAlgoCustody(chainInfo, useAllowList); var balances = [];
try {
balances = await getAlgoCustody(chainInfo, useAllowList);
} catch (e) {
console.log(`could not grab ${chainInfo.name} data`);
}
// await updateTable(chainInfo, balances); // await updateTable(chainInfo, balances);
const chainInfo_ = { const chainInfo_ = {
...chainInfo, ...chainInfo,

View File

@ -206,7 +206,12 @@ export async function getAptosCustody(chainInfo, useAllowList = true) {
export async function grabAptosCustodyData(chain, useAllowList) { export async function grabAptosCustodyData(chain, useAllowList) {
const chainInfo = CHAIN_INFO_MAP[chain]; const chainInfo = CHAIN_INFO_MAP[chain];
const balances = await getAptosCustody(chainInfo, useAllowList); var balances = [];
try {
balances = await getAptosCustody(chainInfo, useAllowList);
} catch (e) {
console.log(`could not grab ${chainInfo.name} data`);
}
// await updateTable(chainInfo, balances); // await updateTable(chainInfo, balances);
const chainInfo_ = { const chainInfo_ = {
...chainInfo, ...chainInfo,

View File

@ -348,7 +348,12 @@ export async function getEvmCustody(chainInfo, useAllowList = true) {
export async function grabEvmCustodyData(chain, useAllowList) { export async function grabEvmCustodyData(chain, useAllowList) {
const chainInfo = CHAIN_INFO_MAP[chain]; const chainInfo = CHAIN_INFO_MAP[chain];
const balances = await getEvmCustody(chainInfo, useAllowList); var balances = [];
try {
balances = await getEvmCustody(chainInfo, useAllowList);
} catch (e) {
console.log(`could not grab ${chainInfo.name} data`);
}
// await updateTable(chainInfo, balances); // await updateTable(chainInfo, balances);
const chainInfo_ = { const chainInfo_ = {
...chainInfo, ...chainInfo,

View File

@ -214,7 +214,12 @@ export async function getNearCustody(chainInfo, useAllowList = true) {
export async function grabNearCustodyData(chain, useAllowList) { export async function grabNearCustodyData(chain, useAllowList) {
const chainInfo = CHAIN_INFO_MAP[chain]; const chainInfo = CHAIN_INFO_MAP[chain];
const balances = await getNearCustody(chainInfo, useAllowList); var balances = [];
try {
balances = await getNearCustody(chainInfo, useAllowList);
} catch (e) {
console.log("could not grab Near data");
}
// await updateTable(chainInfo, balances); // await updateTable(chainInfo, balances);
const chainInfo_ = { const chainInfo_ = {
...chainInfo, ...chainInfo,

View File

@ -310,7 +310,12 @@ export async function getSolanaCustody(chainInfo, useAllowList = true) {
export async function grabSolanaCustodyData(chain, useAllowList) { export async function grabSolanaCustodyData(chain, useAllowList) {
const chainInfo = CHAIN_INFO_MAP[chain]; const chainInfo = CHAIN_INFO_MAP[chain];
const balances = await getSolanaCustody(chainInfo, useAllowList); var balances = [];
try {
balances = await getSolanaCustody(chainInfo, useAllowList);
} catch (e) {
console.log("could not grab Solana data");
}
if (balances.length === 0) { if (balances.length === 0) {
console.log(`could not get ${chainInfo.name} custody data`); console.log(`could not get ${chainInfo.name} custody data`);
} }

View File

@ -312,7 +312,12 @@ export async function getTerraCustody(chainInfo, useAllowList) {
export async function grabTerraCustodyData(chain, useAllowList) { export async function grabTerraCustodyData(chain, useAllowList) {
const chainInfo = CHAIN_INFO_MAP[chain]; const chainInfo = CHAIN_INFO_MAP[chain];
const balances = await getTerraCustody(chainInfo, useAllowList); var balances = [];
try {
balances = await getTerraCustody(chainInfo, useAllowList);
} catch (e) {
console.log("could not grab Solana data");
}
if (balances === undefined) { if (balances === undefined) {
console.log("could not pull terra balances"); console.log("could not pull terra balances");
return { balances: [] }; return { balances: [] };

View File

@ -177,8 +177,7 @@ export var CHAIN_INFO_MAP = {
name: "solana", name: "solana",
evm: false, evm: false,
chain_id: CHAIN_ID_SOLANA, chain_id: CHAIN_ID_SOLANA,
endpoint_url: endpoint_url: process.env.SOLANA_RPC || "https://rpc.ankr.com/solana",
process.env.SOLANA_RPC || "https://solana-api.projectserum.com",
core_bridge: "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth", core_bridge: "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth",
token_bridge_address: "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb", token_bridge_address: "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb",
custody_address: "GugU1tP7doLeTw9hQP51xRJyS8Da1fWxuiy2rVrnMD2m", custody_address: "GugU1tP7doLeTw9hQP51xRJyS8Da1fWxuiy2rVrnMD2m",

11562
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
"license": "Apache-2.0", "license": "Apache-2.0",
"proxy": "http://server:4000", "proxy": "http://server:4000",
"dependencies": { "dependencies": {
"@certusone/wormhole-sdk": "^0.7.6", "@certusone/wormhole-sdk": "^0.9.2",
"@certusone/wormhole-sdk-proto-web": "^0.0.4", "@certusone/wormhole-sdk-proto-web": "^0.0.4",
"@emotion/react": "^11.10.0", "@emotion/react": "^11.10.0",
"@emotion/styled": "^11.10.0", "@emotion/styled": "^11.10.0",
@ -23,7 +23,7 @@
"@types/react-dom": "^18.0.6", "@types/react-dom": "^18.0.6",
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
"assert": "^2.0.0", "assert": "^2.0.0",
"axios": "^0.27.2", "axios": "^1.1.3",
"binary-parser": "^2.2.1", "binary-parser": "^2.2.1",
"coingecko-api": "^1.0.10", "coingecko-api": "^1.0.10",
"dotenv": "^16.0.2", "dotenv": "^16.0.2",

View File

@ -12,26 +12,7 @@ import useEnqueuedVaaDetails, {
} from "../hooks/useEnqueuedVaaDetails"; } from "../hooks/useEnqueuedVaaDetails";
import Table from "./Table"; import Table from "./Table";
import numeral from "numeral"; import numeral from "numeral";
import EnqueuedVaaExists from "./EnqueuedVaaExists";
// async function VaaExists(row: EnqueuedVaaDetailsResponse) {
// try {
// const chainId: ChainId = findChainId(row.chainId.toString());
// const vaa = await getSignedVAAWithRetry(
// WORMHOLE_RPC_HOSTS,
// chainId,
// row.emitterAddress,
// row.sequence.toString(),
// { transport: NodeHttpTransport() },
// 1000,
// 4
// );
// if (vaa != undefined) {
// return true;
// }
// } catch (e) {
// return false;
// }
// }
const columnHelper = createColumnHelper<EnqueuedVaaDetailsResponse>(); const columnHelper = createColumnHelper<EnqueuedVaaDetailsResponse>();
@ -73,14 +54,11 @@ const columns = [
? new Date(info.getValue() * 1000).toLocaleString() ? new Date(info.getValue() * 1000).toLocaleString()
: null, : null,
}), }),
// columnHelper.display({ columnHelper.display({
// id: "hasQuorum", id: "hasQuorum",
// header: () => "Has Quorum?", header: () => "Has Quorum?",
// cell: (info) => { cell: (info) => EnqueuedVaaExists(info.row.original),
// const value = VaaExists(info.row.original); }),
// return value;
// },
// }),
columnHelper.accessor("txHash", { columnHelper.accessor("txHash", {
header: () => "Transaction", header: () => "Transaction",
cell: (info) => ( cell: (info) => (
@ -100,7 +78,8 @@ function EnqueuedVaaDetails(id: string) {
state: { state: {
sorting, sorting,
}, },
getRowId: (chain) => chain.chainId, getRowId: (vaa) =>
`${vaa.chainId}/${vaa.emitterAddress.slice(2)}/${vaa.sequence}`,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(), getSortedRowModel: getSortedRowModel(),
onSortingChange: setSorting, onSortingChange: setSorting,

View File

@ -0,0 +1,46 @@
import axios from "axios";
import { useEffect, useState } from "react";
import { EnqueuedVaaDetailsResponse } from "../hooks/useEnqueuedVaaDetails";
const VAA_CHECK_TIMEOUT = 60000;
function EnqueuedVaaExists(row: EnqueuedVaaDetailsResponse) {
const [vaaHasQuorum, setVaaHasQuorum] = useState<boolean | null>(null);
useEffect(() => {
let cancelled = false;
(async () => {
while (!cancelled) {
setVaaHasQuorum(null);
let result = false;
try {
const response = await axios.get(
`/api/vaas/${row.chainId}/${row.emitterAddress.slice(2)}/${
row.sequence
}`
);
if (response.data) result = true;
} catch (e) {}
if (!cancelled) {
setVaaHasQuorum(result);
if (result) {
cancelled = true;
return;
}
await new Promise((resolve) =>
setTimeout(resolve, VAA_CHECK_TIMEOUT)
);
}
}
})();
return () => {
cancelled = true;
};
}, [row]);
return (
<span role="img">
{vaaHasQuorum === null ? "⏳" : vaaHasQuorum ? "✅" : "❌"}
</span>
);
}
export default EnqueuedVaaExists;

View File

@ -46,34 +46,34 @@ const columns = [
header: () => "Sequence", header: () => "Sequence",
cell: (info) => info.getValue().split("/")[2], cell: (info) => info.getValue().split("/")[2],
}), }),
columnHelper.accessor("vaa", { columnHelper.accessor("vaas", {
id: "type", id: "type",
header: () => "Type", header: () => "Type",
cell: (info) => cell: (info) =>
vaa.parse(Buffer.from(info.getValue(), "base64")).payload.type, vaa.parse(Buffer.from(info.getValue(), "base64")).payload.type,
}), }),
columnHelper.accessor("vaa", { columnHelper.accessor("vaas", {
id: "chain", id: "chain",
header: () => "Chain", header: () => "Chain",
cell: (info) => cell: (info) =>
(vaa.parse(Buffer.from(info.getValue(), "base64")).payload as any) (vaa.parse(Buffer.from(info.getValue(), "base64")).payload as any)
.chain || "", .chain || "",
}), }),
columnHelper.accessor("vaa", { columnHelper.accessor("vaas", {
id: "address", id: "address",
header: () => "Address", header: () => "Address",
cell: (info) => cell: (info) =>
(vaa.parse(Buffer.from(info.getValue(), "base64")).payload as any) (vaa.parse(Buffer.from(info.getValue(), "base64")).payload as any)
.address || "", .address || "",
}), }),
columnHelper.accessor("vaa", { columnHelper.accessor("vaas", {
id: "module", id: "module",
header: () => "Module", header: () => "Module",
cell: (info) => cell: (info) =>
(vaa.parse(Buffer.from(info.getValue(), "base64")).payload as any) (vaa.parse(Buffer.from(info.getValue(), "base64")).payload as any)
.module || "", .module || "",
}), }),
columnHelper.accessor("createdAt", { columnHelper.accessor("updatedAt", {
header: () => "Observed At", header: () => "Observed At",
cell: (info) => new Date(info.getValue()).toLocaleString(), cell: (info) => new Date(info.getValue()).toLocaleString(),
}), }),
@ -95,7 +95,7 @@ const obsColumns = [
function VAADetails({ row }: { row: Row<VAAsResponse> }): ReactElement { function VAADetails({ row }: { row: Row<VAAsResponse> }): ReactElement {
return ( return (
<Typography variant="body2" sx={{ wordBreak: "break-all" }}> <Typography variant="body2" sx={{ wordBreak: "break-all" }}>
{Buffer.from(row.original.vaa, "base64").toString("hex")} {Buffer.from(row.original.vaas, "base64").toString("hex")}
</Typography> </Typography>
); );
} }
@ -118,10 +118,10 @@ function Governance() {
// NOTE: this ignores differing digests // NOTE: this ignores differing digests
Object.entries( Object.entries(
obs.reduce((obvsById, o) => { obs.reduce((obvsById, o) => {
if (!obvsById[o.messageid]) { if (!obvsById[o.messageId]) {
obvsById[o.messageid] = []; obvsById[o.messageId] = [];
} }
obvsById[o.messageid].push(o); obvsById[o.messageId].push(o);
return obvsById; return obvsById;
}, {} as any) }, {} as any)
).map(([key, val]: [string, any]) => ({ ).map(([key, val]: [string, any]) => ({

View File

@ -1,5 +1,6 @@
import { ChainId, tryHexToNativeString } from "@certusone/wormhole-sdk"; import { ChainId, tryHexToNativeString } from "@certusone/wormhole-sdk";
import { _parseVAAAlgorand } from "@certusone/wormhole-sdk/lib/esm/algorand/Algorand"; import { parseVaa } from "@certusone/wormhole-sdk/lib/cjs/vaa";
import { parseTransferPayload } from "@certusone/wormhole-sdk/lib/cjs/utils";
import { ChevronRight } from "@mui/icons-material"; import { ChevronRight } from "@mui/icons-material";
import { Card, IconButton, Typography } from "@mui/material"; import { Card, IconButton, Typography } from "@mui/material";
import { Box } from "@mui/system"; import { Box } from "@mui/system";
@ -61,16 +62,18 @@ const columns = [
]; ];
function VAADetails({ row }: { row: Row<VAAsResponse> }): ReactElement { function VAADetails({ row }: { row: Row<VAAsResponse> }): ReactElement {
const parsedVaa = _parseVAAAlgorand( const parsedVaa = parseVaa(
new Uint8Array(Buffer.from(row.original.vaa, "base64")) new Uint8Array(Buffer.from(row.original.vaas, "base64"))
); );
let token = parsedVaa.Contract; const payload = parsedVaa.payload;
const parsedPayload = parseTransferPayload(payload);
let token = parsedPayload.originAddress;
// FromChain is a misnomer - actually OriginChain // FromChain is a misnomer - actually OriginChain
if (parsedVaa.Contract && parsedVaa.FromChain) if (parsedPayload.originAddress && parsedPayload.originChain)
try { try {
token = tryHexToNativeString( token = tryHexToNativeString(
parsedVaa.Contract, parsedPayload.originAddress,
parsedVaa.FromChain as ChainId parsedPayload.originChain as ChainId
); );
} catch (e) {} } catch (e) {}
return ( return (
@ -79,15 +82,15 @@ function VAADetails({ row }: { row: Row<VAAsResponse> }): ReactElement {
<br /> <br />
Timestamp: {new Date(parsedVaa.timestamp * 1000).toLocaleString()} Timestamp: {new Date(parsedVaa.timestamp * 1000).toLocaleString()}
<br /> <br />
Consistency: {parsedVaa.consistency} Consistency: {parsedVaa.consistencyLevel}
<br /> <br />
Nonce: {parsedVaa.nonce} Nonce: {parsedVaa.nonce}
<br /> <br />
Origin: {parsedVaa.FromChain} Origin: {parsedPayload.originChain}
<br /> <br />
Token: {token} Token: {token}
<br /> <br />
Amount: {BigNumber.from(parsedVaa.Amount).toString()} Amount: {BigNumber.from(parsedPayload.amount).toString()}
<br /> <br />
</> </>
); );

View File

@ -67,7 +67,7 @@ function TokenDetails(id: string) {
state: { state: {
sorting, sorting,
}, },
getRowId: (token) => token.name, getRowId: (token) => token.tokenAddress,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(), getSortedRowModel: getSortedRowModel(),
onSortingChange: setSorting, onSortingChange: setSorting,

View File

@ -1,5 +1,9 @@
import { ChainId, tryHexToNativeString } from "@certusone/wormhole-sdk"; import {
import { _parseVAAAlgorand } from "@certusone/wormhole-sdk/lib/esm/algorand/Algorand"; ChainId,
parseTransferPayload,
parseVaa,
tryHexToNativeString,
} from "@certusone/wormhole-sdk";
import { ChevronRight } from "@mui/icons-material"; import { ChevronRight } from "@mui/icons-material";
import { Card, IconButton, Typography } from "@mui/material"; import { Card, IconButton, Typography } from "@mui/material";
import { Box } from "@mui/system"; import { Box } from "@mui/system";
@ -63,16 +67,18 @@ const columns = [
]; ];
function VAADetails({ row }: { row: Row<VAAsResponse> }): ReactElement { function VAADetails({ row }: { row: Row<VAAsResponse> }): ReactElement {
const parsedVaa = _parseVAAAlgorand( const parsedVaa = parseVaa(
new Uint8Array(Buffer.from(row.original.vaa, "base64")) new Uint8Array(Buffer.from(row.original.vaas, "base64"))
); );
let token = parsedVaa.Contract; const payload = parsedVaa.payload;
const parsedPayload = parseTransferPayload(payload);
let token = parsedPayload.originAddress;
// FromChain is a misnomer - actually OriginChain // FromChain is a misnomer - actually OriginChain
if (parsedVaa.Contract && parsedVaa.FromChain) if (parsedPayload.originAddress && parsedPayload.originChain)
try { try {
token = tryHexToNativeString( token = tryHexToNativeString(
parsedVaa.Contract, parsedPayload.originAddress,
parsedVaa.FromChain as ChainId parsedPayload.originChain as ChainId
); );
} catch (e) {} } catch (e) {}
return ( return (
@ -81,15 +87,15 @@ function VAADetails({ row }: { row: Row<VAAsResponse> }): ReactElement {
<br /> <br />
Timestamp: {new Date(parsedVaa.timestamp * 1000).toLocaleString()} Timestamp: {new Date(parsedVaa.timestamp * 1000).toLocaleString()}
<br /> <br />
Consistency: {parsedVaa.consistency} Consistency: {parsedVaa.consistencyLevel}
<br /> <br />
Nonce: {parsedVaa.nonce} Nonce: {parsedVaa.nonce}
<br /> <br />
Origin: {parsedVaa.FromChain} Origin: {parsedPayload.originChain}
<br /> <br />
Token: {token} Token: {token}
<br /> <br />
Amount: {BigNumber.from(parsedVaa.Amount).toString()} Amount: {BigNumber.from(parsedPayload.amount).toString()}
<br /> <br />
</> </>
); );

View File

@ -5,7 +5,7 @@ import { POLL_TIME } from "../utils/consts";
export type EnqueuedVaa = { export type EnqueuedVaa = {
chainId: number; chainId: number;
emitterAddres: string; emitterAddress: string;
sequence: number; sequence: number;
notionalValue: number; notionalValue: number;
txHash: string; txHash: string;

View File

@ -14,7 +14,7 @@ export type HeartbeatNetwork = {
export type HeartbeatResponse = { export type HeartbeatResponse = {
boottimestamp: NumberLong; boottimestamp: NumberLong;
counter: number; counter: number;
createdAt: string; indexedAt: string;
features: string[] | null; features: string[] | null;
guardianaddr: string; guardianaddr: string;
networks: HeartbeatNetwork[]; networks: HeartbeatNetwork[];

View File

@ -4,11 +4,11 @@ import { useNetworkContext } from "../contexts/NetworkContext";
import { POLL_TIME } from "../utils/consts"; import { POLL_TIME } from "../utils/consts";
export type ObservationsResponse = { export type ObservationsResponse = {
createdAt: string; indexedAt: string;
updatedAt: string; updatedAt: string;
addr: string; addr: string;
hash: string; hash: string;
messageid: string; messageId: string;
signature: string; signature: string;
txhash: string; txhash: string;
_id: string; _id: string;

View File

@ -4,9 +4,15 @@ import { useNetworkContext } from "../contexts/NetworkContext";
import { POLL_TIME } from "../utils/consts"; import { POLL_TIME } from "../utils/consts";
export type VAAsResponse = { export type VAAsResponse = {
createdAt: string; indexedAt: string;
updatedAt: string; updatedAt: string;
vaa: string; emitterAddr: string;
emitterChain: number;
guardianSetIndex: number;
sequence: number;
timestamp: string;
version: number;
vaas: string;
_id: string; _id: string;
}; };