Transaction modal fixes

This commit is contained in:
Justin Starry 2020-04-07 01:27:54 +08:00 committed by Michael Vines
parent 0bda30e1f7
commit e47b178d29
2 changed files with 130 additions and 63 deletions

View File

@ -5,8 +5,13 @@ import {
ActionType, ActionType,
Selected Selected
} from "../providers/transactions"; } from "../providers/transactions";
import { displayAddress } from "../utils";
import { useBlocks } from "../providers/blocks"; import { useBlocks } from "../providers/blocks";
import { LAMPORTS_PER_SOL } from "@solana/web3.js"; import {
LAMPORTS_PER_SOL,
TransferParams,
CreateAccountParams
} from "@solana/web3.js";
function TransactionModal() { function TransactionModal() {
const { selected } = useTransactions(); const { selected } = useTransactions();
@ -45,24 +50,34 @@ function TransactionModal() {
function TransactionDetails({ selected }: { selected: Selected }) { function TransactionDetails({ selected }: { selected: Selected }) {
const { blocks } = useBlocks(); const { blocks } = useBlocks();
const block = blocks[selected.slot]; const block = blocks[selected.slot];
if (!block)
return <span className="text-info">{"Transaction block not found"}</span>; const renderError = (content: React.ReactNode) => {
return (
<div className="card-body">
<span className="text-info">{content}</span>
</div>
);
};
if (!block) return renderError("Transaction block not found");
if (!block.transactions) { if (!block.transactions) {
return ( return renderError(
<span className="text-info"> <>
<span className="spinner-grow spinner-grow-sm mr-2"></span> <span className="spinner-grow spinner-grow-sm mr-2"></span>
Loading Loading
</span> </>
); );
} }
const details = block.transactions[selected.signature]; const details = block.transactions[selected.signature];
if (!details) if (!details) return renderError("Transaction not found");
return <span className="text-info">{"Transaction not found"}</span>;
if (details.transfers.length === 0) const { transfers, creates } = details;
return <span className="text-info">{"No transfers"}</span>; if (transfers.length === 0 && creates.length === 0)
return renderError(
"Details for this transaction's instructions are not yet supported"
);
let i = 0; let i = 0;
return ( return (
@ -71,42 +86,15 @@ function TransactionDetails({ selected }: { selected: Selected }) {
return ( return (
<div key={++i}> <div key={++i}>
{i > 1 ? <hr className="mb-4"></hr> : null} {i > 1 ? <hr className="mb-4"></hr> : null}
<div className="card-body"> <TransferDetails transfer={transfer} />
<div className="list-group list-group-flush my-n3"> </div>
<div className="list-group-item"> );
<div className="row align-items-center"> })}
<div className="col"> {details.creates.map(create => {
<h5 className="mb-0">From</h5> return (
</div> <div key={++i}>
<div className="col-auto"> {i > 1 ? <hr className="mb-4"></hr> : null}
<code>{transfer.fromPubkey.toBase58()}</code> <CreateDetails create={create} />
</div>
</div>
</div>
<div className="list-group-item">
<div className="row align-items-center">
<div className="col">
<h5 className="mb-0">To</h5>
</div>
<div className="col-auto">
<code>{transfer.toPubkey.toBase58()}</code>
</div>
</div>
</div>
<div className="list-group-item">
<div className="row align-items-center">
<div className="col">
<h5 className="mb-0">Amount (SOL)</h5>
</div>
<div className="col-auto">
{`${(1.0 * transfer.lamports) / LAMPORTS_PER_SOL}`}
</div>
</div>
</div>
</div>
</div>
</div> </div>
); );
})} })}
@ -114,4 +102,63 @@ function TransactionDetails({ selected }: { selected: Selected }) {
); );
} }
function TransferDetails({ transfer }: { transfer: TransferParams }) {
return (
<div className="card-body">
<div className="list-group list-group-flush my-n3">
<ListGroupItem label="From">
<code>{transfer.fromPubkey.toBase58()}</code>
</ListGroupItem>
<ListGroupItem label="To">
<code>{transfer.toPubkey.toBase58()}</code>
</ListGroupItem>
<ListGroupItem label="Amount (SOL)">
{`${(1.0 * transfer.lamports) / LAMPORTS_PER_SOL}`}
</ListGroupItem>
</div>
</div>
);
}
function CreateDetails({ create }: { create: CreateAccountParams }) {
return (
<div className="card-body">
<div className="list-group list-group-flush my-n3">
<ListGroupItem label="From">
<code>{create.fromPubkey.toBase58()}</code>
</ListGroupItem>
<ListGroupItem label="New Account">
<code>{create.newAccountPubkey.toBase58()}</code>
</ListGroupItem>
<ListGroupItem label="Amount (SOL)">
{`${(1.0 * create.lamports) / LAMPORTS_PER_SOL}`}
</ListGroupItem>
<ListGroupItem label="Data (Bytes)">{create.space}</ListGroupItem>
<ListGroupItem label="Owner">
<code>{displayAddress(create.programId)}</code>
</ListGroupItem>
</div>
</div>
);
}
function ListGroupItem({
label,
children
}: {
label: string;
children: React.ReactNode;
}) {
return (
<div className="list-group-item">
<div className="row align-items-center">
<div className="col">
<h5 className="mb-0">{label}</h5>
</div>
<div className="col-auto">{children}</div>
</div>
</div>
);
}
export default TransactionModal; export default TransactionModal;

View File

@ -5,7 +5,8 @@ import {
Transaction, Transaction,
TransferParams, TransferParams,
SystemProgram, SystemProgram,
SystemInstruction SystemInstruction,
CreateAccountParams
} from "@solana/web3.js"; } from "@solana/web3.js";
import { useCluster, ClusterStatus } from "./cluster"; import { useCluster, ClusterStatus } from "./cluster";
import { useTransactions } from "./transactions"; import { useTransactions } from "./transactions";
@ -19,6 +20,7 @@ export enum Status {
export interface TransactionDetails { export interface TransactionDetails {
transaction: Transaction; transaction: Transaction;
transfers: Array<TransferParams>; transfers: Array<TransferParams>;
creates: Array<CreateAccountParams>;
} }
type Transactions = { [signature: string]: TransactionDetails }; type Transactions = { [signature: string]: TransactionDetails };
@ -152,6 +154,38 @@ 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,
@ -166,25 +200,11 @@ async function fetchBlock(dispatch: Dispatch, slot: number, url: string) {
block.transactions.forEach(({ transaction }) => { block.transactions.forEach(({ transaction }) => {
const signature = transaction.signature; const signature = transaction.signature;
if (signature) { if (signature) {
const transferInstructions = transaction.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.log(ix, err);
}
});
const sig = bs58.encode(signature); const sig = bs58.encode(signature);
transactions[sig] = { transactions[sig] = {
transaction, transaction,
transfers transfers: decodeTransfers(transaction),
creates: decodeCreates(transaction)
}; };
} }
}); });