Add support for more system instruction types

This commit is contained in:
Justin Starry 2020-05-09 15:11:48 +08:00 committed by Michael Vines
parent 7eb792a0cc
commit 01b99a0570
12 changed files with 690 additions and 51 deletions

View File

@ -9,15 +9,25 @@ import {
} from "../providers/transactions";
import { fetchDetails } from "providers/transactions/details";
import { useCluster, useClusterModal } from "providers/cluster";
import { TransactionSignature, SystemInstruction } from "@solana/web3.js";
import {
TransactionSignature,
SystemInstruction,
SystemProgram
} from "@solana/web3.js";
import ClusterStatusButton from "components/ClusterStatusButton";
import { lamportsToSolString } from "utils";
import { displayAddress } from "utils/tx";
import Copyable from "./Copyable";
import { useHistory, useLocation } from "react-router-dom";
import { TransferDetailsCard } from "./instruction/TransferDetailsCard";
import { AssignDetailsCard } from "./instruction/AssignDetailsCard";
import { CreateDetailsCard } from "./instruction/CreateDetailsCard";
import { RawDetailsCard } from "./instruction/RawDetailsCard";
import { CreateWithSeedDetailsCard } from "./instruction/CreateWithSeedDetailsCard";
import { UnknownDetailsCard } from "./instruction/UnknownDetailsCard";
import { NonceInitializeDetailsCard } from "./instruction/NonceInitializeDetailsCard";
import { NonceAdvanceDetailsCard } from "./instruction/NonceAdvanceDetailsCard";
import { NonceWithdrawDetailsCard } from "./instruction/NonceWithdrawDetailsCard";
import { NonceAuthorizeDetailsCard } from "./instruction/NonceAuthorizeDetailsCard";
type Props = { signature: TransactionSignature };
export default function TransactionDetails({ signature }: Props) {
@ -288,21 +298,37 @@ function InstructionsSection({ signature }: Props) {
const instructionDetails = transaction.instructions.map((ix, index) => {
const props = { ix, result, index };
let instructionType;
try {
instructionType = SystemInstruction.decodeInstructionType(ix);
} catch (err) {
console.error(err);
return <RawDetailsCard {...props} />;
if (!ix.programId.equals(SystemProgram.programId)) {
return <UnknownDetailsCard {...props} />;
}
switch (instructionType) {
case "Transfer":
return <TransferDetailsCard {...props} />;
let systemInstructionType;
try {
systemInstructionType = SystemInstruction.decodeInstructionType(ix);
} catch (err) {
console.error(err);
return <UnknownDetailsCard {...props} />;
}
switch (systemInstructionType) {
case "Create":
return <CreateDetailsCard {...props} />;
case "Assign":
return <AssignDetailsCard {...props} />;
case "Transfer":
return <TransferDetailsCard {...props} />;
case "CreateWithSeed":
return <CreateWithSeedDetailsCard {...props} />;
case "AdvanceNonceAccount":
return <NonceAdvanceDetailsCard {...props} />;
case "WithdrawNonceAccount":
return <NonceWithdrawDetailsCard {...props} />;
case "AuthorizeNonceAccount":
return <NonceAuthorizeDetailsCard {...props} />;
case "InitializeNonceAccount":
return <NonceInitializeDetailsCard {...props} />;
default:
return <RawDetailsCard {...props} />;
return <UnknownDetailsCard {...props} />;
}
});

View File

@ -0,0 +1,74 @@
import React from "react";
import {
TransactionInstruction,
SystemProgram,
SignatureResult,
SystemInstruction
} from "@solana/web3.js";
import { displayAddress } from "utils/tx";
import { InstructionCard } from "./InstructionCard";
import Copyable from "components/Copyable";
import { UnknownDetailsCard } from "./UnknownDetailsCard";
export function AssignDetailsCard(props: {
ix: TransactionInstruction;
index: number;
result: SignatureResult;
}) {
const { ix, index, result } = props;
let params;
try {
params = SystemInstruction.decodeAssign(ix);
} catch (err) {
console.error(err);
return <UnknownDetailsCard {...props} />;
}
const from = params.fromPubkey.toBase58();
const [fromMeta] = ix.keys;
return (
<InstructionCard
ix={ix}
index={index}
result={result}
title="Assign 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>Assigned Owner</td>
<td className="text-right">
<Copyable text={params.programId.toBase58()}>
<code>{displayAddress(params.programId)}</code>
</Copyable>
</td>
</tr>
</InstructionCard>
);
}

View File

@ -9,7 +9,7 @@ import { lamportsToSolString } from "utils";
import { displayAddress } from "utils/tx";
import { InstructionCard } from "./InstructionCard";
import Copyable from "components/Copyable";
import { RawDetailsCard } from "./RawDetailsCard";
import { UnknownDetailsCard } from "./UnknownDetailsCard";
export function CreateDetailsCard(props: {
ix: TransactionInstruction;
@ -18,20 +18,25 @@ export function CreateDetailsCard(props: {
}) {
const { ix, index, result } = props;
let create;
let params;
try {
create = SystemInstruction.decodeCreateAccount(ix);
params = SystemInstruction.decodeCreateAccount(ix);
} catch (err) {
console.error(err);
return <RawDetailsCard {...props} />;
return <UnknownDetailsCard {...props} />;
}
const from = create.fromPubkey.toBase58();
const newKey = create.newAccountPubkey.toBase58();
const from = params.fromPubkey.toBase58();
const newKey = params.newAccountPubkey.toBase58();
const [fromMeta, newMeta] = ix.keys;
return (
<InstructionCard index={index} result={result} title="Create Account">
<InstructionCard
ix={ix}
index={index}
result={result}
title="Create Account"
>
<tr>
<td>Program</td>
<td className="text-right">
@ -77,19 +82,19 @@ export function CreateDetailsCard(props: {
<tr>
<td>Transfer Amount (SOL)</td>
<td className="text-right">{lamportsToSolString(create.lamports)}</td>
<td className="text-right">{lamportsToSolString(params.lamports)}</td>
</tr>
<tr>
<td>Allocated Space (Bytes)</td>
<td className="text-right">{create.space}</td>
<td className="text-right">{params.space}</td>
</tr>
<tr>
<td>Assigned Owner</td>
<td className="text-right">
<Copyable text={create.programId.toBase58()}>
<code>{displayAddress(create.programId)}</code>
<Copyable text={params.programId.toBase58()}>
<code>{displayAddress(params.programId)}</code>
</Copyable>
</td>
</tr>

View File

@ -0,0 +1,139 @@
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 { UnknownDetailsCard } from "./UnknownDetailsCard";
export function CreateWithSeedDetailsCard(props: {
ix: TransactionInstruction;
index: number;
result: SignatureResult;
}) {
const { ix, index, result } = props;
let params;
try {
params = SystemInstruction.decodeCreateWithSeed(ix);
} catch (err) {
console.error(err);
return <UnknownDetailsCard {...props} />;
}
const from = params.fromPubkey.toBase58();
const newKey = params.newAccountPubkey.toBase58();
const baseKey = params.basePubkey.toBase58();
const [fromMeta, newMeta] = ix.keys;
return (
<InstructionCard
ix={ix}
index={index}
result={result}
title="Create Account w/ Seed"
>
<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>
<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>Base Address</td>
<td className="text-right">
<Copyable text={baseKey}>
<code>{baseKey}</code>
</Copyable>
</td>
</tr>
<tr>
<td>Seed</td>
<td className="text-right">
<Copyable text={params.seed}>
<code>{params.seed}</code>
</Copyable>
</td>
</tr>
<tr>
<td>Transfer Amount (SOL)</td>
<td className="text-right">{lamportsToSolString(params.lamports)}</td>
</tr>
<tr>
<td>Allocated Space (Bytes)</td>
<td className="text-right">{params.space}</td>
</tr>
<tr>
<td>Assigned Owner</td>
<td className="text-right">
<Copyable text={params.programId.toBase58()}>
<code>{displayAddress(params.programId)}</code>
</Copyable>
</td>
</tr>
</InstructionCard>
);
}

View File

@ -1,20 +1,27 @@
import React from "react";
import { SignatureResult } from "@solana/web3.js";
import { TransactionInstruction, SignatureResult } from "@solana/web3.js";
import { RawDetails } from "./RawDetails";
type InstructionProps = {
title: string;
children: React.ReactNode;
children?: React.ReactNode;
result: SignatureResult;
index: number;
ix: TransactionInstruction;
defaultRaw?: boolean;
};
export function InstructionCard({
title,
children,
result,
index
index,
ix,
defaultRaw
}: InstructionProps) {
const [resultClass, errorString] = ixResult(result, index);
const [resultClass] = ixResult(result, index);
const [showRaw, setShowRaw] = React.useState(defaultRaw || false);
return (
<div className="card">
<div className="card-header">
@ -24,15 +31,22 @@ export function InstructionCard({
</span>
{title}
</h3>
<h3 className="mb-0">
<span className="badge badge-soft-warning text-monospace">
{errorString}
</span>
</h3>
<button
className={`btn btn-sm d-flex ${
showRaw ? "btn-dark active" : "btn-white"
}`}
onClick={() => setShowRaw(r => !r)}
>
<span className="fe fe-code mr-1"></span>
Raw
</button>
</div>
<div className="table-responsive mb-0">
<table className="table table-sm table-nowrap card-table">
<tbody className="list">{children}</tbody>
<tbody className="list">
{showRaw ? <RawDetails ix={ix} /> : children}
</tbody>
</table>
</div>
</div>

View File

@ -0,0 +1,83 @@
import React from "react";
import {
TransactionInstruction,
SystemProgram,
SignatureResult,
SystemInstruction
} from "@solana/web3.js";
import { displayAddress } from "utils/tx";
import { InstructionCard } from "./InstructionCard";
import Copyable from "components/Copyable";
import { UnknownDetailsCard } from "./UnknownDetailsCard";
export function NonceAdvanceDetailsCard(props: {
ix: TransactionInstruction;
index: number;
result: SignatureResult;
}) {
const { ix, index, result } = props;
let params;
try {
params = SystemInstruction.decodeNonceAdvance(ix);
} catch (err) {
console.error(err);
return <UnknownDetailsCard {...props} />;
}
const nonceKey = params.noncePubkey.toBase58();
const authorizedKey = params.authorizedPubkey.toBase58();
const [nonceMeta, , authorizedMeta] = ix.keys;
return (
<InstructionCard
ix={ix}
index={index}
result={result}
title="Advance Nonce"
>
<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">Nonce Address</div>
{!nonceMeta.isWritable && (
<span className="badge badge-soft-dark mr-1">Readonly</span>
)}
{nonceMeta.isSigner && (
<span className="badge badge-soft-dark mr-1">Signer</span>
)}
</td>
<td className="text-right">
<Copyable text={nonceKey}>
<code>{nonceKey}</code>
</Copyable>
</td>
</tr>
<tr>
<td>
<div className="mr-2 d-md-inline">Authorized Address</div>
{!authorizedMeta.isWritable && (
<span className="badge badge-soft-dark mr-1">Readonly</span>
)}
{authorizedMeta.isSigner && (
<span className="badge badge-soft-dark mr-1">Signer</span>
)}
</td>
<td className="text-right">
<Copyable text={authorizedKey}>
<code>{authorizedKey}</code>
</Copyable>
</td>
</tr>
</InstructionCard>
);
}

View File

@ -0,0 +1,93 @@
import React from "react";
import {
TransactionInstruction,
SystemProgram,
SignatureResult,
SystemInstruction
} from "@solana/web3.js";
import { displayAddress } from "utils/tx";
import { InstructionCard } from "./InstructionCard";
import Copyable from "components/Copyable";
import { UnknownDetailsCard } from "./UnknownDetailsCard";
export function NonceAuthorizeDetailsCard(props: {
ix: TransactionInstruction;
index: number;
result: SignatureResult;
}) {
const { ix, index, result } = props;
let params;
try {
params = SystemInstruction.decodeNonceAuthorize(ix);
} catch (err) {
console.error(err);
return <UnknownDetailsCard {...props} />;
}
const nonceKey = params.noncePubkey.toBase58();
const authorizedKey = params.authorizedPubkey.toBase58();
const newAuthorizedKey = params.newAuthorizedPubkey.toBase58();
const [nonceMeta, authorizedMeta] = ix.keys;
return (
<InstructionCard
ix={ix}
index={index}
result={result}
title="Authorize Nonce"
>
<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">Nonce Address</div>
{!nonceMeta.isWritable && (
<span className="badge badge-soft-dark mr-1">Readonly</span>
)}
{nonceMeta.isSigner && (
<span className="badge badge-soft-dark mr-1">Signer</span>
)}
</td>
<td className="text-right">
<Copyable text={nonceKey}>
<code>{nonceKey}</code>
</Copyable>
</td>
</tr>
<tr>
<td>
<div className="mr-2 d-md-inline">Authorized Address</div>
{!authorizedMeta.isWritable && (
<span className="badge badge-soft-dark mr-1">Readonly</span>
)}
{authorizedMeta.isSigner && (
<span className="badge badge-soft-dark mr-1">Signer</span>
)}
</td>
<td className="text-right">
<Copyable text={authorizedKey}>
<code>{authorizedKey}</code>
</Copyable>
</td>
</tr>
<tr>
<td>New Authorized Address</td>
<td className="text-right">
<Copyable text={newAuthorizedKey}>
<code>{newAuthorizedKey}</code>
</Copyable>
</td>
</tr>
</InstructionCard>
);
}

View File

@ -0,0 +1,75 @@
import React from "react";
import {
TransactionInstruction,
SystemProgram,
SignatureResult,
SystemInstruction
} from "@solana/web3.js";
import { displayAddress } from "utils/tx";
import { InstructionCard } from "./InstructionCard";
import Copyable from "components/Copyable";
import { UnknownDetailsCard } from "./UnknownDetailsCard";
export function NonceInitializeDetailsCard(props: {
ix: TransactionInstruction;
index: number;
result: SignatureResult;
}) {
const { ix, index, result } = props;
let params;
try {
params = SystemInstruction.decodeNonceInitialize(ix);
} catch (err) {
console.error(err);
return <UnknownDetailsCard {...props} />;
}
const nonceKey = params.noncePubkey.toBase58();
const authorizedKey = params.authorizedPubkey.toBase58();
const [nonceMeta] = ix.keys;
return (
<InstructionCard
ix={ix}
index={index}
result={result}
title="Initialize Nonce"
>
<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">Nonce Address</div>
{!nonceMeta.isWritable && (
<span className="badge badge-soft-dark mr-1">Readonly</span>
)}
{nonceMeta.isSigner && (
<span className="badge badge-soft-dark mr-1">Signer</span>
)}
</td>
<td className="text-right">
<Copyable text={nonceKey}>
<code>{nonceKey}</code>
</Copyable>
</td>
</tr>
<tr>
<td>Authorized Address</td>
<td className="text-right">
<Copyable text={authorizedKey}>
<code>{authorizedKey}</code>
</Copyable>
</td>
</tr>
</InstructionCard>
);
}

View File

@ -0,0 +1,108 @@
import React from "react";
import {
TransactionInstruction,
SystemProgram,
SignatureResult,
SystemInstruction
} from "@solana/web3.js";
import { displayAddress } from "utils/tx";
import { lamportsToSolString } from "utils";
import { InstructionCard } from "./InstructionCard";
import Copyable from "components/Copyable";
import { UnknownDetailsCard } from "./UnknownDetailsCard";
export function NonceWithdrawDetailsCard(props: {
ix: TransactionInstruction;
index: number;
result: SignatureResult;
}) {
const { ix, index, result } = props;
let params;
try {
params = SystemInstruction.decodeNonceWithdraw(ix);
} catch (err) {
console.error(err);
return <UnknownDetailsCard {...props} />;
}
const nonceKey = params.noncePubkey.toBase58();
const toKey = params.toPubkey.toBase58();
const authorizedKey = params.authorizedPubkey.toBase58();
const lamports = params.lamports;
const [nonceMeta, toMeta, , , authorizedMeta] = ix.keys;
return (
<InstructionCard
ix={ix}
index={index}
result={result}
title="Withdraw Nonce"
>
<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">Nonce Address</div>
{!nonceMeta.isWritable && (
<span className="badge badge-soft-dark mr-1">Readonly</span>
)}
{nonceMeta.isSigner && (
<span className="badge badge-soft-dark mr-1">Signer</span>
)}
</td>
<td className="text-right">
<Copyable text={nonceKey}>
<code>{nonceKey}</code>
</Copyable>
</td>
</tr>
<tr>
<td>
<div className="mr-2 d-md-inline">Authorized Address</div>
{!authorizedMeta.isWritable && (
<span className="badge badge-soft-dark mr-1">Readonly</span>
)}
{authorizedMeta.isSigner && (
<span className="badge badge-soft-dark mr-1">Signer</span>
)}
</td>
<td className="text-right">
<Copyable text={authorizedKey}>
<code>{authorizedKey}</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={toKey}>
<code>{toKey}</code>
</Copyable>
</td>
</tr>
<tr>
<td>Withdraw Amount (SOL)</td>
<td className="text-right">{lamportsToSolString(lamports)}</td>
</tr>
</InstructionCard>
);
}

View File

@ -1,21 +1,20 @@
import React from "react";
import bs58 from "bs58";
import { TransactionInstruction, SignatureResult } from "@solana/web3.js";
import { TransactionInstruction } 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;
}) {
function displayData(data: string) {
if (data.length > 50) {
return `${data.substring(0, 49)}`;
}
return data;
}
export function RawDetails({ ix }: { ix: TransactionInstruction }) {
const data = bs58.encode(ix.data);
return (
<InstructionCard index={index} result={result} title="Raw">
<>
<tr>
<td>Program</td>
<td className="text-right">
@ -47,11 +46,11 @@ export function RawDetailsCard({
<tr>
<td>Raw Data (Base58)</td>
<td className="text-right">
<Copyable text={bs58.encode(ix.data)}>
<code>{bs58.encode(ix.data)}</code>
<Copyable text={data}>
<code>{displayData(data)}</code>
</Copyable>
</td>
</tr>
</InstructionCard>
</>
);
}

View File

@ -9,7 +9,7 @@ import { lamportsToSolString } from "utils";
import { displayAddress } from "utils/tx";
import { InstructionCard } from "./InstructionCard";
import Copyable from "components/Copyable";
import { RawDetailsCard } from "./RawDetailsCard";
import { UnknownDetailsCard } from "./UnknownDetailsCard";
export function TransferDetailsCard(props: {
ix: TransactionInstruction;
@ -23,14 +23,14 @@ export function TransferDetailsCard(props: {
transfer = SystemInstruction.decodeTransfer(ix);
} catch (err) {
console.error(err);
return <RawDetailsCard {...props} />;
return <UnknownDetailsCard {...props} />;
}
const from = transfer.fromPubkey.toBase58();
const to = transfer.toPubkey.toBase58();
const [fromMeta, toMeta] = ix.keys;
return (
<InstructionCard index={index} result={result} title="Transfer">
<InstructionCard ix={ix} index={index} result={result} title="Transfer">
<tr>
<td>Program</td>
<td className="text-right">

View File

@ -0,0 +1,23 @@
import React from "react";
import { TransactionInstruction, SignatureResult } from "@solana/web3.js";
import { InstructionCard } from "./InstructionCard";
export function UnknownDetailsCard({
ix,
index,
result
}: {
ix: TransactionInstruction;
index: number;
result: SignatureResult;
}) {
return (
<InstructionCard
ix={ix}
index={index}
result={result}
title="Unknown"
defaultRaw
/>
);
}