Show details for all transaction instructions
This commit is contained in:
parent
1c1c628b19
commit
8de34e1f42
|
@ -7,7 +7,8 @@ import {
|
||||||
Account,
|
Account,
|
||||||
Status
|
Status
|
||||||
} from "../providers/accounts";
|
} from "../providers/accounts";
|
||||||
import { assertUnreachable, displayAddress } from "../utils";
|
import { assertUnreachable } from "../utils";
|
||||||
|
import { displayAddress } from "../utils/tx";
|
||||||
import { useCluster } from "../providers/cluster";
|
import { useCluster } from "../providers/cluster";
|
||||||
import { PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js";
|
import { PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js";
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,13 @@ import {
|
||||||
ActionType,
|
ActionType,
|
||||||
Selected
|
Selected
|
||||||
} from "../providers/transactions";
|
} from "../providers/transactions";
|
||||||
import { displayAddress } from "../utils";
|
import { displayAddress, decodeCreate, decodeTransfer } from "../utils/tx";
|
||||||
import { useBlocks } from "../providers/blocks";
|
import { useBlocks } from "../providers/blocks";
|
||||||
import {
|
import {
|
||||||
LAMPORTS_PER_SOL,
|
LAMPORTS_PER_SOL,
|
||||||
TransferParams,
|
TransferParams,
|
||||||
CreateAccountParams
|
CreateAccountParams,
|
||||||
|
TransactionInstruction
|
||||||
} from "@solana/web3.js";
|
} from "@solana/web3.js";
|
||||||
|
|
||||||
function TransactionModal() {
|
function TransactionModal() {
|
||||||
|
@ -70,31 +71,27 @@ function TransactionDetails({ selected }: { selected: Selected }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const details = block.transactions[selected.signature];
|
const transaction = block.transactions[selected.signature];
|
||||||
if (!details) return renderError("Transaction not found");
|
if (!transaction) return renderError("Transaction not found");
|
||||||
|
|
||||||
const { transfers, creates } = details;
|
if (transaction.instructions.length === 0)
|
||||||
if (transfers.length === 0 && creates.length === 0)
|
return renderError("No instructions found");
|
||||||
return renderError(
|
|
||||||
"Details for this transaction's instructions are not yet supported"
|
const instructionDetails = transaction.instructions.map((ix, index) => {
|
||||||
);
|
const transfer = decodeTransfer(ix);
|
||||||
|
if (transfer) return <TransferDetails transfer={transfer} index={index} />;
|
||||||
|
const create = decodeCreate(ix);
|
||||||
|
if (create) return <CreateDetails create={create} index={index} />;
|
||||||
|
return <InstructionDetails ix={ix} index={index} />;
|
||||||
|
});
|
||||||
|
|
||||||
let i = 0;
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{details.transfers.map(transfer => {
|
{instructionDetails.map((details, i) => {
|
||||||
return (
|
return (
|
||||||
<div key={++i}>
|
<div key={++i}>
|
||||||
{i > 1 ? <hr className="mb-4"></hr> : null}
|
{i > 1 ? <hr className="mt-0 mb-0"></hr> : null}
|
||||||
<TransferDetails transfer={transfer} />
|
{details}
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{details.creates.map(create => {
|
|
||||||
return (
|
|
||||||
<div key={++i}>
|
|
||||||
{i > 1 ? <hr className="mb-4"></hr> : null}
|
|
||||||
<CreateDetails create={create} />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -102,9 +99,16 @@ function TransactionDetails({ selected }: { selected: Selected }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TransferDetails({ transfer }: { transfer: TransferParams }) {
|
function TransferDetails({
|
||||||
|
transfer,
|
||||||
|
index
|
||||||
|
}: {
|
||||||
|
transfer: TransferParams;
|
||||||
|
index: number;
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
|
<h4 className="ix-pill">{`Instruction #${index + 1} (Transfer)`}</h4>
|
||||||
<div className="list-group list-group-flush my-n3">
|
<div className="list-group list-group-flush my-n3">
|
||||||
<ListGroupItem label="From">
|
<ListGroupItem label="From">
|
||||||
<code>{transfer.fromPubkey.toBase58()}</code>
|
<code>{transfer.fromPubkey.toBase58()}</code>
|
||||||
|
@ -120,9 +124,17 @@ function TransferDetails({ transfer }: { transfer: TransferParams }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CreateDetails({ create }: { create: CreateAccountParams }) {
|
function CreateDetails({
|
||||||
|
create,
|
||||||
|
index
|
||||||
|
}: {
|
||||||
|
create: CreateAccountParams;
|
||||||
|
index: number;
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
|
<h4 className="ix-pill">{`Instruction #${index +
|
||||||
|
1} (Create Account)`}</h4>
|
||||||
<div className="list-group list-group-flush my-n3">
|
<div className="list-group list-group-flush my-n3">
|
||||||
<ListGroupItem label="From">
|
<ListGroupItem label="From">
|
||||||
<code>{create.fromPubkey.toBase58()}</code>
|
<code>{create.fromPubkey.toBase58()}</code>
|
||||||
|
@ -142,6 +154,31 @@ function CreateDetails({ create }: { create: CreateAccountParams }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function InstructionDetails({
|
||||||
|
ix,
|
||||||
|
index
|
||||||
|
}: {
|
||||||
|
ix: TransactionInstruction;
|
||||||
|
index: number;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="card-body">
|
||||||
|
<h4 className="ix-pill">{`Instruction #${index + 1}`}</h4>
|
||||||
|
<div className="list-group list-group-flush my-n3">
|
||||||
|
{ix.keys.map(({ pubkey }, keyIndex) => (
|
||||||
|
<ListGroupItem key={keyIndex} label={`Address #${keyIndex + 1}`}>
|
||||||
|
<code>{pubkey.toBase58()}</code>
|
||||||
|
</ListGroupItem>
|
||||||
|
))}
|
||||||
|
<ListGroupItem label="Data (Bytes)">{ix.data.length}</ListGroupItem>
|
||||||
|
<ListGroupItem label="Program">
|
||||||
|
<code>{displayAddress(ix.programId)}</code>
|
||||||
|
</ListGroupItem>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function ListGroupItem({
|
function ListGroupItem({
|
||||||
label,
|
label,
|
||||||
children
|
children
|
||||||
|
@ -150,7 +187,7 @@ function ListGroupItem({
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="list-group-item">
|
<div className="list-group-item ix-item">
|
||||||
<div className="row align-items-center">
|
<div className="row align-items-center">
|
||||||
<div className="col">
|
<div className="col">
|
||||||
<h5 className="mb-0">{label}</h5>
|
<h5 className="mb-0">{label}</h5>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { PublicKey, Connection } from "@solana/web3.js";
|
import { PublicKey, Connection } from "@solana/web3.js";
|
||||||
import { findGetParameter, findPathSegment } from "../utils";
|
import { findGetParameter, findPathSegment } from "../utils/url";
|
||||||
import { useCluster, ClusterStatus } from "./cluster";
|
import { useCluster, ClusterStatus } from "./cluster";
|
||||||
|
|
||||||
export enum Status {
|
export enum Status {
|
||||||
|
|
|
@ -1,13 +1,6 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import bs58 from "bs58";
|
import bs58 from "bs58";
|
||||||
import {
|
import { Connection, Transaction } from "@solana/web3.js";
|
||||||
Connection,
|
|
||||||
Transaction,
|
|
||||||
TransferParams,
|
|
||||||
SystemProgram,
|
|
||||||
SystemInstruction,
|
|
||||||
CreateAccountParams
|
|
||||||
} from "@solana/web3.js";
|
|
||||||
import { useCluster, ClusterStatus } from "./cluster";
|
import { useCluster, ClusterStatus } from "./cluster";
|
||||||
import { useTransactions } from "./transactions";
|
import { useTransactions } from "./transactions";
|
||||||
|
|
||||||
|
@ -17,13 +10,7 @@ export enum Status {
|
||||||
Success
|
Success
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TransactionDetails {
|
type Transactions = { [signature: string]: Transaction };
|
||||||
transaction: Transaction;
|
|
||||||
transfers: Array<TransferParams>;
|
|
||||||
creates: Array<CreateAccountParams>;
|
|
||||||
}
|
|
||||||
|
|
||||||
type Transactions = { [signature: string]: TransactionDetails };
|
|
||||||
export interface Block {
|
export interface Block {
|
||||||
status: Status;
|
status: Status;
|
||||||
transactions?: Transactions;
|
transactions?: Transactions;
|
||||||
|
@ -154,38 +141,6 @@ export function BlocksProvider({ children }: BlocksProviderProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function decodeTransfers(tx: Transaction) {
|
|
||||||
const transferInstructions = tx.instructions
|
|
||||||
.filter(ix => ix.programId.equals(SystemProgram.programId))
|
|
||||||
.filter(ix => SystemInstruction.decodeInstructionType(ix) === "Transfer");
|
|
||||||
|
|
||||||
let transfers: TransferParams[] = [];
|
|
||||||
transferInstructions.forEach(ix => {
|
|
||||||
try {
|
|
||||||
transfers.push(SystemInstruction.decodeTransfer(ix));
|
|
||||||
} catch (err) {
|
|
||||||
console.error(ix, err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return transfers;
|
|
||||||
}
|
|
||||||
|
|
||||||
function decodeCreates(tx: Transaction) {
|
|
||||||
const createInstructions = tx.instructions
|
|
||||||
.filter(ix => ix.programId.equals(SystemProgram.programId))
|
|
||||||
.filter(ix => SystemInstruction.decodeInstructionType(ix) === "Create");
|
|
||||||
|
|
||||||
let creates: CreateAccountParams[] = [];
|
|
||||||
createInstructions.forEach(ix => {
|
|
||||||
try {
|
|
||||||
creates.push(SystemInstruction.decodeCreateAccount(ix));
|
|
||||||
} catch (err) {
|
|
||||||
console.error(ix, err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return creates;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchBlock(dispatch: Dispatch, slot: number, url: string) {
|
async function fetchBlock(dispatch: Dispatch, slot: number, url: string) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ActionType.Update,
|
type: ActionType.Update,
|
||||||
|
@ -201,11 +156,7 @@ async function fetchBlock(dispatch: Dispatch, slot: number, url: string) {
|
||||||
const signature = transaction.signature;
|
const signature = transaction.signature;
|
||||||
if (signature) {
|
if (signature) {
|
||||||
const sig = bs58.encode(signature);
|
const sig = bs58.encode(signature);
|
||||||
transactions[sig] = {
|
transactions[sig] = transaction;
|
||||||
transaction,
|
|
||||||
transfers: decodeTransfers(transaction),
|
|
||||||
creates: decodeCreates(transaction)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
status = Status.Success;
|
status = Status.Success;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { clusterApiUrl, Connection } from "@solana/web3.js";
|
import { clusterApiUrl, Connection } from "@solana/web3.js";
|
||||||
import { findGetParameter } from "../utils";
|
import { findGetParameter } from "../utils/url";
|
||||||
|
|
||||||
export enum ClusterStatus {
|
export enum ClusterStatus {
|
||||||
Connected,
|
Connected,
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
SystemProgram,
|
SystemProgram,
|
||||||
Account
|
Account
|
||||||
} from "@solana/web3.js";
|
} from "@solana/web3.js";
|
||||||
import { findGetParameter, findPathSegment } from "../utils";
|
import { findGetParameter, findPathSegment } from "../utils/url";
|
||||||
import { useCluster, ClusterStatus } from "../providers/cluster";
|
import { useCluster, ClusterStatus } from "../providers/cluster";
|
||||||
import base58 from "bs58";
|
import base58 from "bs58";
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -41,3 +41,16 @@ code {
|
||||||
input.text-signature, input.text-address {
|
input.text-signature, input.text-address {
|
||||||
padding: 0 0.75rem
|
padding: 0 0.75rem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h4.ix-pill {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
margin-left: -5px;
|
||||||
|
background-color: theme-color-level(info, $badge-soft-bg-level);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-group-flush .list-group-item.ix-item:first-child {
|
||||||
|
border-top-width: 1px;
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
export function assertUnreachable(x: never): never {
|
||||||
|
throw new Error("Unreachable!");
|
||||||
|
}
|
|
@ -4,49 +4,16 @@ import {
|
||||||
StakeProgram,
|
StakeProgram,
|
||||||
VOTE_PROGRAM_ID,
|
VOTE_PROGRAM_ID,
|
||||||
BpfLoader,
|
BpfLoader,
|
||||||
|
TransferParams,
|
||||||
|
SystemInstruction,
|
||||||
|
CreateAccountParams,
|
||||||
|
TransactionInstruction,
|
||||||
SYSVAR_CLOCK_PUBKEY,
|
SYSVAR_CLOCK_PUBKEY,
|
||||||
SYSVAR_RENT_PUBKEY,
|
SYSVAR_RENT_PUBKEY,
|
||||||
SYSVAR_REWARDS_PUBKEY,
|
SYSVAR_REWARDS_PUBKEY,
|
||||||
SYSVAR_STAKE_HISTORY_PUBKEY
|
SYSVAR_STAKE_HISTORY_PUBKEY
|
||||||
} from "@solana/web3.js";
|
} from "@solana/web3.js";
|
||||||
|
|
||||||
export function findGetParameter(parameterName: string): string | null {
|
|
||||||
let result = null,
|
|
||||||
tmp = [];
|
|
||||||
window.location.search
|
|
||||||
.substr(1)
|
|
||||||
.split("&")
|
|
||||||
.forEach(function(item) {
|
|
||||||
tmp = item.split("=");
|
|
||||||
if (tmp[0].toLowerCase() === parameterName.toLowerCase()) {
|
|
||||||
if (tmp.length === 2) {
|
|
||||||
result = decodeURIComponent(tmp[1]);
|
|
||||||
} else if (tmp.length === 1) {
|
|
||||||
result = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function findPathSegment(pathName: string): string | null {
|
|
||||||
const segments = window.location.pathname.substr(1).split("/");
|
|
||||||
if (segments.length < 2) return null;
|
|
||||||
|
|
||||||
// remove all but last two segments
|
|
||||||
segments.splice(0, segments.length - 2);
|
|
||||||
|
|
||||||
if (segments[0] === pathName) {
|
|
||||||
return segments[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function assertUnreachable(x: never): never {
|
|
||||||
throw new Error("Unreachable!");
|
|
||||||
}
|
|
||||||
|
|
||||||
const PROGRAM_IDS = {
|
const PROGRAM_IDS = {
|
||||||
Budget1111111111111111111111111111111111111: "Budget",
|
Budget1111111111111111111111111111111111111: "Budget",
|
||||||
Config1111111111111111111111111111111111111: "Config",
|
Config1111111111111111111111111111111111111: "Config",
|
||||||
|
@ -86,3 +53,31 @@ export function displayAddress(pubkey: PublicKey): string {
|
||||||
address
|
address
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function decodeTransfer(
|
||||||
|
ix: TransactionInstruction
|
||||||
|
): TransferParams | null {
|
||||||
|
if (!ix.programId.equals(SystemProgram.programId)) return null;
|
||||||
|
if (SystemInstruction.decodeInstructionType(ix) !== "Transfer") return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
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;
|
||||||
|
if (SystemInstruction.decodeInstructionType(ix) !== "Create") return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return SystemInstruction.decodeCreateAccount(ix);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(ix, err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
export function findGetParameter(parameterName: string): string | null {
|
||||||
|
let result = null,
|
||||||
|
tmp = [];
|
||||||
|
window.location.search
|
||||||
|
.substr(1)
|
||||||
|
.split("&")
|
||||||
|
.forEach(function(item) {
|
||||||
|
tmp = item.split("=");
|
||||||
|
if (tmp[0].toLowerCase() === parameterName.toLowerCase()) {
|
||||||
|
if (tmp.length === 2) {
|
||||||
|
result = decodeURIComponent(tmp[1]);
|
||||||
|
} else if (tmp.length === 1) {
|
||||||
|
result = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findPathSegment(pathName: string): string | null {
|
||||||
|
const segments = window.location.pathname.substr(1).split("/");
|
||||||
|
if (segments.length < 2) return null;
|
||||||
|
|
||||||
|
// remove all but last two segments
|
||||||
|
segments.splice(0, segments.length - 2);
|
||||||
|
|
||||||
|
if (segments[0] === pathName) {
|
||||||
|
return segments[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
Loading…
Reference in New Issue