Refactor instruction details components

This commit is contained in:
Justin Starry 2020-05-08 23:36:41 +08:00 committed by Michael Vines
parent 285ae1481f
commit 13af01dcc4
6 changed files with 313 additions and 300 deletions

View File

@ -1,5 +1,4 @@
import React from "react";
import bs58 from "bs58";
import {
Source,
useFetchTransactionStatus,
@ -10,19 +9,15 @@ import {
} from "../providers/transactions";
import { fetchDetails } from "providers/transactions/details";
import { useCluster, useClusterModal } from "providers/cluster";
import {
TransactionSignature,
TransactionInstruction,
TransferParams,
CreateAccountParams,
SystemProgram,
SignatureResult
} from "@solana/web3.js";
import { TransactionSignature, SystemInstruction } from "@solana/web3.js";
import ClusterStatusButton from "components/ClusterStatusButton";
import { lamportsToSolString } from "utils";
import { displayAddress, decodeCreate, decodeTransfer } from "utils/tx";
import { displayAddress } from "utils/tx";
import Copyable from "./Copyable";
import { useHistory, useLocation } from "react-router-dom";
import { TransferDetailsCard } from "./instruction/TransferDetailsCard";
import { CreateDetailsCard } from "./instruction/CreateDetailsCard";
import { RawDetailsCard } from "./instruction/RawDetailsCard";
type Props = { signature: TransactionSignature };
export default function TransactionDetails({ signature }: Props) {
@ -265,50 +260,6 @@ function AccountsCard({ signature }: Props) {
);
}
function ixResult(result: SignatureResult, index: number) {
if (result.err) {
const err = result.err as any;
const ixError = err["InstructionError"];
if (ixError && Array.isArray(ixError)) {
const [errorIndex, error] = ixError;
if (Number.isInteger(errorIndex) && errorIndex === index) {
return ["warning", `Error: ${JSON.stringify(error)}`];
}
}
return ["dark"];
}
return ["success"];
}
type InstructionProps = {
title: string;
children: React.ReactNode;
result: SignatureResult;
index: number;
};
function InstructionCard({ title, children, result, index }: InstructionProps) {
const [resultClass, errorString] = ixResult(result, index);
return (
<div className="card">
<div className="card-header">
<h3 className="card-header-title mb-0 d-flex align-items-center">
<span className={`badge badge-soft-${resultClass} mr-2`}>
#{index + 1}
</span>
{title}
</h3>
<h3 className="mb-0">
<span className="badge badge-soft-warning text-monospace">
{errorString}
</span>
</h3>
</div>
<TableCardBody>{children}</TableCardBody>
</div>
);
}
function InstructionsSection({ signature }: Props) {
const status = useTransactionStatus(signature);
const details = useTransactionDetails(signature);
@ -325,39 +276,24 @@ function InstructionsSection({ signature }: Props) {
const result = status.info.result;
const instructionDetails = transaction.instructions.map((ix, index) => {
const transfer = decodeTransfer(ix);
if (transfer) {
return (
<InstructionCard
key={index}
title="Transfer"
result={result}
index={index}
>
<TransferDetails ix={ix} transfer={transfer} />
</InstructionCard>
);
const props = { ix, result, index };
let instructionType;
try {
instructionType = SystemInstruction.decodeInstructionType(ix);
} catch (err) {
console.error(err);
return <RawDetailsCard {...props} />;
}
const create = decodeCreate(ix);
if (create) {
return (
<InstructionCard
key={index}
title="Create Account"
result={result}
index={index}
>
<CreateDetails ix={ix} create={create} />
</InstructionCard>
);
switch (instructionType) {
case "Transfer":
return <TransferDetailsCard {...props} />;
case "Create":
return <CreateDetailsCard {...props} />;
default:
return <RawDetailsCard {...props} />;
}
return (
<InstructionCard key={index} title="Raw" result={result} index={index}>
<RawDetails ix={ix} />
</InstructionCard>
);
});
return (
@ -374,190 +310,6 @@ function InstructionsSection({ signature }: Props) {
);
}
function TransferDetails({
ix,
transfer
}: {
ix: TransactionInstruction;
transfer: TransferParams;
}) {
const from = transfer.fromPubkey.toBase58();
const to = transfer.toPubkey.toBase58();
const [fromMeta, toMeta] = ix.keys;
return (
<>
<tr>
<td>Program</td>
<td className="text-right">
<Copyable bottom text={SystemProgram.programId.toBase58()}>
<code>{displayAddress(SystemProgram.programId)}</code>
</Copyable>
</td>
</tr>
<tr>
<td>
<div className="mr-2 d-md-inline">From Address</div>
{!fromMeta.isWritable && (
<span className="badge badge-soft-dark mr-1">Readonly</span>
)}
{fromMeta.isSigner && (
<span className="badge badge-soft-dark mr-1">Signer</span>
)}
</td>
<td className="text-right">
<Copyable text={from}>
<code>{from}</code>
</Copyable>
</td>
</tr>
<tr>
<td>
<div className="mr-2 d-md-inline">To Address</div>
{!toMeta.isWritable && (
<span className="badge badge-soft-dark mr-1">Readonly</span>
)}
{toMeta.isSigner && (
<span className="badge badge-soft-dark mr-1">Signer</span>
)}
</td>
<td className="text-right">
<Copyable text={to}>
<code>{to}</code>
</Copyable>
</td>
</tr>
<tr>
<td>Transfer Amount (SOL)</td>
<td className="text-right">{lamportsToSolString(transfer.lamports)}</td>
</tr>
</>
);
}
function CreateDetails({
ix,
create
}: {
ix: TransactionInstruction;
create: CreateAccountParams;
}) {
const from = create.fromPubkey.toBase58();
const newKey = create.newAccountPubkey.toBase58();
const [fromMeta, newMeta] = ix.keys;
return (
<>
<tr>
<td>Program</td>
<td className="text-right">
<Copyable bottom text={SystemProgram.programId.toBase58()}>
<code>{displayAddress(SystemProgram.programId)}</code>
</Copyable>
</td>
</tr>
<tr>
<td>
<div className="mr-2 d-md-inline">From Address</div>
{!fromMeta.isWritable && (
<span className="badge badge-soft-dark mr-1">Readonly</span>
)}
{fromMeta.isSigner && (
<span className="badge badge-soft-dark mr-1">Signer</span>
)}
</td>
<td className="text-right">
<Copyable text={from}>
<code>{from}</code>
</Copyable>
</td>
</tr>
<tr>
<td>
<div className="mr-2 d-md-inline">New Address</div>
{!newMeta.isWritable && (
<span className="badge badge-soft-dark mr-1">Readonly</span>
)}
{newMeta.isSigner && (
<span className="badge badge-soft-dark mr-1">Signer</span>
)}
</td>
<td className="text-right">
<Copyable text={newKey}>
<code>{newKey}</code>
</Copyable>
</td>
</tr>
<tr>
<td>Transfer Amount (SOL)</td>
<td className="text-right">{lamportsToSolString(create.lamports)}</td>
</tr>
<tr>
<td>Allocated Space (Bytes)</td>
<td className="text-right">{create.space}</td>
</tr>
<tr>
<td>Assigned Owner</td>
<td className="text-right">
<Copyable text={create.programId.toBase58()}>
<code>{displayAddress(create.programId)}</code>
</Copyable>
</td>
</tr>
</>
);
}
function RawDetails({ ix }: { ix: TransactionInstruction }) {
return (
<>
<tr>
<td>Program</td>
<td className="text-right">
<Copyable bottom text={ix.programId.toBase58()}>
<code>{displayAddress(ix.programId)}</code>
</Copyable>
</td>
</tr>
{ix.keys.map(({ pubkey, isSigner, isWritable }, keyIndex) => (
<tr key={keyIndex}>
<td>
<div className="mr-2 d-md-inline">Account #{keyIndex + 1}</div>
{!isWritable && (
<span className="badge badge-soft-dark mr-1">Readonly</span>
)}
{isSigner && (
<span className="badge badge-soft-dark mr-1">Signer</span>
)}
</td>
<td className="text-right">
<Copyable text={pubkey.toBase58()}>
<code>{pubkey.toBase58()}</code>
</Copyable>
</td>
</tr>
))}
<tr>
<td>Raw Data (Base58)</td>
<td className="text-right">
<Copyable text={bs58.encode(ix.data)}>
<code>{bs58.encode(ix.data)}</code>
</Copyable>
</td>
</tr>
</>
);
}
function LoadingCard() {
return (
<div className="card">

View File

@ -0,0 +1,98 @@
import React from "react";
import {
TransactionInstruction,
SystemProgram,
SignatureResult,
SystemInstruction
} from "@solana/web3.js";
import { lamportsToSolString } from "utils";
import { displayAddress } from "utils/tx";
import { InstructionCard } from "./InstructionCard";
import Copyable from "components/Copyable";
import { RawDetailsCard } from "./RawDetailsCard";
export function CreateDetailsCard(props: {
ix: TransactionInstruction;
index: number;
result: SignatureResult;
}) {
const { ix, index, result } = props;
let create;
try {
create = SystemInstruction.decodeCreateAccount(ix);
} catch (err) {
console.error(err);
return <RawDetailsCard {...props} />;
}
const from = create.fromPubkey.toBase58();
const newKey = create.newAccountPubkey.toBase58();
const [fromMeta, newMeta] = ix.keys;
return (
<InstructionCard index={index} result={result} title="Create Account">
<tr>
<td>Program</td>
<td className="text-right">
<Copyable bottom text={SystemProgram.programId.toBase58()}>
<code>{displayAddress(SystemProgram.programId)}</code>
</Copyable>
</td>
</tr>
<tr>
<td>
<div className="mr-2 d-md-inline">From Address</div>
{!fromMeta.isWritable && (
<span className="badge badge-soft-dark mr-1">Readonly</span>
)}
{fromMeta.isSigner && (
<span className="badge badge-soft-dark mr-1">Signer</span>
)}
</td>
<td className="text-right">
<Copyable text={from}>
<code>{from}</code>
</Copyable>
</td>
</tr>
<tr>
<td>
<div className="mr-2 d-md-inline">New Address</div>
{!newMeta.isWritable && (
<span className="badge badge-soft-dark mr-1">Readonly</span>
)}
{newMeta.isSigner && (
<span className="badge badge-soft-dark mr-1">Signer</span>
)}
</td>
<td className="text-right">
<Copyable text={newKey}>
<code>{newKey}</code>
</Copyable>
</td>
</tr>
<tr>
<td>Transfer Amount (SOL)</td>
<td className="text-right">{lamportsToSolString(create.lamports)}</td>
</tr>
<tr>
<td>Allocated Space (Bytes)</td>
<td className="text-right">{create.space}</td>
</tr>
<tr>
<td>Assigned Owner</td>
<td className="text-right">
<Copyable text={create.programId.toBase58()}>
<code>{displayAddress(create.programId)}</code>
</Copyable>
</td>
</tr>
</InstructionCard>
);
}

View File

@ -0,0 +1,55 @@
import React from "react";
import { SignatureResult } from "@solana/web3.js";
type InstructionProps = {
title: string;
children: React.ReactNode;
result: SignatureResult;
index: number;
};
export function InstructionCard({
title,
children,
result,
index
}: InstructionProps) {
const [resultClass, errorString] = ixResult(result, index);
return (
<div className="card">
<div className="card-header">
<h3 className="card-header-title mb-0 d-flex align-items-center">
<span className={`badge badge-soft-${resultClass} mr-2`}>
#{index + 1}
</span>
{title}
</h3>
<h3 className="mb-0">
<span className="badge badge-soft-warning text-monospace">
{errorString}
</span>
</h3>
</div>
<div className="table-responsive mb-0">
<table className="table table-sm table-nowrap card-table">
<tbody className="list">{children}</tbody>
</table>
</div>
</div>
);
}
function ixResult(result: SignatureResult, index: number) {
if (result.err) {
const err = result.err as any;
const ixError = err["InstructionError"];
if (ixError && Array.isArray(ixError)) {
const [errorIndex, error] = ixError;
if (Number.isInteger(errorIndex) && errorIndex === index) {
return ["warning", `Error: ${JSON.stringify(error)}`];
}
}
return ["dark"];
}
return ["success"];
}

View File

@ -0,0 +1,57 @@
import React from "react";
import bs58 from "bs58";
import { TransactionInstruction, SignatureResult } from "@solana/web3.js";
import { displayAddress } from "utils/tx";
import { InstructionCard } from "./InstructionCard";
import Copyable from "components/Copyable";
export function RawDetailsCard({
ix,
index,
result
}: {
ix: TransactionInstruction;
index: number;
result: SignatureResult;
}) {
return (
<InstructionCard index={index} result={result} title="Raw">
<tr>
<td>Program</td>
<td className="text-right">
<Copyable bottom text={ix.programId.toBase58()}>
<code>{displayAddress(ix.programId)}</code>
</Copyable>
</td>
</tr>
{ix.keys.map(({ pubkey, isSigner, isWritable }, keyIndex) => (
<tr key={keyIndex}>
<td>
<div className="mr-2 d-md-inline">Account #{keyIndex + 1}</div>
{!isWritable && (
<span className="badge badge-soft-dark mr-1">Readonly</span>
)}
{isSigner && (
<span className="badge badge-soft-dark mr-1">Signer</span>
)}
</td>
<td className="text-right">
<Copyable text={pubkey.toBase58()}>
<code>{pubkey.toBase58()}</code>
</Copyable>
</td>
</tr>
))}
<tr>
<td>Raw Data (Base58)</td>
<td className="text-right">
<Copyable text={bs58.encode(ix.data)}>
<code>{bs58.encode(ix.data)}</code>
</Copyable>
</td>
</tr>
</InstructionCard>
);
}

View File

@ -0,0 +1,83 @@
import React from "react";
import {
TransactionInstruction,
SystemProgram,
SignatureResult,
SystemInstruction
} from "@solana/web3.js";
import { lamportsToSolString } from "utils";
import { displayAddress } from "utils/tx";
import { InstructionCard } from "./InstructionCard";
import Copyable from "components/Copyable";
import { RawDetailsCard } from "./RawDetailsCard";
export function TransferDetailsCard(props: {
ix: TransactionInstruction;
index: number;
result: SignatureResult;
}) {
const { ix, index, result } = props;
let transfer;
try {
transfer = SystemInstruction.decodeTransfer(ix);
} catch (err) {
console.error(err);
return <RawDetailsCard {...props} />;
}
const from = transfer.fromPubkey.toBase58();
const to = transfer.toPubkey.toBase58();
const [fromMeta, toMeta] = ix.keys;
return (
<InstructionCard index={index} result={result} title="Transfer">
<tr>
<td>Program</td>
<td className="text-right">
<Copyable bottom text={SystemProgram.programId.toBase58()}>
<code>{displayAddress(SystemProgram.programId)}</code>
</Copyable>
</td>
</tr>
<tr>
<td>
<div className="mr-2 d-md-inline">From Address</div>
{!fromMeta.isWritable && (
<span className="badge badge-soft-dark mr-1">Readonly</span>
)}
{fromMeta.isSigner && (
<span className="badge badge-soft-dark mr-1">Signer</span>
)}
</td>
<td className="text-right">
<Copyable text={from}>
<code>{from}</code>
</Copyable>
</td>
</tr>
<tr>
<td>
<div className="mr-2 d-md-inline">To Address</div>
{!toMeta.isWritable && (
<span className="badge badge-soft-dark mr-1">Readonly</span>
)}
{toMeta.isSigner && (
<span className="badge badge-soft-dark mr-1">Signer</span>
)}
</td>
<td className="text-right">
<Copyable text={to}>
<code>{to}</code>
</Copyable>
</td>
</tr>
<tr>
<td>Transfer Amount (SOL)</td>
<td className="text-right">{lamportsToSolString(transfer.lamports)}</td>
</tr>
</InstructionCard>
);
}

View File

@ -4,10 +4,6 @@ import {
StakeProgram,
VOTE_PROGRAM_ID,
BpfLoader,
TransferParams,
SystemInstruction,
CreateAccountParams,
TransactionInstruction,
SYSVAR_CLOCK_PUBKEY,
SYSVAR_RENT_PUBKEY,
SYSVAR_REWARDS_PUBKEY,
@ -53,31 +49,3 @@ export function displayAddress(pubkey: PublicKey): string {
address
);
}
export function decodeTransfer(
ix: TransactionInstruction
): TransferParams | null {
if (!ix.programId.equals(SystemProgram.programId)) return null;
try {
if (SystemInstruction.decodeInstructionType(ix) !== "Transfer") return null;
return SystemInstruction.decodeTransfer(ix);
} catch (err) {
console.error(ix, err);
return null;
}
}
export function decodeCreate(
ix: TransactionInstruction
): CreateAccountParams | null {
if (!ix.programId.equals(SystemProgram.programId)) return null;
try {
if (SystemInstruction.decodeInstructionType(ix) !== "Create") return null;
return SystemInstruction.decodeCreateAccount(ix);
} catch (err) {
console.error(ix, err);
return null;
}
}