diff --git a/explorer/src/components/account/TokenAccountSection.tsx b/explorer/src/components/account/TokenAccountSection.tsx index 35f6db3041..73ab017969 100644 --- a/explorer/src/components/account/TokenAccountSection.tsx +++ b/explorer/src/components/account/TokenAccountSection.tsx @@ -18,6 +18,19 @@ import { useTokenRegistry } from "providers/mints/token-registry"; import { BigNumber } from "bignumber.js"; import { Copyable } from "components/common/Copyable"; +const getEthAddress = (link?: string) => { + let address = ""; + if (link) { + const extractEth = link.match(/0x[a-fA-F0-9]{40,64}/); + + if (extractEth) { + address = extractEth[0]; + } + } + + return address; +}; + export function TokenAccountSection({ account, tokenAccount, @@ -62,16 +75,12 @@ function MintAccountCard({ const tokenInfo = tokenRegistry.get(mintAddress); - let bridgeContractAddress = tokenInfo?.extensions?.address; - if (tokenInfo?.extensions?.bridgeContract && !bridgeContractAddress) { - const extractEth = tokenInfo.extensions.bridgeContract.match( - /0x[a-fA-F0-9]{40,64}/ - ); - - if (extractEth) { - bridgeContractAddress = extractEth[0]; - } - } + const bridgeContractAddress = getEthAddress( + tokenInfo?.extensions?.bridgeContract + ); + const assetContractAddress = getEthAddress( + tokenInfo?.extensions?.assetContract + ); return (
@@ -145,7 +154,7 @@ function MintAccountCard({ )} {tokenInfo?.extensions?.bridgeContract && bridgeContractAddress && ( - Wormhole Bridge Contract + Bridge Contract )} + {tokenInfo?.extensions?.assetContract && assetContractAddress && ( + + Bridged Asset Contract + + + + {assetContractAddress} + + + + + )}
); diff --git a/explorer/src/components/instruction/WormholeDetailsCard.tsx b/explorer/src/components/instruction/WormholeDetailsCard.tsx new file mode 100644 index 0000000000..a7690edc8a --- /dev/null +++ b/explorer/src/components/instruction/WormholeDetailsCard.tsx @@ -0,0 +1,46 @@ +import React from "react"; +import { TransactionInstruction, SignatureResult } from "@solana/web3.js"; +import { InstructionCard } from "./InstructionCard"; +import { useCluster } from "providers/cluster"; +import { reportError } from "utils/sentry"; +import { parsWormholeInstructionTitle } from "./wormhole/types"; + +export function WormholeDetailsCard({ + ix, + index, + result, + signature, + innerCards, + childIndex, +}: { + ix: TransactionInstruction; + index: number; + result: SignatureResult; + signature: string; + innerCards?: JSX.Element[]; + childIndex?: number; +}) { + const { url } = useCluster(); + + let title; + try { + title = parsWormholeInstructionTitle(ix); + } catch (error) { + reportError(error, { + url: url, + signature: signature, + }); + } + + return ( + + ); +} diff --git a/explorer/src/components/instruction/token-lending/types.ts b/explorer/src/components/instruction/token-lending/types.ts index a635ed4c50..3586f2337d 100644 --- a/explorer/src/components/instruction/token-lending/types.ts +++ b/explorer/src/components/instruction/token-lending/types.ts @@ -28,7 +28,7 @@ export function parseTokenLendingInstructionTitle( const code = instruction.data[0]; if (!(code in INSTRUCTION_LOOKUP)) { - throw new Error(`Unrecognized Token Swap instruction code: ${code}`); + throw new Error(`Unrecognized Token Lending instruction code: ${code}`); } return INSTRUCTION_LOOKUP[code]; diff --git a/explorer/src/components/instruction/wormhole/types.ts b/explorer/src/components/instruction/wormhole/types.ts new file mode 100644 index 0000000000..15e132aa50 --- /dev/null +++ b/explorer/src/components/instruction/wormhole/types.ts @@ -0,0 +1,34 @@ +import { TransactionInstruction } from "@solana/web3.js"; + +export const PROGRAM_IDS: string[] = [ + "WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC", // mainnet / testnet / devnet +]; + +const INSTRUCTION_LOOKUP: { [key: number]: string } = { + 0: "Initialize Bridge", + 1: "Transfer Assets Out", + 2: "Post VAA", + 3: "Evict Transfer Proposal", + 4: "Evict Claimed VAA", + 5: "Poke Proposal", + 6: "Verify Signatures", + 7: "Create Wrapped Asset", +}; + +export function isWormholeInstruction( + instruction: TransactionInstruction +): boolean { + return PROGRAM_IDS.includes(instruction.programId.toBase58()); +} + +export function parsWormholeInstructionTitle( + instruction: TransactionInstruction +): string { + const code = instruction.data[0]; + + if (!(code in INSTRUCTION_LOOKUP)) { + throw new Error(`Unrecognized Wormhole instruction code: ${code}`); + } + + return INSTRUCTION_LOOKUP[code]; +} diff --git a/explorer/src/components/transaction/InstructionsSection.tsx b/explorer/src/components/transaction/InstructionsSection.tsx index 0c8385a6d2..503352b9fb 100644 --- a/explorer/src/components/transaction/InstructionsSection.tsx +++ b/explorer/src/components/transaction/InstructionsSection.tsx @@ -18,6 +18,7 @@ import { SystemDetailsCard } from "components/instruction/system/SystemDetailsCa import { TokenDetailsCard } from "components/instruction/token/TokenDetailsCard"; import { TokenLendingDetailsCard } from "components/instruction/TokenLendingDetailsCard"; import { TokenSwapDetailsCard } from "components/instruction/TokenSwapDetailsCard"; +import { WormholeDetailsCard } from "components/instruction/WormholeDetailsCard"; import { UnknownDetailsCard } from "components/instruction/UnknownDetailsCard"; import { SignatureProps, @@ -35,6 +36,7 @@ import { import { Cluster, useCluster } from "providers/cluster"; import { BpfUpgradeableLoaderDetailsCard } from "components/instruction/bpf-upgradeable-loader/BpfUpgradeableLoaderDetailsCard"; import { VoteDetailsCard } from "components/instruction/vote/VoteDetailsCard"; +import { isWormholeInstruction } from "components/instruction/wormhole/types"; export type InstructionDetailsProps = { tx: ParsedTransaction; @@ -212,6 +214,8 @@ function renderInstructionCard({ return ; } else if (isTokenLendingInstruction(transactionIx)) { return ; + } else if (isWormholeInstruction(transactionIx)) { + return ; } else { return ; } diff --git a/explorer/src/utils/tx.ts b/explorer/src/utils/tx.ts index 4766687587..846c02550d 100644 --- a/explorer/src/utils/tx.ts +++ b/explorer/src/utils/tx.ts @@ -14,6 +14,7 @@ import { Transaction, PartiallyDecodedInstruction, ParsedInstruction, + Secp256k1Program, } from "@solana/web3.js"; import { Cluster } from "providers/cluster"; import { SerumMarketRegistry } from "serumMarketRegistry"; @@ -27,6 +28,7 @@ export enum PROGRAM_NAMES { STAKE = "Stake Program", SYSTEM = "System Program", VOTE = "Vote Program", + SECP256K1 = "Secp256k1 Program", // spl ASSOCIATED_TOKEN = "Associated Token Program", @@ -38,6 +40,7 @@ export enum PROGRAM_NAMES { TOKEN = "Token Program", // other + WORMHOLE = "Wormhole", BONFIDA_POOL = "Bonfida Pool Program", BREAK_SOLANA = "Break Solana Program", RAYDIUM_LIQUIDITY_1 = "Raydium Liquidity Pool Program v1", @@ -65,6 +68,7 @@ export const PROGRAM_DEPLOYMENTS = { [PROGRAM_NAMES.STAKE]: ALL_CLUSTERS, [PROGRAM_NAMES.SYSTEM]: ALL_CLUSTERS, [PROGRAM_NAMES.VOTE]: ALL_CLUSTERS, + [PROGRAM_NAMES.SECP256K1]: ALL_CLUSTERS, // spl [PROGRAM_NAMES.ASSOCIATED_TOKEN]: ALL_CLUSTERS, @@ -76,6 +80,7 @@ export const PROGRAM_DEPLOYMENTS = { [PROGRAM_NAMES.TOKEN]: ALL_CLUSTERS, // other + [PROGRAM_NAMES.WORMHOLE]: MAINNET_ONLY, [PROGRAM_NAMES.BONFIDA_POOL]: MAINNET_ONLY, [PROGRAM_NAMES.BREAK_SOLANA]: LIVE_CLUSTERS, [PROGRAM_NAMES.RAYDIUM_LIQUIDITY_1]: MAINNET_ONLY, @@ -92,6 +97,7 @@ export const PROGRAM_NAME_BY_ID = { [StakeProgram.programId.toBase58()]: PROGRAM_NAMES.STAKE, [SystemProgram.programId.toBase58()]: PROGRAM_NAMES.SYSTEM, [VOTE_PROGRAM_ID.toBase58()]: PROGRAM_NAMES.VOTE, + [Secp256k1Program.programId.toBase58()]: PROGRAM_NAMES.SECP256K1, // spl ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL: PROGRAM_NAMES.ASSOCIATED_TOKEN, @@ -103,6 +109,7 @@ export const PROGRAM_NAME_BY_ID = { LendZqTs7gn5CTSJU1jWKhKuVpjJGom45nnwPb2AMTi: PROGRAM_NAMES.LENDING, // other + WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC: PROGRAM_NAMES.WORMHOLE, WvmTNLpGMVbwJVYztYL4Hnsy82cJhQorxjnnXcRm3b6: PROGRAM_NAMES.BONFIDA_POOL, BrEAK7zGZ6dM71zUDACDqJnekihmwF15noTddWTsknjC: PROGRAM_NAMES.BREAK_SOLANA, RVKd61ztZW9GUwhRbbLoYVRE5Xf1B2tVscKqwZqXgEr: @@ -161,7 +168,7 @@ export function tokenLabel( if (tokenInfo.name === tokenInfo.symbol) { return tokenInfo.name; } - return `${tokenInfo.name} (${tokenInfo.symbol})`; + return `${tokenInfo.symbol} - ${tokenInfo.name}`; } export function addressLabel(