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=="
|
"integrity": "sha512-zLtOIToct1EBTbwldkMJsXC2eCsmWOOP7z6UG0M/sCgnPExtIjvVMCpPESvPnMbQzDZytXVy0nvMbUuK2gZs2A=="
|
||||||
},
|
},
|
||||||
"@solana/web3.js": {
|
"@solana/web3.js": {
|
||||||
"version": "0.64.0",
|
"version": "0.66.0",
|
||||||
"resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-0.64.0.tgz",
|
"resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-0.66.0.tgz",
|
||||||
"integrity": "sha512-DlNzAXgNdk7k4Pt6CfcaAutaiXJiog9hxswtzItf0q/0/Um8JvDI1YjnMONE3IKI/jyjmTaxhsQHWAQE42KofQ==",
|
"integrity": "sha512-Uw7ooRWLqrq8I5U21mEryvvF/Eqqh4mq4K2W9Sxuz3boxkz7Ed7aAJVj5C5n1fbQr9I1cxxxgC+D5BHnogfS1A==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.3.1",
|
"@babel/runtime": "^7.3.1",
|
||||||
"bn.js": "^5.0.0",
|
"bn.js": "^5.0.0",
|
||||||
@ -3032,9 +3032,9 @@
|
|||||||
"integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ=="
|
"integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ=="
|
||||||
},
|
},
|
||||||
"@types/lodash": {
|
"@types/lodash": {
|
||||||
"version": "4.14.158",
|
"version": "4.14.159",
|
||||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.158.tgz",
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.159.tgz",
|
||||||
"integrity": "sha512-InCEXJNTv/59yO4VSfuvNrZHt7eeNtWQEgnieIA+mIC+MOWM9arOWG2eQ8Vhk6NbOre6/BidiXhkZYeDY9U35w=="
|
"integrity": "sha512-gF7A72f7WQN33DpqOWw9geApQPh4M3PxluMtaHxWHXEGSN12/WbcEk/eNSqWNQcQhF66VSZ06vCF94CrHwXJDg=="
|
||||||
},
|
},
|
||||||
"@types/minimatch": {
|
"@types/minimatch": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
@ -9055,9 +9055,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "12.12.53",
|
"version": "12.12.54",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.53.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.54.tgz",
|
||||||
"integrity": "sha512-51MYTDTyCziHb70wtGNFRwB4l+5JNvdqzFSkbDvpbftEgVUBEE+T5f7pROhWMp/fxp07oNIEQZd5bbfAH22ohQ=="
|
"integrity": "sha512-ge4xZ3vSBornVYlDnk7yZ0gK6ChHf/CHB7Gl1I0Jhah8DDnEQqBzgohYG4FX4p81TNirSETOiSyn+y1r9/IR6w=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -14176,9 +14176,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rpc-websockets": {
|
"rpc-websockets": {
|
||||||
"version": "5.2.4",
|
"version": "5.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-5.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-5.3.1.tgz",
|
||||||
"integrity": "sha512-6jqeJK/18hPTsmeiN+K9O4miZiAmrIncgxfPXHwuGWs9BClA2zC3fOnTThRWo4blkrjH59oKKi0KMxSK+wdtNw==",
|
"integrity": "sha512-rIxEl1BbXRlIA9ON7EmY/2GUM7RLMy8zrUPTiLPFiYnYOz0I3PXfCmDDrge5vt4pW4oIcAXBDvgZuJ1jlY5+VA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.8.7",
|
"@babel/runtime": "^7.8.7",
|
||||||
"assert-args": "^1.2.1",
|
"assert-args": "^1.2.1",
|
||||||
@ -15386,9 +15386,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"superstruct": {
|
"superstruct": {
|
||||||
"version": "0.10.12",
|
"version": "github:solana-labs/superstruct#097ee6e2553ea609331bb3b2965a3b778d42015f",
|
||||||
"resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.10.12.tgz",
|
"from": "github:solana-labs/superstruct"
|
||||||
"integrity": "sha512-FiNhfegyytDI0QxrrEoeGknFM28SnoHqCBpkWewUm8jRNj74NVxLpiiePvkOo41Ze/aKMSHa/twWjNF81mKaQQ=="
|
|
||||||
},
|
},
|
||||||
"supports-color": {
|
"supports-color": {
|
||||||
"version": "5.5.0",
|
"version": "5.5.0",
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-hook/debounce": "^3.0.0",
|
"@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/jest-dom": "^5.11.2",
|
||||||
"@testing-library/react": "^10.4.8",
|
"@testing-library/react": "^10.4.8",
|
||||||
"@testing-library/user-event": "^12.1.0",
|
"@testing-library/user-event": "^12.1.0",
|
||||||
@ -30,7 +30,7 @@
|
|||||||
"react-select": "^3.1.0",
|
"react-select": "^3.1.0",
|
||||||
"socket.io-client": "^2.3.0",
|
"socket.io-client": "^2.3.0",
|
||||||
"solana-sdk-wasm": "file:wasm/pkg",
|
"solana-sdk-wasm": "file:wasm/pkg",
|
||||||
"superstruct": "^0.10.12",
|
"superstruct": "github:solana-labs/superstruct",
|
||||||
"typescript": "^3.9.7",
|
"typescript": "^3.9.7",
|
||||||
"wasm-loader": "^1.3.0"
|
"wasm-loader": "^1.3.0"
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { PublicKey, StakeProgram, TokenAccountInfo } from "@solana/web3.js";
|
import { PublicKey, StakeProgram } from "@solana/web3.js";
|
||||||
import {
|
import {
|
||||||
FetchStatus,
|
FetchStatus,
|
||||||
useFetchAccountInfo,
|
useFetchAccountInfo,
|
||||||
@ -16,6 +16,7 @@ import { useFetchAccountHistory } from "providers/accounts/history";
|
|||||||
import {
|
import {
|
||||||
useFetchAccountOwnedTokens,
|
useFetchAccountOwnedTokens,
|
||||||
useAccountOwnedTokens,
|
useAccountOwnedTokens,
|
||||||
|
TokenAccountData,
|
||||||
} from "providers/accounts/tokens";
|
} from "providers/accounts/tokens";
|
||||||
import { useCluster, ClusterStatus } from "providers/cluster";
|
import { useCluster, ClusterStatus } from "providers/cluster";
|
||||||
import Address from "./common/Address";
|
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) {
|
for (const token of tokens) {
|
||||||
const mintAddress = token.mint.toBase58();
|
const mintAddress = token.mint.toBase58();
|
||||||
const tokenInfo = mappedTokens.get(mintAddress);
|
const tokenInfo = mappedTokens.get(mintAddress);
|
||||||
|
@ -25,6 +25,8 @@ import InfoTooltip from "components/InfoTooltip";
|
|||||||
import { isCached } from "providers/transactions/cached";
|
import { isCached } from "providers/transactions/cached";
|
||||||
import Address from "./common/Address";
|
import Address from "./common/Address";
|
||||||
import Signature from "./common/Signature";
|
import Signature from "./common/Signature";
|
||||||
|
import { intoTransactionInstruction } from "utils/tx";
|
||||||
|
import { TokenDetailsCard } from "./instruction/token/TokenDetailsCard";
|
||||||
|
|
||||||
type Props = { signature: TransactionSignature };
|
type Props = { signature: TransactionSignature };
|
||||||
export default function TransactionDetails({ signature }: Props) {
|
export default function TransactionDetails({ signature }: Props) {
|
||||||
@ -91,12 +93,17 @@ function StatusCard({ signature }: Props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fee = details?.transaction?.meta?.fee;
|
const fee = details?.transaction?.meta?.fee;
|
||||||
const blockhash = details?.transaction?.transaction.recentBlockhash;
|
const transaction = details?.transaction?.transaction;
|
||||||
const ix = details?.transaction?.transaction.instructions[0];
|
const blockhash = transaction?.message.recentBlockhash;
|
||||||
const isNonce =
|
const isNonce = (() => {
|
||||||
ix &&
|
if (!transaction) return false;
|
||||||
SystemProgram.programId.equals(ix.programId) &&
|
const ix = intoTransactionInstruction(transaction, 0);
|
||||||
SystemInstruction.decodeInstructionType(ix) === "AdvanceNonceAccount";
|
return (
|
||||||
|
ix &&
|
||||||
|
SystemProgram.programId.equals(ix.programId) &&
|
||||||
|
SystemInstruction.decodeInstructionType(ix) === "AdvanceNonceAccount"
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
@ -188,10 +195,7 @@ function AccountsCard({ signature }: Props) {
|
|||||||
const refreshStatus = () => fetchStatus(signature);
|
const refreshStatus = () => fetchStatus(signature);
|
||||||
const refreshDetails = () => fetchDetails(signature);
|
const refreshDetails = () => fetchDetails(signature);
|
||||||
const transaction = details?.transaction?.transaction;
|
const transaction = details?.transaction?.transaction;
|
||||||
const message = React.useMemo(() => {
|
const message = transaction?.message;
|
||||||
return transaction?.compileMessage();
|
|
||||||
}, [transaction]);
|
|
||||||
|
|
||||||
const status = useTransactionStatus(signature);
|
const status = useTransactionStatus(signature);
|
||||||
|
|
||||||
if (!status || !status.info) {
|
if (!status || !status.info) {
|
||||||
@ -219,10 +223,11 @@ function AccountsCard({ signature }: Props) {
|
|||||||
return <ErrorCard retry={refreshDetails} text="Metadata Missing" />;
|
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 pre = meta.preBalances[index];
|
||||||
const post = meta.postBalances[index];
|
const post = meta.postBalances[index];
|
||||||
const key = pubkey.toBase58();
|
const pubkey = account.pubkey;
|
||||||
|
const key = account.pubkey.toBase58();
|
||||||
const renderChange = () => {
|
const renderChange = () => {
|
||||||
const change = post - pre;
|
const change = post - pre;
|
||||||
if (change === 0) return "";
|
if (change === 0) return "";
|
||||||
@ -245,13 +250,13 @@ function AccountsCard({ signature }: Props) {
|
|||||||
{index === 0 && (
|
{index === 0 && (
|
||||||
<span className="badge badge-soft-info mr-1">Fee Payer</span>
|
<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>
|
<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>
|
<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>
|
<span className="badge badge-soft-info mr-1">Program</span>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
@ -290,22 +295,50 @@ function InstructionsSection({ signature }: Props) {
|
|||||||
if (!status || !status.info || !details || !details.transaction) return null;
|
if (!status || !status.info || !details || !details.transaction) return null;
|
||||||
|
|
||||||
const { transaction } = details.transaction;
|
const { transaction } = details.transaction;
|
||||||
if (transaction.instructions.length === 0) {
|
if (transaction.message.instructions.length === 0) {
|
||||||
return <ErrorCard retry={refreshDetails} text="No instructions found" />;
|
return <ErrorCard retry={refreshDetails} text="No instructions found" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = status.info.result;
|
const result = status.info.result;
|
||||||
const instructionDetails = transaction.instructions.map((ix, index) => {
|
const instructionDetails = transaction.message.instructions.map(
|
||||||
const props = { ix, result, index };
|
(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)) {
|
const props = { ix: next, result, index };
|
||||||
return <SystemDetailsCard key={index} {...props} />;
|
return <UnknownDetailsCard key={index} {...props} />;
|
||||||
} else if (StakeProgram.programId.equals(ix.programId)) {
|
}
|
||||||
return <StakeDetailsCard key={index} {...props} />;
|
|
||||||
} else {
|
const ix = intoTransactionInstruction(transaction, index);
|
||||||
return <UnknownDetailsCard key={index} {...props} />;
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { TransactionInstruction, SignatureResult } from "@solana/web3.js";
|
import {
|
||||||
|
TransactionInstruction,
|
||||||
|
SignatureResult,
|
||||||
|
ParsedInstruction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
import { RawDetails } from "./RawDetails";
|
import { RawDetails } from "./RawDetails";
|
||||||
|
import { RawParsedDetails } from "./RawParsedDetails";
|
||||||
|
|
||||||
type InstructionProps = {
|
type InstructionProps = {
|
||||||
title: string;
|
title: string;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
result: SignatureResult;
|
result: SignatureResult;
|
||||||
index: number;
|
index: number;
|
||||||
ix: TransactionInstruction;
|
ix: TransactionInstruction | ParsedInstruction;
|
||||||
defaultRaw?: boolean;
|
defaultRaw?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -45,7 +50,15 @@ export function InstructionCard({
|
|||||||
<div className="table-responsive mb-0">
|
<div className="table-responsive mb-0">
|
||||||
<table className="table table-sm table-nowrap card-table">
|
<table className="table table-sm table-nowrap card-table">
|
||||||
<tbody className="list">
|
<tbody className="list">
|
||||||
{showRaw ? <RawDetails ix={ix} /> : children}
|
{showRaw ? (
|
||||||
|
"parsed" in ix ? (
|
||||||
|
<RawParsedDetails ix={ix} />
|
||||||
|
) : (
|
||||||
|
<RawDetails ix={ix} />
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</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 React from "react";
|
||||||
import { TransactionInstruction, SignatureResult } from "@solana/web3.js";
|
import {
|
||||||
|
TransactionInstruction,
|
||||||
|
SignatureResult,
|
||||||
|
ParsedInstruction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
import { InstructionCard } from "./InstructionCard";
|
import { InstructionCard } from "./InstructionCard";
|
||||||
|
|
||||||
export function UnknownDetailsCard({
|
export function UnknownDetailsCard({
|
||||||
@ -7,7 +11,7 @@ export function UnknownDetailsCard({
|
|||||||
index,
|
index,
|
||||||
result,
|
result,
|
||||||
}: {
|
}: {
|
||||||
ix: TransactionInstruction;
|
ix: TransactionInstruction | ParsedInstruction;
|
||||||
index: number;
|
index: number;
|
||||||
result: SignatureResult;
|
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 React from "react";
|
||||||
import { Connection, PublicKey, TokenAccountInfo } from "@solana/web3.js";
|
import { Connection, PublicKey } from "@solana/web3.js";
|
||||||
import { FetchStatus, useAccounts } from "./index";
|
import { FetchStatus, useAccounts } from "./index";
|
||||||
import { useCluster, Cluster } from "../cluster";
|
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 {
|
interface AccountTokens {
|
||||||
status: FetchStatus;
|
status: FetchStatus;
|
||||||
tokens?: TokenAccountInfo[];
|
tokens?: TokenAccountData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Update {
|
interface Update {
|
||||||
pubkey: PublicKey;
|
pubkey: PublicKey;
|
||||||
status: FetchStatus;
|
status: FetchStatus;
|
||||||
tokens?: TokenAccountInfo[];
|
tokens?: TokenAccountData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
type Action = Update | "clear";
|
type Action = Update | "clear";
|
||||||
@ -98,8 +117,18 @@ async function fetchAccountTokens(
|
|||||||
const { value } = await new Connection(
|
const { value } = await new Connection(
|
||||||
url,
|
url,
|
||||||
"recent"
|
"recent"
|
||||||
).getTokenAccountsByOwner(pubkey, { programId: TOKEN_PROGRAM_ID });
|
).getParsedTokenAccountsByOwner(pubkey, { programId: TOKEN_PROGRAM_ID });
|
||||||
tokens = value.map((accountInfo) => accountInfo.account.data);
|
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;
|
status = FetchStatus.Fetched;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
status = FetchStatus.FetchFailed;
|
status = FetchStatus.FetchFailed;
|
||||||
|
@ -1,49 +1,29 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import io from "socket.io-client";
|
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";
|
import { useCluster, Cluster } from "providers/cluster";
|
||||||
|
|
||||||
// TODO: use `partial` when it is fixed
|
const DashboardInfo = pick({
|
||||||
// https://github.com/ianstormtaylor/superstruct/issues/405
|
|
||||||
const DashboardInfo = object({
|
|
||||||
activatedStake: number(),
|
|
||||||
avgBlockTime_1h: number(),
|
avgBlockTime_1h: number(),
|
||||||
avgBlockTime_1min: number(),
|
avgBlockTime_1min: number(),
|
||||||
circulatingSupply: number(),
|
epochInfo: pick({
|
||||||
dailyPriceChange: number(),
|
|
||||||
dailyVolume: number(),
|
|
||||||
delinquentStake: number(),
|
|
||||||
epochInfo: object({
|
|
||||||
absoluteEpochStartSlot: number(),
|
|
||||||
absoluteSlot: number(),
|
absoluteSlot: number(),
|
||||||
blockHeight: number(),
|
blockHeight: number(),
|
||||||
epoch: number(),
|
epoch: number(),
|
||||||
slotIndex: number(),
|
slotIndex: number(),
|
||||||
slotsInEpoch: number(),
|
slotsInEpoch: number(),
|
||||||
}),
|
}),
|
||||||
stakingYield: number(),
|
|
||||||
tokenPrice: number(),
|
|
||||||
totalDelegatedStake: number(),
|
|
||||||
totalSupply: number(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: use `partial` when it is fixed
|
const RootInfo = pick({
|
||||||
// https://github.com/ianstormtaylor/superstruct/issues/405
|
|
||||||
const RootInfo = object({
|
|
||||||
currentLeader: any(),
|
|
||||||
nextLeaders: any(),
|
|
||||||
root: number(),
|
root: number(),
|
||||||
servedSlots: any(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const PERF_UPDATE_SEC = 5;
|
export const PERF_UPDATE_SEC = 5;
|
||||||
|
|
||||||
// TODO: use `partial` when it is fixed
|
const PerformanceInfo = pick({
|
||||||
// https://github.com/ianstormtaylor/superstruct/issues/405
|
|
||||||
const PerformanceInfo = object({
|
|
||||||
avgTPS: number(),
|
avgTPS: number(),
|
||||||
perfHistory: any(),
|
|
||||||
totalTransactionCount: number(),
|
totalTransactionCount: number(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -8,10 +8,11 @@
|
|||||||
import { TransactionStatusInfo } from "./index";
|
import { TransactionStatusInfo } from "./index";
|
||||||
import {
|
import {
|
||||||
Transaction,
|
Transaction,
|
||||||
ConfirmedTransaction,
|
ParsedConfirmedTransaction,
|
||||||
Message,
|
Message,
|
||||||
clusterApiUrl,
|
clusterApiUrl,
|
||||||
} from "@solana/web3.js";
|
} from "@solana/web3.js";
|
||||||
|
import { intoParsedTransaction } from "utils/tx";
|
||||||
|
|
||||||
export const isCached = (url: string, signature: string): boolean => {
|
export const isCached = (url: string, signature: string): boolean => {
|
||||||
return url === clusterApiUrl("mainnet-beta") && signature in CACHED_STATUSES;
|
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: {
|
uQf4pS38FjRF294QFEXizhYkZFjSR9ZSBvvV6MV5b4VpdfRnK3PY9TWZ2qHMQKtte3XwKVLcWqsTF6wL9NEZMty: {
|
||||||
meta: null,
|
meta: null,
|
||||||
slot: 10440804,
|
slot: 10440804,
|
||||||
transaction: Transaction.populate(
|
transaction: intoParsedTransaction(
|
||||||
new Message({
|
Transaction.populate(
|
||||||
accountKeys: [
|
new Message({
|
||||||
"2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S",
|
accountKeys: [
|
||||||
"4C6NCcLPUgGuBBkV2dJW96mrptMUCp3RG1ft9rqwjFi9",
|
"2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S",
|
||||||
"11111111111111111111111111111111",
|
"4C6NCcLPUgGuBBkV2dJW96mrptMUCp3RG1ft9rqwjFi9",
|
||||||
],
|
"11111111111111111111111111111111",
|
||||||
header: {
|
],
|
||||||
numReadonlySignedAccounts: 0,
|
header: {
|
||||||
numReadonlyUnsignedAccounts: 1,
|
numReadonlySignedAccounts: 0,
|
||||||
numRequiredSignatures: 1,
|
numReadonlyUnsignedAccounts: 1,
|
||||||
},
|
numRequiredSignatures: 1,
|
||||||
instructions: [
|
},
|
||||||
{ accounts: [0, 1], data: "3Bxs411UBrj8QXUb", programIdIndex: 2 },
|
instructions: [
|
||||||
],
|
{ accounts: [0, 1], data: "3Bxs411UBrj8QXUb", programIdIndex: 2 },
|
||||||
recentBlockhash: "5Aw8MaMYdYtnfJyyrregWMWGgiMtWZ6GtRzeP6Ufo65Z",
|
],
|
||||||
}),
|
recentBlockhash: "5Aw8MaMYdYtnfJyyrregWMWGgiMtWZ6GtRzeP6Ufo65Z",
|
||||||
[
|
}),
|
||||||
"uQf4pS38FjRF294QFEXizhYkZFjSR9ZSBvvV6MV5b4VpdfRnK3PY9TWZ2qHMQKtte3XwKVLcWqsTF6wL9NEZMty",
|
[
|
||||||
]
|
"uQf4pS38FjRF294QFEXizhYkZFjSR9ZSBvvV6MV5b4VpdfRnK3PY9TWZ2qHMQKtte3XwKVLcWqsTF6wL9NEZMty",
|
||||||
|
]
|
||||||
|
)
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
DYrfStEEzbV5sftX8LgUa54Nwnc5m5E1731cqBtiiC66TeXgKpfqZEQTuFY3vhHZ2K1BsaFM3X9FqisR28EtZr8: {
|
DYrfStEEzbV5sftX8LgUa54Nwnc5m5E1731cqBtiiC66TeXgKpfqZEQTuFY3vhHZ2K1BsaFM3X9FqisR28EtZr8: {
|
||||||
meta: null,
|
meta: null,
|
||||||
slot: 10451288,
|
slot: 10451288,
|
||||||
transaction: Transaction.populate(
|
transaction: intoParsedTransaction(
|
||||||
new Message({
|
Transaction.populate(
|
||||||
accountKeys: [
|
new Message({
|
||||||
"2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S",
|
accountKeys: [
|
||||||
"4C6NCcLPUgGuBBkV2dJW96mrptMUCp3RG1ft9rqwjFi9",
|
"2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S",
|
||||||
"11111111111111111111111111111111",
|
"4C6NCcLPUgGuBBkV2dJW96mrptMUCp3RG1ft9rqwjFi9",
|
||||||
],
|
"11111111111111111111111111111111",
|
||||||
header: {
|
],
|
||||||
numReadonlySignedAccounts: 0,
|
header: {
|
||||||
numReadonlyUnsignedAccounts: 1,
|
numReadonlySignedAccounts: 0,
|
||||||
numRequiredSignatures: 1,
|
numReadonlyUnsignedAccounts: 1,
|
||||||
},
|
numRequiredSignatures: 1,
|
||||||
instructions: [
|
|
||||||
{
|
|
||||||
accounts: [0, 1],
|
|
||||||
data: "3Bxs3zwYHuDo723R",
|
|
||||||
programIdIndex: 2,
|
|
||||||
},
|
},
|
||||||
],
|
instructions: [
|
||||||
recentBlockhash: "4hXYcBdfcadcjfWV17ZwMa4MXe8kbZHYHwr3GzfyqunL",
|
{
|
||||||
}),
|
accounts: [0, 1],
|
||||||
[
|
data: "3Bxs3zwYHuDo723R",
|
||||||
"DYrfStEEzbV5sftX8LgUa54Nwnc5m5E1731cqBtiiC66TeXgKpfqZEQTuFY3vhHZ2K1BsaFM3X9FqisR28EtZr8",
|
programIdIndex: 2,
|
||||||
]
|
},
|
||||||
|
],
|
||||||
|
recentBlockhash: "4hXYcBdfcadcjfWV17ZwMa4MXe8kbZHYHwr3GzfyqunL",
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
"DYrfStEEzbV5sftX8LgUa54Nwnc5m5E1731cqBtiiC66TeXgKpfqZEQTuFY3vhHZ2K1BsaFM3X9FqisR28EtZr8",
|
||||||
|
]
|
||||||
|
)
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
"3bLx2PLpkxCxJA5P7HVe8asFdSWXVAh1DrxfkqWE9bWvPRxXE2hqwj1vuSC858fUw3XAGQcHbJknhtNdxY2sehab": {
|
"3bLx2PLpkxCxJA5P7HVe8asFdSWXVAh1DrxfkqWE9bWvPRxXE2hqwj1vuSC858fUw3XAGQcHbJknhtNdxY2sehab": {
|
||||||
meta: null,
|
meta: null,
|
||||||
slot: 10516588,
|
slot: 10516588,
|
||||||
transaction: Transaction.populate(
|
transaction: intoParsedTransaction(
|
||||||
new Message({
|
Transaction.populate(
|
||||||
accountKeys: [
|
new Message({
|
||||||
"2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S",
|
accountKeys: [
|
||||||
"4C6NCcLPUgGuBBkV2dJW96mrptMUCp3RG1ft9rqwjFi9",
|
"2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S",
|
||||||
"11111111111111111111111111111111",
|
"4C6NCcLPUgGuBBkV2dJW96mrptMUCp3RG1ft9rqwjFi9",
|
||||||
],
|
"11111111111111111111111111111111",
|
||||||
header: {
|
],
|
||||||
numReadonlySignedAccounts: 0,
|
header: {
|
||||||
numReadonlyUnsignedAccounts: 1,
|
numReadonlySignedAccounts: 0,
|
||||||
numRequiredSignatures: 1,
|
numReadonlyUnsignedAccounts: 1,
|
||||||
},
|
numRequiredSignatures: 1,
|
||||||
instructions: [
|
|
||||||
{
|
|
||||||
accounts: [0, 1],
|
|
||||||
data: "3Bxs3zwYHuDo723R",
|
|
||||||
programIdIndex: 2,
|
|
||||||
},
|
},
|
||||||
],
|
instructions: [
|
||||||
recentBlockhash: "HSzTGt3PJMeQtFr94gEdeZqTRaBxgS8Wf1zq3MDdNT3L",
|
{
|
||||||
}),
|
accounts: [0, 1],
|
||||||
[
|
data: "3Bxs3zwYHuDo723R",
|
||||||
"3bLx2PLpkxCxJA5P7HVe8asFdSWXVAh1DrxfkqWE9bWvPRxXE2hqwj1vuSC858fUw3XAGQcHbJknhtNdxY2sehab",
|
programIdIndex: 2,
|
||||||
]
|
},
|
||||||
|
],
|
||||||
|
recentBlockhash: "HSzTGt3PJMeQtFr94gEdeZqTRaBxgS8Wf1zq3MDdNT3L",
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
"3bLx2PLpkxCxJA5P7HVe8asFdSWXVAh1DrxfkqWE9bWvPRxXE2hqwj1vuSC858fUw3XAGQcHbJknhtNdxY2sehab",
|
||||||
|
]
|
||||||
|
)
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
"3fE8xNgyxbwbvA5MX3wM87ahDDgCVEaaMMSa8UCWWNxojaRYBgrQyiKXLSxcryMWb7sEyVLBWyqUaRWnQCroSqjY": {
|
"3fE8xNgyxbwbvA5MX3wM87ahDDgCVEaaMMSa8UCWWNxojaRYBgrQyiKXLSxcryMWb7sEyVLBWyqUaRWnQCroSqjY": {
|
||||||
meta: null,
|
meta: null,
|
||||||
slot: 10575124,
|
slot: 10575124,
|
||||||
transaction: Transaction.populate(
|
transaction: intoParsedTransaction(
|
||||||
new Message({
|
Transaction.populate(
|
||||||
accountKeys: [
|
new Message({
|
||||||
"2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S",
|
accountKeys: [
|
||||||
"4C6NCcLPUgGuBBkV2dJW96mrptMUCp3RG1ft9rqwjFi9",
|
"2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S",
|
||||||
"11111111111111111111111111111111",
|
"4C6NCcLPUgGuBBkV2dJW96mrptMUCp3RG1ft9rqwjFi9",
|
||||||
],
|
"11111111111111111111111111111111",
|
||||||
header: {
|
],
|
||||||
numReadonlySignedAccounts: 0,
|
header: {
|
||||||
numReadonlyUnsignedAccounts: 1,
|
numReadonlySignedAccounts: 0,
|
||||||
numRequiredSignatures: 1,
|
numReadonlyUnsignedAccounts: 1,
|
||||||
},
|
numRequiredSignatures: 1,
|
||||||
instructions: [
|
|
||||||
{
|
|
||||||
accounts: [0, 1],
|
|
||||||
data: "3Bxs3zuKU6mRKSqD",
|
|
||||||
programIdIndex: 2,
|
|
||||||
},
|
},
|
||||||
],
|
instructions: [
|
||||||
recentBlockhash: "6f6TBMhUoypfR5HHnEqC6VoooKxEcNad5W3Sf63j9MSD",
|
{
|
||||||
}),
|
accounts: [0, 1],
|
||||||
[
|
data: "3Bxs3zuKU6mRKSqD",
|
||||||
"3fE8xNgyxbwbvA5MX3wM87ahDDgCVEaaMMSa8UCWWNxojaRYBgrQyiKXLSxcryMWb7sEyVLBWyqUaRWnQCroSqjY",
|
programIdIndex: 2,
|
||||||
]
|
},
|
||||||
|
],
|
||||||
|
recentBlockhash: "6f6TBMhUoypfR5HHnEqC6VoooKxEcNad5W3Sf63j9MSD",
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
"3fE8xNgyxbwbvA5MX3wM87ahDDgCVEaaMMSa8UCWWNxojaRYBgrQyiKXLSxcryMWb7sEyVLBWyqUaRWnQCroSqjY",
|
||||||
|
]
|
||||||
|
)
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
"5PWymGjKV7T1oqeqGn139EHFyjNM2dnNhHCUcfD2bmdj8cfF95HpY1uJ84W89c4sJQnmyZxXcYrcjumx2jHUvxZQ": {
|
"5PWymGjKV7T1oqeqGn139EHFyjNM2dnNhHCUcfD2bmdj8cfF95HpY1uJ84W89c4sJQnmyZxXcYrcjumx2jHUvxZQ": {
|
||||||
meta: null,
|
meta: null,
|
||||||
slot: 12447825,
|
slot: 12447825,
|
||||||
transaction: Transaction.populate(
|
transaction: intoParsedTransaction(
|
||||||
new Message({
|
Transaction.populate(
|
||||||
accountKeys: [
|
new Message({
|
||||||
"HCV5dGFJXRrJ3jhDYA4DCeb9TEDTwGGYXtT3wHksu2Zr",
|
accountKeys: [
|
||||||
"4C6NCcLPUgGuBBkV2dJW96mrptMUCp3RG1ft9rqwjFi9",
|
"HCV5dGFJXRrJ3jhDYA4DCeb9TEDTwGGYXtT3wHksu2Zr",
|
||||||
"11111111111111111111111111111111",
|
"4C6NCcLPUgGuBBkV2dJW96mrptMUCp3RG1ft9rqwjFi9",
|
||||||
],
|
"11111111111111111111111111111111",
|
||||||
header: {
|
],
|
||||||
numReadonlySignedAccounts: 0,
|
header: {
|
||||||
numReadonlyUnsignedAccounts: 1,
|
numReadonlySignedAccounts: 0,
|
||||||
numRequiredSignatures: 1,
|
numReadonlyUnsignedAccounts: 1,
|
||||||
},
|
numRequiredSignatures: 1,
|
||||||
instructions: [
|
|
||||||
{
|
|
||||||
accounts: [0, 1],
|
|
||||||
data: "3Bxs3zrfhSqZJTR1",
|
|
||||||
programIdIndex: 2,
|
|
||||||
},
|
},
|
||||||
],
|
instructions: [
|
||||||
recentBlockhash: "3HJNFraT7XGAqMrQs83EKwDGB6LpHVwUMQKGaYMNY49E",
|
{
|
||||||
}),
|
accounts: [0, 1],
|
||||||
[
|
data: "3Bxs3zrfhSqZJTR1",
|
||||||
"5PWymGjKV7T1oqeqGn139EHFyjNM2dnNhHCUcfD2bmdj8cfF95HpY1uJ84W89c4sJQnmyZxXcYrcjumx2jHUvxZQ",
|
programIdIndex: 2,
|
||||||
]
|
},
|
||||||
|
],
|
||||||
|
recentBlockhash: "3HJNFraT7XGAqMrQs83EKwDGB6LpHVwUMQKGaYMNY49E",
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
"5PWymGjKV7T1oqeqGn139EHFyjNM2dnNhHCUcfD2bmdj8cfF95HpY1uJ84W89c4sJQnmyZxXcYrcjumx2jHUvxZQ",
|
||||||
|
]
|
||||||
|
)
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
"5K4KuqTTRNtzfpxWiwnkePzGfsa3tBEmpMy7vQFR3KWFAZNVY9tvoSaz1Yt5dKxcgsZPio2EsASVDGbQB1HvirGD": {
|
"5K4KuqTTRNtzfpxWiwnkePzGfsa3tBEmpMy7vQFR3KWFAZNVY9tvoSaz1Yt5dKxcgsZPio2EsASVDGbQB1HvirGD": {
|
||||||
meta: null,
|
meta: null,
|
||||||
slot: 12450728,
|
slot: 12450728,
|
||||||
transaction: Transaction.populate(
|
transaction: intoParsedTransaction(
|
||||||
new Message({
|
Transaction.populate(
|
||||||
accountKeys: [
|
new Message({
|
||||||
"6yKHERk8rsbmJxvMpPuwPs1ct3hRiP7xaJF2tvnGU6nK",
|
accountKeys: [
|
||||||
"4C6NCcLPUgGuBBkV2dJW96mrptMUCp3RG1ft9rqwjFi9",
|
"6yKHERk8rsbmJxvMpPuwPs1ct3hRiP7xaJF2tvnGU6nK",
|
||||||
"3o6xgkJ9sTmDeQWyfj3sxwon18fXJB9PV5LDc8sfgR4a",
|
"4C6NCcLPUgGuBBkV2dJW96mrptMUCp3RG1ft9rqwjFi9",
|
||||||
"11111111111111111111111111111111",
|
"3o6xgkJ9sTmDeQWyfj3sxwon18fXJB9PV5LDc8sfgR4a",
|
||||||
],
|
"11111111111111111111111111111111",
|
||||||
header: {
|
],
|
||||||
numReadonlySignedAccounts: 0,
|
header: {
|
||||||
numReadonlyUnsignedAccounts: 1,
|
numReadonlySignedAccounts: 0,
|
||||||
numRequiredSignatures: 2,
|
numReadonlyUnsignedAccounts: 1,
|
||||||
},
|
numRequiredSignatures: 2,
|
||||||
instructions: [
|
|
||||||
{
|
|
||||||
accounts: [1, 2],
|
|
||||||
data: "3Bxs3ztRCp3tH1yZ",
|
|
||||||
programIdIndex: 3,
|
|
||||||
},
|
},
|
||||||
],
|
instructions: [
|
||||||
recentBlockhash: "8eXVUNRxrDgpsEuoTWyLay1LUh2djc3Y8cw2owXRN8cU",
|
{
|
||||||
}),
|
accounts: [1, 2],
|
||||||
[
|
data: "3Bxs3ztRCp3tH1yZ",
|
||||||
"5K4KuqTTRNtzfpxWiwnkePzGfsa3tBEmpMy7vQFR3KWFAZNVY9tvoSaz1Yt5dKxcgsZPio2EsASVDGbQB1HvirGD",
|
programIdIndex: 3,
|
||||||
"37tvpG1eAeEBizJPhJvmpC2BY8npwy6K1wrZdNwdRAfWSbkerY3ZwYAPMHbrzoq7tthvWC2qFU28niqLPxbukeXF",
|
},
|
||||||
]
|
],
|
||||||
|
recentBlockhash: "8eXVUNRxrDgpsEuoTWyLay1LUh2djc3Y8cw2owXRN8cU",
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
"5K4KuqTTRNtzfpxWiwnkePzGfsa3tBEmpMy7vQFR3KWFAZNVY9tvoSaz1Yt5dKxcgsZPio2EsASVDGbQB1HvirGD",
|
||||||
|
"37tvpG1eAeEBizJPhJvmpC2BY8npwy6K1wrZdNwdRAfWSbkerY3ZwYAPMHbrzoq7tthvWC2qFU28niqLPxbukeXF",
|
||||||
|
]
|
||||||
|
)
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
"45pGoC4Rr3fJ1TKrsiRkhHRbdUeX7633XAGVec6XzVdpRbzQgHhe6ZC6Uq164MPWtiqMg7wCkC6Wy3jy2BqsDEKf": {
|
"45pGoC4Rr3fJ1TKrsiRkhHRbdUeX7633XAGVec6XzVdpRbzQgHhe6ZC6Uq164MPWtiqMg7wCkC6Wy3jy2BqsDEKf": {
|
||||||
meta: null,
|
meta: null,
|
||||||
slot: 12972684,
|
slot: 12972684,
|
||||||
transaction: Transaction.populate(
|
transaction: intoParsedTransaction(
|
||||||
new Message({
|
Transaction.populate(
|
||||||
accountKeys: [
|
new Message({
|
||||||
"6yKHERk8rsbmJxvMpPuwPs1ct3hRiP7xaJF2tvnGU6nK",
|
accountKeys: [
|
||||||
"3o6xgkJ9sTmDeQWyfj3sxwon18fXJB9PV5LDc8sfgR4a",
|
"6yKHERk8rsbmJxvMpPuwPs1ct3hRiP7xaJF2tvnGU6nK",
|
||||||
"1nc1nerator11111111111111111111111111111111",
|
"3o6xgkJ9sTmDeQWyfj3sxwon18fXJB9PV5LDc8sfgR4a",
|
||||||
"11111111111111111111111111111111",
|
"1nc1nerator11111111111111111111111111111111",
|
||||||
],
|
"11111111111111111111111111111111",
|
||||||
header: {
|
],
|
||||||
numReadonlySignedAccounts: 0,
|
header: {
|
||||||
numReadonlyUnsignedAccounts: 1,
|
numReadonlySignedAccounts: 0,
|
||||||
numRequiredSignatures: 2,
|
numReadonlyUnsignedAccounts: 1,
|
||||||
},
|
numRequiredSignatures: 2,
|
||||||
instructions: [
|
|
||||||
{
|
|
||||||
accounts: [1, 2],
|
|
||||||
data: "3Bxs4NNAyLXRbuZZ",
|
|
||||||
programIdIndex: 3,
|
|
||||||
},
|
},
|
||||||
],
|
instructions: [
|
||||||
recentBlockhash: "2xnatNUtSbeMRwi3k4vxPwXxeKFQYVuCNRg2rAgydWVP",
|
{
|
||||||
}),
|
accounts: [1, 2],
|
||||||
[
|
data: "3Bxs4NNAyLXRbuZZ",
|
||||||
"45pGoC4Rr3fJ1TKrsiRkhHRbdUeX7633XAGVec6XzVdpRbzQgHhe6ZC6Uq164MPWtiqMg7wCkC6Wy3jy2BqsDEKf",
|
programIdIndex: 3,
|
||||||
"2E7CDMTssxTYkdetCKVWQv9X2KNDPiuZrT2Y7647PhFEXuAWWxmHJb3ryCmP29ocQ1SNc7VyJjjm4X3jE8xWDmGY",
|
},
|
||||||
]
|
],
|
||||||
|
recentBlockhash: "2xnatNUtSbeMRwi3k4vxPwXxeKFQYVuCNRg2rAgydWVP",
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
"45pGoC4Rr3fJ1TKrsiRkhHRbdUeX7633XAGVec6XzVdpRbzQgHhe6ZC6Uq164MPWtiqMg7wCkC6Wy3jy2BqsDEKf",
|
||||||
|
"2E7CDMTssxTYkdetCKVWQv9X2KNDPiuZrT2Y7647PhFEXuAWWxmHJb3ryCmP29ocQ1SNc7VyJjjm4X3jE8xWDmGY",
|
||||||
|
]
|
||||||
|
)
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,7 @@ import React from "react";
|
|||||||
import {
|
import {
|
||||||
Connection,
|
Connection,
|
||||||
TransactionSignature,
|
TransactionSignature,
|
||||||
ConfirmedTransaction,
|
ParsedConfirmedTransaction,
|
||||||
} from "@solana/web3.js";
|
} from "@solana/web3.js";
|
||||||
import { useCluster } from "../cluster";
|
import { useCluster } from "../cluster";
|
||||||
import { useTransactions, FetchStatus } from "./index";
|
import { useTransactions, FetchStatus } from "./index";
|
||||||
@ -10,7 +10,7 @@ import { CACHED_DETAILS, isCached } from "./cached";
|
|||||||
|
|
||||||
export interface Details {
|
export interface Details {
|
||||||
fetchStatus: FetchStatus;
|
fetchStatus: FetchStatus;
|
||||||
transaction: ConfirmedTransaction | null;
|
transaction: ParsedConfirmedTransaction | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
type State = { [signature: string]: Details };
|
type State = { [signature: string]: Details };
|
||||||
@ -25,7 +25,7 @@ interface Update {
|
|||||||
type: ActionType.Update;
|
type: ActionType.Update;
|
||||||
signature: string;
|
signature: string;
|
||||||
fetchStatus: FetchStatus;
|
fetchStatus: FetchStatus;
|
||||||
transaction: ConfirmedTransaction | null;
|
transaction: ParsedConfirmedTransaction | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Add {
|
interface Add {
|
||||||
@ -134,7 +134,7 @@ async function fetchDetails(
|
|||||||
fetchStatus = FetchStatus.Fetched;
|
fetchStatus = FetchStatus.Fetched;
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
transaction = await new Connection(url).getConfirmedTransaction(
|
transaction = await new Connection(url).getParsedConfirmedTransaction(
|
||||||
signature
|
signature
|
||||||
);
|
);
|
||||||
fetchStatus = FetchStatus.Fetched;
|
fetchStatus = FetchStatus.Fetched;
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
// Use this to write your custom SCSS
|
// Use this to write your custom SCSS
|
||||||
//
|
//
|
||||||
|
|
||||||
code {
|
code, pre {
|
||||||
background-color: $black-dark;
|
background-color: $black-dark;
|
||||||
color: $white;
|
color: $white;
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
// Use this to write your custom SCSS
|
// Use this to write your custom SCSS
|
||||||
//
|
//
|
||||||
|
|
||||||
code {
|
code, pre {
|
||||||
padding: 0.33rem;
|
padding: 0.33rem;
|
||||||
border-radius: $border-radius;
|
border-radius: $border-radius;
|
||||||
background-color: $gray-200;
|
background-color: $gray-200;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import bs58 from "bs58";
|
||||||
import {
|
import {
|
||||||
SystemProgram,
|
SystemProgram,
|
||||||
StakeProgram,
|
StakeProgram,
|
||||||
@ -7,6 +8,9 @@ import {
|
|||||||
SYSVAR_RENT_PUBKEY,
|
SYSVAR_RENT_PUBKEY,
|
||||||
SYSVAR_REWARDS_PUBKEY,
|
SYSVAR_REWARDS_PUBKEY,
|
||||||
SYSVAR_STAKE_HISTORY_PUBKEY,
|
SYSVAR_STAKE_HISTORY_PUBKEY,
|
||||||
|
ParsedTransaction,
|
||||||
|
TransactionInstruction,
|
||||||
|
Transaction,
|
||||||
} from "@solana/web3.js";
|
} from "@solana/web3.js";
|
||||||
|
|
||||||
const PROGRAM_IDS = {
|
const PROGRAM_IDS = {
|
||||||
@ -48,3 +52,53 @@ export function displayAddress(address: string): string {
|
|||||||
address
|
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