Parse token instructions in the explorer (#11443)
This commit is contained in:
29
explorer/package-lock.json
generated
29
explorer/package-lock.json
generated
@ -2334,9 +2334,9 @@
|
||||
"integrity": "sha512-zLtOIToct1EBTbwldkMJsXC2eCsmWOOP7z6UG0M/sCgnPExtIjvVMCpPESvPnMbQzDZytXVy0nvMbUuK2gZs2A=="
|
||||
},
|
||||
"@solana/web3.js": {
|
||||
"version": "0.64.0",
|
||||
"resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-0.64.0.tgz",
|
||||
"integrity": "sha512-DlNzAXgNdk7k4Pt6CfcaAutaiXJiog9hxswtzItf0q/0/Um8JvDI1YjnMONE3IKI/jyjmTaxhsQHWAQE42KofQ==",
|
||||
"version": "0.66.0",
|
||||
"resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-0.66.0.tgz",
|
||||
"integrity": "sha512-Uw7ooRWLqrq8I5U21mEryvvF/Eqqh4mq4K2W9Sxuz3boxkz7Ed7aAJVj5C5n1fbQr9I1cxxxgC+D5BHnogfS1A==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"bn.js": "^5.0.0",
|
||||
@ -3032,9 +3032,9 @@
|
||||
"integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ=="
|
||||
},
|
||||
"@types/lodash": {
|
||||
"version": "4.14.158",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.158.tgz",
|
||||
"integrity": "sha512-InCEXJNTv/59yO4VSfuvNrZHt7eeNtWQEgnieIA+mIC+MOWM9arOWG2eQ8Vhk6NbOre6/BidiXhkZYeDY9U35w=="
|
||||
"version": "4.14.159",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.159.tgz",
|
||||
"integrity": "sha512-gF7A72f7WQN33DpqOWw9geApQPh4M3PxluMtaHxWHXEGSN12/WbcEk/eNSqWNQcQhF66VSZ06vCF94CrHwXJDg=="
|
||||
},
|
||||
"@types/minimatch": {
|
||||
"version": "3.0.3",
|
||||
@ -9055,9 +9055,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "12.12.53",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.53.tgz",
|
||||
"integrity": "sha512-51MYTDTyCziHb70wtGNFRwB4l+5JNvdqzFSkbDvpbftEgVUBEE+T5f7pROhWMp/fxp07oNIEQZd5bbfAH22ohQ=="
|
||||
"version": "12.12.54",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.54.tgz",
|
||||
"integrity": "sha512-ge4xZ3vSBornVYlDnk7yZ0gK6ChHf/CHB7Gl1I0Jhah8DDnEQqBzgohYG4FX4p81TNirSETOiSyn+y1r9/IR6w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -14176,9 +14176,9 @@
|
||||
}
|
||||
},
|
||||
"rpc-websockets": {
|
||||
"version": "5.2.4",
|
||||
"resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-5.2.4.tgz",
|
||||
"integrity": "sha512-6jqeJK/18hPTsmeiN+K9O4miZiAmrIncgxfPXHwuGWs9BClA2zC3fOnTThRWo4blkrjH59oKKi0KMxSK+wdtNw==",
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-5.3.1.tgz",
|
||||
"integrity": "sha512-rIxEl1BbXRlIA9ON7EmY/2GUM7RLMy8zrUPTiLPFiYnYOz0I3PXfCmDDrge5vt4pW4oIcAXBDvgZuJ1jlY5+VA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.8.7",
|
||||
"assert-args": "^1.2.1",
|
||||
@ -15386,9 +15386,8 @@
|
||||
}
|
||||
},
|
||||
"superstruct": {
|
||||
"version": "0.10.12",
|
||||
"resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.10.12.tgz",
|
||||
"integrity": "sha512-FiNhfegyytDI0QxrrEoeGknFM28SnoHqCBpkWewUm8jRNj74NVxLpiiePvkOo41Ze/aKMSHa/twWjNF81mKaQQ=="
|
||||
"version": "github:solana-labs/superstruct#097ee6e2553ea609331bb3b2965a3b778d42015f",
|
||||
"from": "github:solana-labs/superstruct"
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@react-hook/debounce": "^3.0.0",
|
||||
"@solana/web3.js": "^0.64.0",
|
||||
"@solana/web3.js": "^0.66.0",
|
||||
"@testing-library/jest-dom": "^5.11.2",
|
||||
"@testing-library/react": "^10.4.8",
|
||||
"@testing-library/user-event": "^12.1.0",
|
||||
@ -30,7 +30,7 @@
|
||||
"react-select": "^3.1.0",
|
||||
"socket.io-client": "^2.3.0",
|
||||
"solana-sdk-wasm": "file:wasm/pkg",
|
||||
"superstruct": "^0.10.12",
|
||||
"superstruct": "github:solana-labs/superstruct",
|
||||
"typescript": "^3.9.7",
|
||||
"wasm-loader": "^1.3.0"
|
||||
},
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { PublicKey, StakeProgram, TokenAccountInfo } from "@solana/web3.js";
|
||||
import { PublicKey, StakeProgram } from "@solana/web3.js";
|
||||
import {
|
||||
FetchStatus,
|
||||
useFetchAccountInfo,
|
||||
@ -16,6 +16,7 @@ import { useFetchAccountHistory } from "providers/accounts/history";
|
||||
import {
|
||||
useFetchAccountOwnedTokens,
|
||||
useAccountOwnedTokens,
|
||||
TokenAccountData,
|
||||
} from "providers/accounts/tokens";
|
||||
import { useCluster, ClusterStatus } from "providers/cluster";
|
||||
import Address from "./common/Address";
|
||||
@ -158,7 +159,7 @@ function TokensCard({ pubkey }: { pubkey: PublicKey }) {
|
||||
);
|
||||
}
|
||||
|
||||
const mappedTokens = new Map<string, TokenAccountInfo>();
|
||||
const mappedTokens = new Map<string, TokenAccountData>();
|
||||
for (const token of tokens) {
|
||||
const mintAddress = token.mint.toBase58();
|
||||
const tokenInfo = mappedTokens.get(mintAddress);
|
||||
|
@ -25,6 +25,8 @@ import InfoTooltip from "components/InfoTooltip";
|
||||
import { isCached } from "providers/transactions/cached";
|
||||
import Address from "./common/Address";
|
||||
import Signature from "./common/Signature";
|
||||
import { intoTransactionInstruction } from "utils/tx";
|
||||
import { TokenDetailsCard } from "./instruction/token/TokenDetailsCard";
|
||||
|
||||
type Props = { signature: TransactionSignature };
|
||||
export default function TransactionDetails({ signature }: Props) {
|
||||
@ -91,12 +93,17 @@ function StatusCard({ signature }: Props) {
|
||||
};
|
||||
|
||||
const fee = details?.transaction?.meta?.fee;
|
||||
const blockhash = details?.transaction?.transaction.recentBlockhash;
|
||||
const ix = details?.transaction?.transaction.instructions[0];
|
||||
const isNonce =
|
||||
ix &&
|
||||
SystemProgram.programId.equals(ix.programId) &&
|
||||
SystemInstruction.decodeInstructionType(ix) === "AdvanceNonceAccount";
|
||||
const transaction = details?.transaction?.transaction;
|
||||
const blockhash = transaction?.message.recentBlockhash;
|
||||
const isNonce = (() => {
|
||||
if (!transaction) return false;
|
||||
const ix = intoTransactionInstruction(transaction, 0);
|
||||
return (
|
||||
ix &&
|
||||
SystemProgram.programId.equals(ix.programId) &&
|
||||
SystemInstruction.decodeInstructionType(ix) === "AdvanceNonceAccount"
|
||||
);
|
||||
})();
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
@ -188,10 +195,7 @@ function AccountsCard({ signature }: Props) {
|
||||
const refreshStatus = () => fetchStatus(signature);
|
||||
const refreshDetails = () => fetchDetails(signature);
|
||||
const transaction = details?.transaction?.transaction;
|
||||
const message = React.useMemo(() => {
|
||||
return transaction?.compileMessage();
|
||||
}, [transaction]);
|
||||
|
||||
const message = transaction?.message;
|
||||
const status = useTransactionStatus(signature);
|
||||
|
||||
if (!status || !status.info) {
|
||||
@ -219,10 +223,11 @@ function AccountsCard({ signature }: Props) {
|
||||
return <ErrorCard retry={refreshDetails} text="Metadata Missing" />;
|
||||
}
|
||||
|
||||
const accountRows = message.accountKeys.map((pubkey, index) => {
|
||||
const accountRows = message.accountKeys.map((account, index) => {
|
||||
const pre = meta.preBalances[index];
|
||||
const post = meta.postBalances[index];
|
||||
const key = pubkey.toBase58();
|
||||
const pubkey = account.pubkey;
|
||||
const key = account.pubkey.toBase58();
|
||||
const renderChange = () => {
|
||||
const change = post - pre;
|
||||
if (change === 0) return "";
|
||||
@ -245,13 +250,13 @@ function AccountsCard({ signature }: Props) {
|
||||
{index === 0 && (
|
||||
<span className="badge badge-soft-info mr-1">Fee Payer</span>
|
||||
)}
|
||||
{!message.isAccountWritable(index) && (
|
||||
{!account.writable && (
|
||||
<span className="badge badge-soft-info mr-1">Readonly</span>
|
||||
)}
|
||||
{index < message.header.numRequiredSignatures && (
|
||||
{account.signer && (
|
||||
<span className="badge badge-soft-info mr-1">Signer</span>
|
||||
)}
|
||||
{message.instructions.find((ix) => ix.programIdIndex === index) && (
|
||||
{message.instructions.find((ix) => ix.programId.equals(pubkey)) && (
|
||||
<span className="badge badge-soft-info mr-1">Program</span>
|
||||
)}
|
||||
</td>
|
||||
@ -290,22 +295,50 @@ function InstructionsSection({ signature }: Props) {
|
||||
if (!status || !status.info || !details || !details.transaction) return null;
|
||||
|
||||
const { transaction } = details.transaction;
|
||||
if (transaction.instructions.length === 0) {
|
||||
if (transaction.message.instructions.length === 0) {
|
||||
return <ErrorCard retry={refreshDetails} text="No instructions found" />;
|
||||
}
|
||||
|
||||
const result = status.info.result;
|
||||
const instructionDetails = transaction.instructions.map((ix, index) => {
|
||||
const props = { ix, result, index };
|
||||
const instructionDetails = transaction.message.instructions.map(
|
||||
(next, index) => {
|
||||
if ("parsed" in next) {
|
||||
if (next.program === "spl-token") {
|
||||
return (
|
||||
<TokenDetailsCard
|
||||
key={index}
|
||||
tx={transaction}
|
||||
ix={next}
|
||||
result={result}
|
||||
index={index}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (SystemProgram.programId.equals(ix.programId)) {
|
||||
return <SystemDetailsCard key={index} {...props} />;
|
||||
} else if (StakeProgram.programId.equals(ix.programId)) {
|
||||
return <StakeDetailsCard key={index} {...props} />;
|
||||
} else {
|
||||
return <UnknownDetailsCard key={index} {...props} />;
|
||||
const props = { ix: next, result, index };
|
||||
return <UnknownDetailsCard key={index} {...props} />;
|
||||
}
|
||||
|
||||
const ix = intoTransactionInstruction(transaction, index);
|
||||
if (!ix) {
|
||||
return (
|
||||
<ErrorCard
|
||||
key={index}
|
||||
text="Could not display this instruction, please report"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const props = { ix, result, index };
|
||||
if (SystemProgram.programId.equals(ix.programId)) {
|
||||
return <SystemDetailsCard key={index} {...props} />;
|
||||
} else if (StakeProgram.programId.equals(ix.programId)) {
|
||||
return <StakeDetailsCard key={index} {...props} />;
|
||||
} else {
|
||||
return <UnknownDetailsCard key={index} {...props} />;
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -1,13 +1,18 @@
|
||||
import React from "react";
|
||||
import { TransactionInstruction, SignatureResult } from "@solana/web3.js";
|
||||
import {
|
||||
TransactionInstruction,
|
||||
SignatureResult,
|
||||
ParsedInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import { RawDetails } from "./RawDetails";
|
||||
import { RawParsedDetails } from "./RawParsedDetails";
|
||||
|
||||
type InstructionProps = {
|
||||
title: string;
|
||||
children?: React.ReactNode;
|
||||
result: SignatureResult;
|
||||
index: number;
|
||||
ix: TransactionInstruction;
|
||||
ix: TransactionInstruction | ParsedInstruction;
|
||||
defaultRaw?: boolean;
|
||||
};
|
||||
|
||||
@ -45,7 +50,15 @@ export function InstructionCard({
|
||||
<div className="table-responsive mb-0">
|
||||
<table className="table table-sm table-nowrap card-table">
|
||||
<tbody className="list">
|
||||
{showRaw ? <RawDetails ix={ix} /> : children}
|
||||
{showRaw ? (
|
||||
"parsed" in ix ? (
|
||||
<RawParsedDetails ix={ix} />
|
||||
) : (
|
||||
<RawDetails ix={ix} />
|
||||
)
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
25
explorer/src/components/instruction/RawParsedDetails.tsx
Normal file
25
explorer/src/components/instruction/RawParsedDetails.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import React from "react";
|
||||
import { ParsedInstruction } from "@solana/web3.js";
|
||||
import Address from "components/common/Address";
|
||||
|
||||
export function RawParsedDetails({ ix }: { ix: ParsedInstruction }) {
|
||||
return (
|
||||
<>
|
||||
<tr>
|
||||
<td>Program</td>
|
||||
<td className="text-lg-right">
|
||||
<Address pubkey={ix.programId} alignRight link />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Instruction Data (JSON)</td>
|
||||
<td className="text-lg-right">
|
||||
<pre className="d-inline-block text-left">
|
||||
{JSON.stringify(ix.parsed, null, 2)}
|
||||
</pre>
|
||||
</td>
|
||||
</tr>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,5 +1,9 @@
|
||||
import React from "react";
|
||||
import { TransactionInstruction, SignatureResult } from "@solana/web3.js";
|
||||
import {
|
||||
TransactionInstruction,
|
||||
SignatureResult,
|
||||
ParsedInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import { InstructionCard } from "./InstructionCard";
|
||||
|
||||
export function UnknownDetailsCard({
|
||||
@ -7,7 +11,7 @@ export function UnknownDetailsCard({
|
||||
index,
|
||||
result,
|
||||
}: {
|
||||
ix: TransactionInstruction;
|
||||
ix: TransactionInstruction | ParsedInstruction;
|
||||
index: number;
|
||||
result: SignatureResult;
|
||||
}) {
|
||||
|
@ -0,0 +1,88 @@
|
||||
import React from "react";
|
||||
import { coerce } from "superstruct";
|
||||
import {
|
||||
SignatureResult,
|
||||
ParsedTransaction,
|
||||
PublicKey,
|
||||
ParsedInstruction,
|
||||
} from "@solana/web3.js";
|
||||
|
||||
import { UnknownDetailsCard } from "../UnknownDetailsCard";
|
||||
import { InstructionCard } from "../InstructionCard";
|
||||
import Address from "components/common/Address";
|
||||
import { ParsedInstructionInfo, IX_STRUCTS } from "./types";
|
||||
|
||||
const IX_TITLES = {
|
||||
initializeMint: "Initialize Mint",
|
||||
initializeAccount: "Initialize Account",
|
||||
initializeMultisig: "Initialize Multisig",
|
||||
transfer: "Transfer",
|
||||
approve: "Approve",
|
||||
revoke: "Revoke",
|
||||
setOwner: "Set Owner",
|
||||
mintTo: "Mint To",
|
||||
burn: "Burn",
|
||||
closeAccount: "Close Account",
|
||||
};
|
||||
|
||||
type DetailsProps = {
|
||||
tx: ParsedTransaction;
|
||||
ix: ParsedInstruction;
|
||||
result: SignatureResult;
|
||||
index: number;
|
||||
};
|
||||
|
||||
export function TokenDetailsCard(props: DetailsProps) {
|
||||
try {
|
||||
const parsed = coerce(props.ix.parsed, ParsedInstructionInfo);
|
||||
const { type, info } = parsed;
|
||||
const title = `Token: ${IX_TITLES[type]}`;
|
||||
const coerced = coerce(info, IX_STRUCTS[type] as any);
|
||||
return <TokenInstruction title={title} info={coerced} {...props} />;
|
||||
} catch (err) {
|
||||
return <UnknownDetailsCard {...props} />;
|
||||
}
|
||||
}
|
||||
|
||||
type InfoProps = {
|
||||
ix: ParsedInstruction;
|
||||
info: any;
|
||||
result: SignatureResult;
|
||||
index: number;
|
||||
title: string;
|
||||
};
|
||||
|
||||
function TokenInstruction(props: InfoProps) {
|
||||
const attributes = [];
|
||||
for (let key in props.info) {
|
||||
const value = props.info[key];
|
||||
if (value === undefined) continue;
|
||||
|
||||
let tag;
|
||||
if (value instanceof PublicKey) {
|
||||
tag = <Address pubkey={value} alignRight link />;
|
||||
} else {
|
||||
tag = <>{value}</>;
|
||||
}
|
||||
|
||||
key = key.charAt(0).toUpperCase() + key.slice(1);
|
||||
|
||||
attributes.push(
|
||||
<tr key={key}>
|
||||
<td>{key}</td>
|
||||
<td className="text-lg-right">{tag}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<InstructionCard
|
||||
ix={props.ix}
|
||||
index={props.index}
|
||||
result={props.result}
|
||||
title={props.title}
|
||||
>
|
||||
{attributes}
|
||||
</InstructionCard>
|
||||
);
|
||||
}
|
129
explorer/src/components/instruction/token/types.ts
Normal file
129
explorer/src/components/instruction/token/types.ts
Normal file
@ -0,0 +1,129 @@
|
||||
import {
|
||||
enums,
|
||||
object,
|
||||
any,
|
||||
StructType,
|
||||
coercion,
|
||||
struct,
|
||||
number,
|
||||
optional,
|
||||
array,
|
||||
} from "superstruct";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
const PubkeyValue = struct("Pubkey", (value) => value instanceof PublicKey);
|
||||
const Pubkey = coercion(PubkeyValue, (value) => {
|
||||
if (typeof value === "string") return new PublicKey(value);
|
||||
throw new Error("invalid pubkey");
|
||||
});
|
||||
|
||||
const InitializeMint = object({
|
||||
mint: Pubkey,
|
||||
amount: number(),
|
||||
decimals: number(),
|
||||
owner: optional(Pubkey),
|
||||
account: optional(Pubkey),
|
||||
});
|
||||
|
||||
const InitializeAccount = object({
|
||||
account: Pubkey,
|
||||
mint: Pubkey,
|
||||
owner: Pubkey,
|
||||
});
|
||||
|
||||
const InitializeMultisig = object({
|
||||
multisig: Pubkey,
|
||||
signers: array(Pubkey),
|
||||
m: number(),
|
||||
});
|
||||
|
||||
const Transfer = object({
|
||||
source: Pubkey,
|
||||
destination: Pubkey,
|
||||
amount: number(),
|
||||
authority: optional(Pubkey),
|
||||
multisigAuthority: optional(Pubkey),
|
||||
signers: optional(array(Pubkey)),
|
||||
});
|
||||
|
||||
const Approve = object({
|
||||
source: Pubkey,
|
||||
delegate: Pubkey,
|
||||
amount: number(),
|
||||
owner: optional(Pubkey),
|
||||
multisigOwner: optional(Pubkey),
|
||||
signers: optional(array(Pubkey)),
|
||||
});
|
||||
|
||||
const Revoke = object({
|
||||
source: Pubkey,
|
||||
owner: optional(Pubkey),
|
||||
multisigOwner: optional(Pubkey),
|
||||
signers: optional(array(Pubkey)),
|
||||
});
|
||||
|
||||
const SetOwner = object({
|
||||
owned: Pubkey,
|
||||
newOwner: Pubkey,
|
||||
owner: optional(Pubkey),
|
||||
multisigOwner: optional(Pubkey),
|
||||
signers: optional(array(Pubkey)),
|
||||
});
|
||||
|
||||
const MintTo = object({
|
||||
mint: Pubkey,
|
||||
account: Pubkey,
|
||||
amount: number(),
|
||||
owner: optional(Pubkey),
|
||||
multisigOwner: optional(Pubkey),
|
||||
signers: optional(array(Pubkey)),
|
||||
});
|
||||
|
||||
const Burn = object({
|
||||
account: Pubkey,
|
||||
amount: number(),
|
||||
authority: optional(Pubkey),
|
||||
multisigAuthority: optional(Pubkey),
|
||||
signers: optional(array(Pubkey)),
|
||||
});
|
||||
|
||||
const CloseAccount = object({
|
||||
account: Pubkey,
|
||||
destination: Pubkey,
|
||||
owner: optional(Pubkey),
|
||||
multisigOwner: optional(Pubkey),
|
||||
signers: optional(array(Pubkey)),
|
||||
});
|
||||
|
||||
type TokenInstructionType = StructType<typeof TokenInstructionType>;
|
||||
const TokenInstructionType = enums([
|
||||
"initializeMint",
|
||||
"initializeAccount",
|
||||
"initializeMultisig",
|
||||
"transfer",
|
||||
"approve",
|
||||
"revoke",
|
||||
"setOwner",
|
||||
"mintTo",
|
||||
"burn",
|
||||
"closeAccount",
|
||||
]);
|
||||
|
||||
export const IX_STRUCTS = {
|
||||
initializeMint: InitializeMint,
|
||||
initializeAccount: InitializeAccount,
|
||||
initializeMultisig: InitializeMultisig,
|
||||
transfer: Transfer,
|
||||
approve: Approve,
|
||||
revoke: Revoke,
|
||||
setOwner: SetOwner,
|
||||
mintTo: MintTo,
|
||||
burn: Burn,
|
||||
closeAccount: CloseAccount,
|
||||
};
|
||||
|
||||
export type ParsedInstructionInfo = StructType<typeof ParsedInstructionInfo>;
|
||||
export const ParsedInstructionInfo = object({
|
||||
type: TokenInstructionType,
|
||||
info: any(),
|
||||
});
|
@ -1,17 +1,36 @@
|
||||
import React from "react";
|
||||
import { Connection, PublicKey, TokenAccountInfo } from "@solana/web3.js";
|
||||
import { Connection, PublicKey } from "@solana/web3.js";
|
||||
import { FetchStatus, useAccounts } from "./index";
|
||||
import { useCluster, Cluster } from "../cluster";
|
||||
import { number, string, boolean, coerce, object, nullable } from "superstruct";
|
||||
|
||||
export type TokenAccountData = {
|
||||
mint: PublicKey;
|
||||
owner: PublicKey;
|
||||
amount: number;
|
||||
isInitialized: boolean;
|
||||
isNative: boolean;
|
||||
};
|
||||
|
||||
const TokenAccountInfo = object({
|
||||
mint: string(),
|
||||
owner: string(),
|
||||
amount: number(),
|
||||
delegate: nullable(string()),
|
||||
delegatedAmount: number(),
|
||||
isInitialized: boolean(),
|
||||
isNative: boolean(),
|
||||
});
|
||||
|
||||
interface AccountTokens {
|
||||
status: FetchStatus;
|
||||
tokens?: TokenAccountInfo[];
|
||||
tokens?: TokenAccountData[];
|
||||
}
|
||||
|
||||
interface Update {
|
||||
pubkey: PublicKey;
|
||||
status: FetchStatus;
|
||||
tokens?: TokenAccountInfo[];
|
||||
tokens?: TokenAccountData[];
|
||||
}
|
||||
|
||||
type Action = Update | "clear";
|
||||
@ -98,8 +117,18 @@ async function fetchAccountTokens(
|
||||
const { value } = await new Connection(
|
||||
url,
|
||||
"recent"
|
||||
).getTokenAccountsByOwner(pubkey, { programId: TOKEN_PROGRAM_ID });
|
||||
tokens = value.map((accountInfo) => accountInfo.account.data);
|
||||
).getParsedTokenAccountsByOwner(pubkey, { programId: TOKEN_PROGRAM_ID });
|
||||
tokens = value.map((accountInfo) => {
|
||||
const parsedInfo = accountInfo.account.data.parsed.info;
|
||||
const info = coerce(parsedInfo, TokenAccountInfo);
|
||||
return {
|
||||
mint: new PublicKey(info.mint),
|
||||
owner: new PublicKey(info.owner),
|
||||
amount: info.amount,
|
||||
isInitialized: info.isInitialized,
|
||||
isNative: info.isNative,
|
||||
};
|
||||
});
|
||||
status = FetchStatus.Fetched;
|
||||
} catch (error) {
|
||||
status = FetchStatus.FetchFailed;
|
||||
|
@ -1,49 +1,29 @@
|
||||
import React from "react";
|
||||
import io from "socket.io-client";
|
||||
|
||||
import { object, number, is, StructType, any } from "superstruct";
|
||||
import { pick, number, is, StructType } from "superstruct";
|
||||
import { useCluster, Cluster } from "providers/cluster";
|
||||
|
||||
// TODO: use `partial` when it is fixed
|
||||
// https://github.com/ianstormtaylor/superstruct/issues/405
|
||||
const DashboardInfo = object({
|
||||
activatedStake: number(),
|
||||
const DashboardInfo = pick({
|
||||
avgBlockTime_1h: number(),
|
||||
avgBlockTime_1min: number(),
|
||||
circulatingSupply: number(),
|
||||
dailyPriceChange: number(),
|
||||
dailyVolume: number(),
|
||||
delinquentStake: number(),
|
||||
epochInfo: object({
|
||||
absoluteEpochStartSlot: number(),
|
||||
epochInfo: pick({
|
||||
absoluteSlot: number(),
|
||||
blockHeight: number(),
|
||||
epoch: number(),
|
||||
slotIndex: number(),
|
||||
slotsInEpoch: number(),
|
||||
}),
|
||||
stakingYield: number(),
|
||||
tokenPrice: number(),
|
||||
totalDelegatedStake: number(),
|
||||
totalSupply: number(),
|
||||
});
|
||||
|
||||
// TODO: use `partial` when it is fixed
|
||||
// https://github.com/ianstormtaylor/superstruct/issues/405
|
||||
const RootInfo = object({
|
||||
currentLeader: any(),
|
||||
nextLeaders: any(),
|
||||
const RootInfo = pick({
|
||||
root: number(),
|
||||
servedSlots: any(),
|
||||
});
|
||||
|
||||
export const PERF_UPDATE_SEC = 5;
|
||||
|
||||
// TODO: use `partial` when it is fixed
|
||||
// https://github.com/ianstormtaylor/superstruct/issues/405
|
||||
const PerformanceInfo = object({
|
||||
const PerformanceInfo = pick({
|
||||
avgTPS: number(),
|
||||
perfHistory: any(),
|
||||
totalTransactionCount: number(),
|
||||
});
|
||||
|
||||
|
@ -8,10 +8,11 @@
|
||||
import { TransactionStatusInfo } from "./index";
|
||||
import {
|
||||
Transaction,
|
||||
ConfirmedTransaction,
|
||||
ParsedConfirmedTransaction,
|
||||
Message,
|
||||
clusterApiUrl,
|
||||
} from "@solana/web3.js";
|
||||
import { intoParsedTransaction } from "utils/tx";
|
||||
|
||||
export const isCached = (url: string, signature: string): boolean => {
|
||||
return url === clusterApiUrl("mainnet-beta") && signature in CACHED_STATUSES;
|
||||
@ -62,214 +63,228 @@ export const CACHED_STATUSES: { [key: string]: TransactionStatusInfo } = {
|
||||
},
|
||||
};
|
||||
|
||||
export const CACHED_DETAILS: { [key: string]: ConfirmedTransaction } = {
|
||||
export const CACHED_DETAILS: { [key: string]: ParsedConfirmedTransaction } = {
|
||||
uQf4pS38FjRF294QFEXizhYkZFjSR9ZSBvvV6MV5b4VpdfRnK3PY9TWZ2qHMQKtte3XwKVLcWqsTF6wL9NEZMty: {
|
||||
meta: null,
|
||||
slot: 10440804,
|
||||
transaction: Transaction.populate(
|
||||
new Message({
|
||||
accountKeys: [
|
||||
"2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S",
|
||||
"4C6NCcLPUgGuBBkV2dJW96mrptMUCp3RG1ft9rqwjFi9",
|
||||
"11111111111111111111111111111111",
|
||||
],
|
||||
header: {
|
||||
numReadonlySignedAccounts: 0,
|
||||
numReadonlyUnsignedAccounts: 1,
|
||||
numRequiredSignatures: 1,
|
||||
},
|
||||
instructions: [
|
||||
{ accounts: [0, 1], data: "3Bxs411UBrj8QXUb", programIdIndex: 2 },
|
||||
],
|
||||
recentBlockhash: "5Aw8MaMYdYtnfJyyrregWMWGgiMtWZ6GtRzeP6Ufo65Z",
|
||||
}),
|
||||
[
|
||||
"uQf4pS38FjRF294QFEXizhYkZFjSR9ZSBvvV6MV5b4VpdfRnK3PY9TWZ2qHMQKtte3XwKVLcWqsTF6wL9NEZMty",
|
||||
]
|
||||
transaction: intoParsedTransaction(
|
||||
Transaction.populate(
|
||||
new Message({
|
||||
accountKeys: [
|
||||
"2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S",
|
||||
"4C6NCcLPUgGuBBkV2dJW96mrptMUCp3RG1ft9rqwjFi9",
|
||||
"11111111111111111111111111111111",
|
||||
],
|
||||
header: {
|
||||
numReadonlySignedAccounts: 0,
|
||||
numReadonlyUnsignedAccounts: 1,
|
||||
numRequiredSignatures: 1,
|
||||
},
|
||||
instructions: [
|
||||
{ accounts: [0, 1], data: "3Bxs411UBrj8QXUb", programIdIndex: 2 },
|
||||
],
|
||||
recentBlockhash: "5Aw8MaMYdYtnfJyyrregWMWGgiMtWZ6GtRzeP6Ufo65Z",
|
||||
}),
|
||||
[
|
||||
"uQf4pS38FjRF294QFEXizhYkZFjSR9ZSBvvV6MV5b4VpdfRnK3PY9TWZ2qHMQKtte3XwKVLcWqsTF6wL9NEZMty",
|
||||
]
|
||||
)
|
||||
),
|
||||
},
|
||||
|
||||
DYrfStEEzbV5sftX8LgUa54Nwnc5m5E1731cqBtiiC66TeXgKpfqZEQTuFY3vhHZ2K1BsaFM3X9FqisR28EtZr8: {
|
||||
meta: null,
|
||||
slot: 10451288,
|
||||
transaction: Transaction.populate(
|
||||
new Message({
|
||||
accountKeys: [
|
||||
"2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S",
|
||||
"4C6NCcLPUgGuBBkV2dJW96mrptMUCp3RG1ft9rqwjFi9",
|
||||
"11111111111111111111111111111111",
|
||||
],
|
||||
header: {
|
||||
numReadonlySignedAccounts: 0,
|
||||
numReadonlyUnsignedAccounts: 1,
|
||||
numRequiredSignatures: 1,
|
||||
},
|
||||
instructions: [
|
||||
{
|
||||
accounts: [0, 1],
|
||||
data: "3Bxs3zwYHuDo723R",
|
||||
programIdIndex: 2,
|
||||
transaction: intoParsedTransaction(
|
||||
Transaction.populate(
|
||||
new Message({
|
||||
accountKeys: [
|
||||
"2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S",
|
||||
"4C6NCcLPUgGuBBkV2dJW96mrptMUCp3RG1ft9rqwjFi9",
|
||||
"11111111111111111111111111111111",
|
||||
],
|
||||
header: {
|
||||
numReadonlySignedAccounts: 0,
|
||||
numReadonlyUnsignedAccounts: 1,
|
||||
numRequiredSignatures: 1,
|
||||
},
|
||||
],
|
||||
recentBlockhash: "4hXYcBdfcadcjfWV17ZwMa4MXe8kbZHYHwr3GzfyqunL",
|
||||
}),
|
||||
[
|
||||
"DYrfStEEzbV5sftX8LgUa54Nwnc5m5E1731cqBtiiC66TeXgKpfqZEQTuFY3vhHZ2K1BsaFM3X9FqisR28EtZr8",
|
||||
]
|
||||
instructions: [
|
||||
{
|
||||
accounts: [0, 1],
|
||||
data: "3Bxs3zwYHuDo723R",
|
||||
programIdIndex: 2,
|
||||
},
|
||||
],
|
||||
recentBlockhash: "4hXYcBdfcadcjfWV17ZwMa4MXe8kbZHYHwr3GzfyqunL",
|
||||
}),
|
||||
[
|
||||
"DYrfStEEzbV5sftX8LgUa54Nwnc5m5E1731cqBtiiC66TeXgKpfqZEQTuFY3vhHZ2K1BsaFM3X9FqisR28EtZr8",
|
||||
]
|
||||
)
|
||||
),
|
||||
},
|
||||
|
||||
"3bLx2PLpkxCxJA5P7HVe8asFdSWXVAh1DrxfkqWE9bWvPRxXE2hqwj1vuSC858fUw3XAGQcHbJknhtNdxY2sehab": {
|
||||
meta: null,
|
||||
slot: 10516588,
|
||||
transaction: Transaction.populate(
|
||||
new Message({
|
||||
accountKeys: [
|
||||
"2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S",
|
||||
"4C6NCcLPUgGuBBkV2dJW96mrptMUCp3RG1ft9rqwjFi9",
|
||||
"11111111111111111111111111111111",
|
||||
],
|
||||
header: {
|
||||
numReadonlySignedAccounts: 0,
|
||||
numReadonlyUnsignedAccounts: 1,
|
||||
numRequiredSignatures: 1,
|
||||
},
|
||||
instructions: [
|
||||
{
|
||||
accounts: [0, 1],
|
||||
data: "3Bxs3zwYHuDo723R",
|
||||
programIdIndex: 2,
|
||||
transaction: intoParsedTransaction(
|
||||
Transaction.populate(
|
||||
new Message({
|
||||
accountKeys: [
|
||||
"2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S",
|
||||
"4C6NCcLPUgGuBBkV2dJW96mrptMUCp3RG1ft9rqwjFi9",
|
||||
"11111111111111111111111111111111",
|
||||
],
|
||||
header: {
|
||||
numReadonlySignedAccounts: 0,
|
||||
numReadonlyUnsignedAccounts: 1,
|
||||
numRequiredSignatures: 1,
|
||||
},
|
||||
],
|
||||
recentBlockhash: "HSzTGt3PJMeQtFr94gEdeZqTRaBxgS8Wf1zq3MDdNT3L",
|
||||
}),
|
||||
[
|
||||
"3bLx2PLpkxCxJA5P7HVe8asFdSWXVAh1DrxfkqWE9bWvPRxXE2hqwj1vuSC858fUw3XAGQcHbJknhtNdxY2sehab",
|
||||
]
|
||||
instructions: [
|
||||
{
|
||||
accounts: [0, 1],
|
||||
data: "3Bxs3zwYHuDo723R",
|
||||
programIdIndex: 2,
|
||||
},
|
||||
],
|
||||
recentBlockhash: "HSzTGt3PJMeQtFr94gEdeZqTRaBxgS8Wf1zq3MDdNT3L",
|
||||
}),
|
||||
[
|
||||
"3bLx2PLpkxCxJA5P7HVe8asFdSWXVAh1DrxfkqWE9bWvPRxXE2hqwj1vuSC858fUw3XAGQcHbJknhtNdxY2sehab",
|
||||
]
|
||||
)
|
||||
),
|
||||
},
|
||||
|
||||
"3fE8xNgyxbwbvA5MX3wM87ahDDgCVEaaMMSa8UCWWNxojaRYBgrQyiKXLSxcryMWb7sEyVLBWyqUaRWnQCroSqjY": {
|
||||
meta: null,
|
||||
slot: 10575124,
|
||||
transaction: Transaction.populate(
|
||||
new Message({
|
||||
accountKeys: [
|
||||
"2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S",
|
||||
"4C6NCcLPUgGuBBkV2dJW96mrptMUCp3RG1ft9rqwjFi9",
|
||||
"11111111111111111111111111111111",
|
||||
],
|
||||
header: {
|
||||
numReadonlySignedAccounts: 0,
|
||||
numReadonlyUnsignedAccounts: 1,
|
||||
numRequiredSignatures: 1,
|
||||
},
|
||||
instructions: [
|
||||
{
|
||||
accounts: [0, 1],
|
||||
data: "3Bxs3zuKU6mRKSqD",
|
||||
programIdIndex: 2,
|
||||
transaction: intoParsedTransaction(
|
||||
Transaction.populate(
|
||||
new Message({
|
||||
accountKeys: [
|
||||
"2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S",
|
||||
"4C6NCcLPUgGuBBkV2dJW96mrptMUCp3RG1ft9rqwjFi9",
|
||||
"11111111111111111111111111111111",
|
||||
],
|
||||
header: {
|
||||
numReadonlySignedAccounts: 0,
|
||||
numReadonlyUnsignedAccounts: 1,
|
||||
numRequiredSignatures: 1,
|
||||
},
|
||||
],
|
||||
recentBlockhash: "6f6TBMhUoypfR5HHnEqC6VoooKxEcNad5W3Sf63j9MSD",
|
||||
}),
|
||||
[
|
||||
"3fE8xNgyxbwbvA5MX3wM87ahDDgCVEaaMMSa8UCWWNxojaRYBgrQyiKXLSxcryMWb7sEyVLBWyqUaRWnQCroSqjY",
|
||||
]
|
||||
instructions: [
|
||||
{
|
||||
accounts: [0, 1],
|
||||
data: "3Bxs3zuKU6mRKSqD",
|
||||
programIdIndex: 2,
|
||||
},
|
||||
],
|
||||
recentBlockhash: "6f6TBMhUoypfR5HHnEqC6VoooKxEcNad5W3Sf63j9MSD",
|
||||
}),
|
||||
[
|
||||
"3fE8xNgyxbwbvA5MX3wM87ahDDgCVEaaMMSa8UCWWNxojaRYBgrQyiKXLSxcryMWb7sEyVLBWyqUaRWnQCroSqjY",
|
||||
]
|
||||
)
|
||||
),
|
||||
},
|
||||
|
||||
"5PWymGjKV7T1oqeqGn139EHFyjNM2dnNhHCUcfD2bmdj8cfF95HpY1uJ84W89c4sJQnmyZxXcYrcjumx2jHUvxZQ": {
|
||||
meta: null,
|
||||
slot: 12447825,
|
||||
transaction: Transaction.populate(
|
||||
new Message({
|
||||
accountKeys: [
|
||||
"HCV5dGFJXRrJ3jhDYA4DCeb9TEDTwGGYXtT3wHksu2Zr",
|
||||
"4C6NCcLPUgGuBBkV2dJW96mrptMUCp3RG1ft9rqwjFi9",
|
||||
"11111111111111111111111111111111",
|
||||
],
|
||||
header: {
|
||||
numReadonlySignedAccounts: 0,
|
||||
numReadonlyUnsignedAccounts: 1,
|
||||
numRequiredSignatures: 1,
|
||||
},
|
||||
instructions: [
|
||||
{
|
||||
accounts: [0, 1],
|
||||
data: "3Bxs3zrfhSqZJTR1",
|
||||
programIdIndex: 2,
|
||||
transaction: intoParsedTransaction(
|
||||
Transaction.populate(
|
||||
new Message({
|
||||
accountKeys: [
|
||||
"HCV5dGFJXRrJ3jhDYA4DCeb9TEDTwGGYXtT3wHksu2Zr",
|
||||
"4C6NCcLPUgGuBBkV2dJW96mrptMUCp3RG1ft9rqwjFi9",
|
||||
"11111111111111111111111111111111",
|
||||
],
|
||||
header: {
|
||||
numReadonlySignedAccounts: 0,
|
||||
numReadonlyUnsignedAccounts: 1,
|
||||
numRequiredSignatures: 1,
|
||||
},
|
||||
],
|
||||
recentBlockhash: "3HJNFraT7XGAqMrQs83EKwDGB6LpHVwUMQKGaYMNY49E",
|
||||
}),
|
||||
[
|
||||
"5PWymGjKV7T1oqeqGn139EHFyjNM2dnNhHCUcfD2bmdj8cfF95HpY1uJ84W89c4sJQnmyZxXcYrcjumx2jHUvxZQ",
|
||||
]
|
||||
instructions: [
|
||||
{
|
||||
accounts: [0, 1],
|
||||
data: "3Bxs3zrfhSqZJTR1",
|
||||
programIdIndex: 2,
|
||||
},
|
||||
],
|
||||
recentBlockhash: "3HJNFraT7XGAqMrQs83EKwDGB6LpHVwUMQKGaYMNY49E",
|
||||
}),
|
||||
[
|
||||
"5PWymGjKV7T1oqeqGn139EHFyjNM2dnNhHCUcfD2bmdj8cfF95HpY1uJ84W89c4sJQnmyZxXcYrcjumx2jHUvxZQ",
|
||||
]
|
||||
)
|
||||
),
|
||||
},
|
||||
|
||||
"5K4KuqTTRNtzfpxWiwnkePzGfsa3tBEmpMy7vQFR3KWFAZNVY9tvoSaz1Yt5dKxcgsZPio2EsASVDGbQB1HvirGD": {
|
||||
meta: null,
|
||||
slot: 12450728,
|
||||
transaction: Transaction.populate(
|
||||
new Message({
|
||||
accountKeys: [
|
||||
"6yKHERk8rsbmJxvMpPuwPs1ct3hRiP7xaJF2tvnGU6nK",
|
||||
"4C6NCcLPUgGuBBkV2dJW96mrptMUCp3RG1ft9rqwjFi9",
|
||||
"3o6xgkJ9sTmDeQWyfj3sxwon18fXJB9PV5LDc8sfgR4a",
|
||||
"11111111111111111111111111111111",
|
||||
],
|
||||
header: {
|
||||
numReadonlySignedAccounts: 0,
|
||||
numReadonlyUnsignedAccounts: 1,
|
||||
numRequiredSignatures: 2,
|
||||
},
|
||||
instructions: [
|
||||
{
|
||||
accounts: [1, 2],
|
||||
data: "3Bxs3ztRCp3tH1yZ",
|
||||
programIdIndex: 3,
|
||||
transaction: intoParsedTransaction(
|
||||
Transaction.populate(
|
||||
new Message({
|
||||
accountKeys: [
|
||||
"6yKHERk8rsbmJxvMpPuwPs1ct3hRiP7xaJF2tvnGU6nK",
|
||||
"4C6NCcLPUgGuBBkV2dJW96mrptMUCp3RG1ft9rqwjFi9",
|
||||
"3o6xgkJ9sTmDeQWyfj3sxwon18fXJB9PV5LDc8sfgR4a",
|
||||
"11111111111111111111111111111111",
|
||||
],
|
||||
header: {
|
||||
numReadonlySignedAccounts: 0,
|
||||
numReadonlyUnsignedAccounts: 1,
|
||||
numRequiredSignatures: 2,
|
||||
},
|
||||
],
|
||||
recentBlockhash: "8eXVUNRxrDgpsEuoTWyLay1LUh2djc3Y8cw2owXRN8cU",
|
||||
}),
|
||||
[
|
||||
"5K4KuqTTRNtzfpxWiwnkePzGfsa3tBEmpMy7vQFR3KWFAZNVY9tvoSaz1Yt5dKxcgsZPio2EsASVDGbQB1HvirGD",
|
||||
"37tvpG1eAeEBizJPhJvmpC2BY8npwy6K1wrZdNwdRAfWSbkerY3ZwYAPMHbrzoq7tthvWC2qFU28niqLPxbukeXF",
|
||||
]
|
||||
instructions: [
|
||||
{
|
||||
accounts: [1, 2],
|
||||
data: "3Bxs3ztRCp3tH1yZ",
|
||||
programIdIndex: 3,
|
||||
},
|
||||
],
|
||||
recentBlockhash: "8eXVUNRxrDgpsEuoTWyLay1LUh2djc3Y8cw2owXRN8cU",
|
||||
}),
|
||||
[
|
||||
"5K4KuqTTRNtzfpxWiwnkePzGfsa3tBEmpMy7vQFR3KWFAZNVY9tvoSaz1Yt5dKxcgsZPio2EsASVDGbQB1HvirGD",
|
||||
"37tvpG1eAeEBizJPhJvmpC2BY8npwy6K1wrZdNwdRAfWSbkerY3ZwYAPMHbrzoq7tthvWC2qFU28niqLPxbukeXF",
|
||||
]
|
||||
)
|
||||
),
|
||||
},
|
||||
|
||||
"45pGoC4Rr3fJ1TKrsiRkhHRbdUeX7633XAGVec6XzVdpRbzQgHhe6ZC6Uq164MPWtiqMg7wCkC6Wy3jy2BqsDEKf": {
|
||||
meta: null,
|
||||
slot: 12972684,
|
||||
transaction: Transaction.populate(
|
||||
new Message({
|
||||
accountKeys: [
|
||||
"6yKHERk8rsbmJxvMpPuwPs1ct3hRiP7xaJF2tvnGU6nK",
|
||||
"3o6xgkJ9sTmDeQWyfj3sxwon18fXJB9PV5LDc8sfgR4a",
|
||||
"1nc1nerator11111111111111111111111111111111",
|
||||
"11111111111111111111111111111111",
|
||||
],
|
||||
header: {
|
||||
numReadonlySignedAccounts: 0,
|
||||
numReadonlyUnsignedAccounts: 1,
|
||||
numRequiredSignatures: 2,
|
||||
},
|
||||
instructions: [
|
||||
{
|
||||
accounts: [1, 2],
|
||||
data: "3Bxs4NNAyLXRbuZZ",
|
||||
programIdIndex: 3,
|
||||
transaction: intoParsedTransaction(
|
||||
Transaction.populate(
|
||||
new Message({
|
||||
accountKeys: [
|
||||
"6yKHERk8rsbmJxvMpPuwPs1ct3hRiP7xaJF2tvnGU6nK",
|
||||
"3o6xgkJ9sTmDeQWyfj3sxwon18fXJB9PV5LDc8sfgR4a",
|
||||
"1nc1nerator11111111111111111111111111111111",
|
||||
"11111111111111111111111111111111",
|
||||
],
|
||||
header: {
|
||||
numReadonlySignedAccounts: 0,
|
||||
numReadonlyUnsignedAccounts: 1,
|
||||
numRequiredSignatures: 2,
|
||||
},
|
||||
],
|
||||
recentBlockhash: "2xnatNUtSbeMRwi3k4vxPwXxeKFQYVuCNRg2rAgydWVP",
|
||||
}),
|
||||
[
|
||||
"45pGoC4Rr3fJ1TKrsiRkhHRbdUeX7633XAGVec6XzVdpRbzQgHhe6ZC6Uq164MPWtiqMg7wCkC6Wy3jy2BqsDEKf",
|
||||
"2E7CDMTssxTYkdetCKVWQv9X2KNDPiuZrT2Y7647PhFEXuAWWxmHJb3ryCmP29ocQ1SNc7VyJjjm4X3jE8xWDmGY",
|
||||
]
|
||||
instructions: [
|
||||
{
|
||||
accounts: [1, 2],
|
||||
data: "3Bxs4NNAyLXRbuZZ",
|
||||
programIdIndex: 3,
|
||||
},
|
||||
],
|
||||
recentBlockhash: "2xnatNUtSbeMRwi3k4vxPwXxeKFQYVuCNRg2rAgydWVP",
|
||||
}),
|
||||
[
|
||||
"45pGoC4Rr3fJ1TKrsiRkhHRbdUeX7633XAGVec6XzVdpRbzQgHhe6ZC6Uq164MPWtiqMg7wCkC6Wy3jy2BqsDEKf",
|
||||
"2E7CDMTssxTYkdetCKVWQv9X2KNDPiuZrT2Y7647PhFEXuAWWxmHJb3ryCmP29ocQ1SNc7VyJjjm4X3jE8xWDmGY",
|
||||
]
|
||||
)
|
||||
),
|
||||
},
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ import React from "react";
|
||||
import {
|
||||
Connection,
|
||||
TransactionSignature,
|
||||
ConfirmedTransaction,
|
||||
ParsedConfirmedTransaction,
|
||||
} from "@solana/web3.js";
|
||||
import { useCluster } from "../cluster";
|
||||
import { useTransactions, FetchStatus } from "./index";
|
||||
@ -10,7 +10,7 @@ import { CACHED_DETAILS, isCached } from "./cached";
|
||||
|
||||
export interface Details {
|
||||
fetchStatus: FetchStatus;
|
||||
transaction: ConfirmedTransaction | null;
|
||||
transaction: ParsedConfirmedTransaction | null;
|
||||
}
|
||||
|
||||
type State = { [signature: string]: Details };
|
||||
@ -25,7 +25,7 @@ interface Update {
|
||||
type: ActionType.Update;
|
||||
signature: string;
|
||||
fetchStatus: FetchStatus;
|
||||
transaction: ConfirmedTransaction | null;
|
||||
transaction: ParsedConfirmedTransaction | null;
|
||||
}
|
||||
|
||||
interface Add {
|
||||
@ -134,7 +134,7 @@ async function fetchDetails(
|
||||
fetchStatus = FetchStatus.Fetched;
|
||||
} else {
|
||||
try {
|
||||
transaction = await new Connection(url).getConfirmedTransaction(
|
||||
transaction = await new Connection(url).getParsedConfirmedTransaction(
|
||||
signature
|
||||
);
|
||||
fetchStatus = FetchStatus.Fetched;
|
||||
|
@ -3,7 +3,7 @@
|
||||
// Use this to write your custom SCSS
|
||||
//
|
||||
|
||||
code {
|
||||
code, pre {
|
||||
background-color: $black-dark;
|
||||
color: $white;
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
// Use this to write your custom SCSS
|
||||
//
|
||||
|
||||
code {
|
||||
code, pre {
|
||||
padding: 0.33rem;
|
||||
border-radius: $border-radius;
|
||||
background-color: $gray-200;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import bs58 from "bs58";
|
||||
import {
|
||||
SystemProgram,
|
||||
StakeProgram,
|
||||
@ -7,6 +8,9 @@ import {
|
||||
SYSVAR_RENT_PUBKEY,
|
||||
SYSVAR_REWARDS_PUBKEY,
|
||||
SYSVAR_STAKE_HISTORY_PUBKEY,
|
||||
ParsedTransaction,
|
||||
TransactionInstruction,
|
||||
Transaction,
|
||||
} from "@solana/web3.js";
|
||||
|
||||
const PROGRAM_IDS = {
|
||||
@ -48,3 +52,53 @@ export function displayAddress(address: string): string {
|
||||
address
|
||||
);
|
||||
}
|
||||
|
||||
export function intoTransactionInstruction(
|
||||
tx: ParsedTransaction,
|
||||
index: number
|
||||
): TransactionInstruction | undefined {
|
||||
const message = tx.message;
|
||||
const instruction = message.instructions[index];
|
||||
if ("parsed" in instruction) return;
|
||||
|
||||
const keys = [];
|
||||
for (const account of instruction.accounts) {
|
||||
const accountKey = message.accountKeys.find(({ pubkey }) =>
|
||||
pubkey.equals(account)
|
||||
);
|
||||
if (!accountKey) return;
|
||||
keys.push({
|
||||
pubkey: accountKey.pubkey,
|
||||
isSigner: accountKey.signer,
|
||||
isWritable: accountKey.writable,
|
||||
});
|
||||
}
|
||||
|
||||
return new TransactionInstruction({
|
||||
data: bs58.decode(instruction.data),
|
||||
keys: keys,
|
||||
programId: instruction.programId,
|
||||
});
|
||||
}
|
||||
|
||||
export function intoParsedTransaction(tx: Transaction): ParsedTransaction {
|
||||
const message = tx.compileMessage();
|
||||
return {
|
||||
signatures: tx.signatures.map((value) =>
|
||||
bs58.encode(value.signature as any)
|
||||
),
|
||||
message: {
|
||||
accountKeys: message.accountKeys.map((key, index) => ({
|
||||
pubkey: key,
|
||||
signer: tx.signatures.some(({ publicKey }) => publicKey.equals(key)),
|
||||
writable: message.isAccountWritable(index),
|
||||
})),
|
||||
instructions: message.instructions.map((ix) => ({
|
||||
programId: message.accountKeys[ix.programIdIndex],
|
||||
accounts: ix.accounts.map((index) => message.accountKeys[index]),
|
||||
data: ix.data,
|
||||
})),
|
||||
recentBlockhash: message.recentBlockhash,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
Reference in New Issue
Block a user