Explorer: Support displaying and inspecting versioned transactions (#27825)
* Explorer: Bump @solana/web3.js to v1.63.1 * Explorer: Support displaying and inspecting versioned transactions
This commit is contained in:
parent
7810387b00
commit
b93392cfca
|
@ -19,7 +19,7 @@
|
|||
"@sentry/react": "^7.6.0",
|
||||
"@solana/buffer-layout": "^3.0.0",
|
||||
"@solana/spl-token-registry": "^0.2.3736",
|
||||
"@solana/web3.js": "^1.62.0",
|
||||
"@solana/web3.js": "^1.63.1",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.3.0",
|
||||
"@testing-library/user-event": "^14.2.3",
|
||||
|
@ -5152,9 +5152,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@solana/web3.js": {
|
||||
"version": "1.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.62.0.tgz",
|
||||
"integrity": "sha512-rHnqJR5ECooUp8egurP9Qi1SKI1Q3pbF2ZkaHbEmFsSjBsyEe+Qqxa5h+7ueylqApYyk0zawnxz83y4kdrlNIA==",
|
||||
"version": "1.63.1",
|
||||
"resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.63.1.tgz",
|
||||
"integrity": "sha512-wgEdGVK5FTS2zENxbcGSvKpGZ0jDS6BUdGu8Gn6ns0CzgJkK83u4ip3THSnBPEQ5i/jrqukg998BwV1H67+qiQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@noble/ed25519": "^1.7.0",
|
||||
|
@ -31100,9 +31100,9 @@
|
|||
}
|
||||
},
|
||||
"@solana/web3.js": {
|
||||
"version": "1.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.62.0.tgz",
|
||||
"integrity": "sha512-rHnqJR5ECooUp8egurP9Qi1SKI1Q3pbF2ZkaHbEmFsSjBsyEe+Qqxa5h+7ueylqApYyk0zawnxz83y4kdrlNIA==",
|
||||
"version": "1.63.1",
|
||||
"resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.63.1.tgz",
|
||||
"integrity": "sha512-wgEdGVK5FTS2zENxbcGSvKpGZ0jDS6BUdGu8Gn6ns0CzgJkK83u4ip3THSnBPEQ5i/jrqukg998BwV1H67+qiQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@noble/ed25519": "^1.7.0",
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
"@sentry/react": "^7.6.0",
|
||||
"@solana/buffer-layout": "^3.0.0",
|
||||
"@solana/spl-token-registry": "^0.2.3736",
|
||||
"@solana/web3.js": "^1.62.0",
|
||||
"@solana/web3.js": "^1.63.1",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.3.0",
|
||||
"@testing-library/user-event": "^14.2.3",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Message, ParsedMessage } from "@solana/web3.js";
|
||||
import { ParsedMessage, PublicKey, VersionedMessage } from "@solana/web3.js";
|
||||
import { Cluster } from "providers/cluster";
|
||||
import { TableCardBody } from "components/common/TableCardBody";
|
||||
import { InstructionLogs } from "utils/program-logs";
|
||||
|
@ -21,27 +21,24 @@ export function ProgramLogsCardBody({
|
|||
cluster,
|
||||
url,
|
||||
}: {
|
||||
message: Message | ParsedMessage;
|
||||
message: VersionedMessage | ParsedMessage;
|
||||
logs: InstructionLogs[];
|
||||
cluster: Cluster;
|
||||
url: string;
|
||||
}) {
|
||||
let logIndex = 0;
|
||||
let instructionProgramIds: PublicKey[];
|
||||
if ("compiledInstructions" in message) {
|
||||
instructionProgramIds = message.compiledInstructions.map((ix) => {
|
||||
return message.staticAccountKeys[ix.programIdIndex];
|
||||
});
|
||||
} else {
|
||||
instructionProgramIds = message.instructions.map((ix) => ix.programId);
|
||||
}
|
||||
|
||||
return (
|
||||
<TableCardBody>
|
||||
{message.instructions.map((ix, index) => {
|
||||
let programId;
|
||||
if ("programIdIndex" in ix) {
|
||||
const programAccount = message.accountKeys[ix.programIdIndex];
|
||||
if ("pubkey" in programAccount) {
|
||||
programId = programAccount.pubkey;
|
||||
} else {
|
||||
programId = programAccount;
|
||||
}
|
||||
} else {
|
||||
programId = ix.programId;
|
||||
}
|
||||
|
||||
{instructionProgramIds.map((programId, index) => {
|
||||
const programAddress = programId.toBase58();
|
||||
let programLogs: InstructionLogs | undefined = logs[logIndex];
|
||||
if (programLogs?.invokedProgram === programAddress) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react";
|
||||
import { BlockResponse, PublicKey } from "@solana/web3.js";
|
||||
import { PublicKey, VersionedBlockResponse } from "@solana/web3.js";
|
||||
import { Address } from "components/common/Address";
|
||||
import { Link } from "react-router-dom";
|
||||
import { clusterPath } from "utils/url";
|
||||
|
@ -15,7 +15,7 @@ export function BlockAccountsCard({
|
|||
block,
|
||||
blockSlot,
|
||||
}: {
|
||||
block: BlockResponse;
|
||||
block: VersionedBlockResponse;
|
||||
blockSlot: number;
|
||||
}) {
|
||||
const [numDisplayed, setNumDisplayed] = React.useState(10);
|
||||
|
@ -26,9 +26,12 @@ export function BlockAccountsCard({
|
|||
block.transactions.forEach((tx) => {
|
||||
const message = tx.transaction.message;
|
||||
const txSet = new Map<string, boolean>();
|
||||
message.instructions.forEach((ix) => {
|
||||
ix.accounts.forEach((index) => {
|
||||
const address = message.accountKeys[index].toBase58();
|
||||
const accountKeys = message.getAccountKeys({
|
||||
accountKeysFromLookups: tx.meta?.loadedAddresses,
|
||||
});
|
||||
message.compiledInstructions.forEach((ix) => {
|
||||
ix.accountKeyIndexes.forEach((index) => {
|
||||
const address = accountKeys.get(index)!.toBase58();
|
||||
txSet.set(address, message.isAccountWritable(index));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,11 +2,11 @@ import React from "react";
|
|||
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||
import { Location } from "history";
|
||||
import {
|
||||
BlockResponse,
|
||||
ConfirmedTransactionMeta,
|
||||
TransactionSignature,
|
||||
PublicKey,
|
||||
VOTE_PROGRAM_ID,
|
||||
VersionedBlockResponse,
|
||||
} from "@solana/web3.js";
|
||||
import { ErrorCard } from "components/common/ErrorCard";
|
||||
import { Signature } from "components/common/Signature";
|
||||
|
@ -51,7 +51,7 @@ type TransactionWithInvocations = {
|
|||
logTruncated: boolean;
|
||||
};
|
||||
|
||||
export function BlockHistoryCard({ block }: { block: BlockResponse }) {
|
||||
export function BlockHistoryCard({ block }: { block: VersionedBlockResponse }) {
|
||||
const [numDisplayed, setNumDisplayed] = React.useState(PAGE_SIZE);
|
||||
const [showDropdown, setDropdown] = React.useState(false);
|
||||
const query = useQuery();
|
||||
|
@ -72,7 +72,7 @@ export function BlockHistoryCard({ block }: { block: BlockResponse }) {
|
|||
signature = tx.transaction.signatures[0];
|
||||
}
|
||||
|
||||
let programIndexes = tx.transaction.message.instructions
|
||||
let programIndexes = tx.transaction.message.compiledInstructions
|
||||
.map((ix) => ix.programIdIndex)
|
||||
.concat(
|
||||
tx.meta?.innerInstructions?.flatMap((ix) => {
|
||||
|
@ -87,8 +87,11 @@ export function BlockHistoryCard({ block }: { block: BlockResponse }) {
|
|||
});
|
||||
|
||||
const invocations = new Map<string, number>();
|
||||
const accountKeys = tx.transaction.message.getAccountKeys({
|
||||
accountKeysFromLookups: tx.meta?.loadedAddresses,
|
||||
});
|
||||
for (const [i, count] of indexMap.entries()) {
|
||||
const programId = tx.transaction.message.accountKeys[i].toBase58();
|
||||
const programId = accountKeys.get(i)!.toBase58();
|
||||
invocations.set(programId, count);
|
||||
const programTransactionCount = invokedPrograms.get(programId) || 0;
|
||||
invokedPrograms.set(programId, programTransactionCount + 1);
|
||||
|
@ -143,8 +146,15 @@ export function BlockHistoryCard({ block }: { block: BlockResponse }) {
|
|||
if (accountFilter === null) {
|
||||
return true;
|
||||
}
|
||||
const tx = block.transactions[index].transaction;
|
||||
return tx.message.accountKeys.find((key) => key.equals(accountFilter));
|
||||
|
||||
const tx = block.transactions[index];
|
||||
const accountKeys = tx.transaction.message.getAccountKeys({
|
||||
accountKeysFromLookups: tx.meta?.loadedAddresses,
|
||||
});
|
||||
return accountKeys
|
||||
.keySegments()
|
||||
.flat()
|
||||
.find((key) => key.equals(accountFilter));
|
||||
});
|
||||
|
||||
const showComputeUnits = filteredTxs.every(
|
||||
|
|
|
@ -7,7 +7,7 @@ import { Slot } from "components/common/Slot";
|
|||
import { ClusterStatus, useCluster } from "providers/cluster";
|
||||
import { BlockHistoryCard } from "./BlockHistoryCard";
|
||||
import { BlockRewardsCard } from "./BlockRewardsCard";
|
||||
import { BlockResponse } from "@solana/web3.js";
|
||||
import { VersionedBlockResponse } from "@solana/web3.js";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { clusterPath } from "utils/url";
|
||||
import { BlockProgramsCard } from "./BlockProgramsCard";
|
||||
|
@ -211,7 +211,7 @@ function MoreSection({
|
|||
tab,
|
||||
}: {
|
||||
slot: number;
|
||||
block: BlockResponse;
|
||||
block: VersionedBlockResponse;
|
||||
tab?: string;
|
||||
}) {
|
||||
return (
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import React from "react";
|
||||
import { BlockResponse, PublicKey } from "@solana/web3.js";
|
||||
import { PublicKey, VersionedBlockResponse } from "@solana/web3.js";
|
||||
import { Address } from "components/common/Address";
|
||||
import { TableCardBody } from "components/common/TableCardBody";
|
||||
|
||||
export function BlockProgramsCard({ block }: { block: BlockResponse }) {
|
||||
export function BlockProgramsCard({
|
||||
block,
|
||||
}: {
|
||||
block: VersionedBlockResponse;
|
||||
}) {
|
||||
const totalTransactions = block.transactions.length;
|
||||
const txSuccesses = new Map<string, number>();
|
||||
const txFrequency = new Map<string, number>();
|
||||
|
@ -12,18 +16,23 @@ export function BlockProgramsCard({ block }: { block: BlockResponse }) {
|
|||
let totalInstructions = 0;
|
||||
block.transactions.forEach((tx) => {
|
||||
const message = tx.transaction.message;
|
||||
totalInstructions += message.instructions.length;
|
||||
totalInstructions += message.compiledInstructions.length;
|
||||
const programUsed = new Set<string>();
|
||||
const accountKeys = tx.transaction.message.getAccountKeys({
|
||||
accountKeysFromLookups: tx.meta?.loadedAddresses,
|
||||
});
|
||||
const trackProgram = (index: number) => {
|
||||
if (index >= message.accountKeys.length) return;
|
||||
const programId = message.accountKeys[index];
|
||||
if (index >= accountKeys.length) return;
|
||||
const programId = accountKeys.get(index)!;
|
||||
const programAddress = programId.toBase58();
|
||||
programUsed.add(programAddress);
|
||||
const frequency = ixFrequency.get(programAddress);
|
||||
ixFrequency.set(programAddress, frequency ? frequency + 1 : 1);
|
||||
};
|
||||
|
||||
message.instructions.forEach((ix) => trackProgram(ix.programIdIndex));
|
||||
message.compiledInstructions.forEach((ix) =>
|
||||
trackProgram(ix.programIdIndex)
|
||||
);
|
||||
tx.meta?.innerInstructions?.forEach((inner) => {
|
||||
totalInstructions += inner.instructions.length;
|
||||
inner.instructions.forEach((innerIx) =>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import React from "react";
|
||||
import { SolBalance } from "utils";
|
||||
import { BlockResponse, PublicKey } from "@solana/web3.js";
|
||||
import { PublicKey, VersionedBlockResponse } from "@solana/web3.js";
|
||||
import { Address } from "components/common/Address";
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
export function BlockRewardsCard({ block }: { block: BlockResponse }) {
|
||||
export function BlockRewardsCard({ block }: { block: VersionedBlockResponse }) {
|
||||
const [rewardsDisplayed, setRewardsDisplayed] = React.useState(PAGE_SIZE);
|
||||
|
||||
if (!block.rewards || block.rewards.length < 1) {
|
||||
|
|
|
@ -197,6 +197,7 @@ function StatusCard({
|
|||
const fee = transactionWithMeta?.meta?.fee;
|
||||
const transaction = transactionWithMeta?.transaction;
|
||||
const blockhash = transaction?.message.recentBlockhash;
|
||||
const version = transactionWithMeta?.version;
|
||||
const isNonce = (() => {
|
||||
if (!transaction || transaction.message.instructions.length < 1) {
|
||||
return false;
|
||||
|
@ -330,6 +331,13 @@ function StatusCard({
|
|||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
||||
{version !== undefined && (
|
||||
<tr>
|
||||
<td>Transaction Version</td>
|
||||
<td className="text-lg-end text-uppercase">{version}</td>
|
||||
</tr>
|
||||
)}
|
||||
</TableCardBody>
|
||||
</div>
|
||||
);
|
||||
|
@ -414,14 +422,19 @@ function AccountsCard({ signature }: SignatureProps) {
|
|||
{index === 0 && (
|
||||
<span className="badge bg-info-soft me-1">Fee Payer</span>
|
||||
)}
|
||||
{account.writable && (
|
||||
<span className="badge bg-info-soft me-1">Writable</span>
|
||||
)}
|
||||
{account.signer && (
|
||||
<span className="badge bg-info-soft me-1">Signer</span>
|
||||
)}
|
||||
{account.writable && (
|
||||
<span className="badge bg-danger-soft me-1">Writable</span>
|
||||
)}
|
||||
{message.instructions.find((ix) => ix.programId.equals(pubkey)) && (
|
||||
<span className="badge bg-info-soft me-1">Program</span>
|
||||
<span className="badge bg-warning-soft me-1">Program</span>
|
||||
)}
|
||||
{account.source === "lookupTable" && (
|
||||
<span className="badge bg-gray-soft me-1">
|
||||
Address Table Lookup
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import React from "react";
|
||||
import { Message, PublicKey } from "@solana/web3.js";
|
||||
import { PublicKey, VersionedMessage } from "@solana/web3.js";
|
||||
import { TableCardBody } from "components/common/TableCardBody";
|
||||
import { AddressWithContext } from "./AddressWithContext";
|
||||
import {
|
||||
AddressFromLookupTableWithContext,
|
||||
AddressWithContext,
|
||||
} from "./AddressWithContext";
|
||||
import { ErrorCard } from "components/common/ErrorCard";
|
||||
|
||||
export function AccountsCard({ message }: { message: Message }) {
|
||||
export function AccountsCard({ message }: { message: VersionedMessage }) {
|
||||
const [expanded, setExpanded] = React.useState(true);
|
||||
|
||||
const { validMessage, error } = React.useMemo(() => {
|
||||
|
@ -16,9 +19,11 @@ export function AccountsCard({ message }: { message: Message }) {
|
|||
|
||||
if (numReadonlySignedAccounts >= numRequiredSignatures) {
|
||||
return { validMessage: undefined, error: "Invalid header" };
|
||||
} else if (numReadonlyUnsignedAccounts >= message.accountKeys.length) {
|
||||
} else if (
|
||||
numReadonlyUnsignedAccounts >= message.staticAccountKeys.length
|
||||
) {
|
||||
return { validMessage: undefined, error: "Invalid header" };
|
||||
} else if (message.accountKeys.length === 0) {
|
||||
} else if (message.staticAccountKeys.length === 0) {
|
||||
return { validMessage: undefined, error: "Message has no accounts" };
|
||||
}
|
||||
|
||||
|
@ -28,39 +33,86 @@ export function AccountsCard({ message }: { message: Message }) {
|
|||
};
|
||||
}, [message]);
|
||||
|
||||
const accountRows = React.useMemo(() => {
|
||||
const { accountRows, numAccounts } = React.useMemo(() => {
|
||||
const message = validMessage;
|
||||
if (!message) return;
|
||||
return message.accountKeys.map((publicKey, accountIndex) => {
|
||||
const {
|
||||
numRequiredSignatures,
|
||||
numReadonlySignedAccounts,
|
||||
numReadonlyUnsignedAccounts,
|
||||
} = message.header;
|
||||
if (!message) return { accountRows: undefined, numAccounts: 0 };
|
||||
const staticAccountRows = message.staticAccountKeys.map(
|
||||
(publicKey, accountIndex) => {
|
||||
const {
|
||||
numRequiredSignatures,
|
||||
numReadonlySignedAccounts,
|
||||
numReadonlyUnsignedAccounts,
|
||||
} = message.header;
|
||||
|
||||
let readOnly = false;
|
||||
let signer = false;
|
||||
if (accountIndex < numRequiredSignatures) {
|
||||
signer = true;
|
||||
if (accountIndex >= numRequiredSignatures - numReadonlySignedAccounts) {
|
||||
let readOnly = false;
|
||||
let signer = false;
|
||||
if (accountIndex < numRequiredSignatures) {
|
||||
signer = true;
|
||||
if (
|
||||
accountIndex >=
|
||||
numRequiredSignatures - numReadonlySignedAccounts
|
||||
) {
|
||||
readOnly = true;
|
||||
}
|
||||
} else if (
|
||||
accountIndex >=
|
||||
message.staticAccountKeys.length - numReadonlyUnsignedAccounts
|
||||
) {
|
||||
readOnly = true;
|
||||
}
|
||||
} else if (
|
||||
accountIndex >=
|
||||
message.accountKeys.length - numReadonlyUnsignedAccounts
|
||||
) {
|
||||
readOnly = true;
|
||||
|
||||
const props = {
|
||||
accountIndex,
|
||||
publicKey,
|
||||
signer,
|
||||
readOnly,
|
||||
};
|
||||
|
||||
return <AccountRow key={accountIndex} {...props} />;
|
||||
}
|
||||
);
|
||||
|
||||
const props = {
|
||||
accountIndex,
|
||||
publicKey,
|
||||
signer,
|
||||
readOnly,
|
||||
};
|
||||
let accountIndex = message.staticAccountKeys.length;
|
||||
const writableLookupTableRows = message.addressTableLookups.flatMap(
|
||||
(lookup) => {
|
||||
return lookup.writableIndexes.map((lookupTableIndex) => {
|
||||
const props = {
|
||||
accountIndex,
|
||||
lookupTableKey: lookup.accountKey,
|
||||
lookupTableIndex,
|
||||
readOnly: false,
|
||||
};
|
||||
|
||||
return <AccountRow key={accountIndex} {...props} />;
|
||||
});
|
||||
accountIndex += 1;
|
||||
return <AccountFromLookupTableRow key={accountIndex} {...props} />;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const readonlyLookupTableRows = message.addressTableLookups.flatMap(
|
||||
(lookup) => {
|
||||
return lookup.readonlyIndexes.map((lookupTableIndex) => {
|
||||
const props = {
|
||||
accountIndex,
|
||||
lookupTableKey: lookup.accountKey,
|
||||
lookupTableIndex,
|
||||
readOnly: true,
|
||||
};
|
||||
|
||||
accountIndex += 1;
|
||||
return <AccountFromLookupTableRow key={accountIndex} {...props} />;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
accountRows: [
|
||||
...staticAccountRows,
|
||||
...writableLookupTableRows,
|
||||
...readonlyLookupTableRows,
|
||||
],
|
||||
numAccounts: accountIndex,
|
||||
};
|
||||
}, [validMessage]);
|
||||
|
||||
if (error) {
|
||||
|
@ -70,9 +122,7 @@ export function AccountsCard({ message }: { message: Message }) {
|
|||
return (
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<h3 className="card-header-title">
|
||||
{`Account List (${message.accountKeys.length})`}
|
||||
</h3>
|
||||
<h3 className="card-header-title">{`Account List (${numAccounts})`}</h3>
|
||||
<button
|
||||
className={`btn btn-sm d-flex ${
|
||||
expanded ? "btn-black active" : "btn-white"
|
||||
|
@ -87,6 +137,40 @@ export function AccountsCard({ message }: { message: Message }) {
|
|||
);
|
||||
}
|
||||
|
||||
function AccountFromLookupTableRow({
|
||||
accountIndex,
|
||||
lookupTableKey,
|
||||
lookupTableIndex,
|
||||
readOnly,
|
||||
}: {
|
||||
accountIndex: number;
|
||||
lookupTableKey: PublicKey;
|
||||
lookupTableIndex: number;
|
||||
readOnly: boolean;
|
||||
}) {
|
||||
return (
|
||||
<tr>
|
||||
<td>
|
||||
<div className="d-flex align-items-start flex-column">
|
||||
Account #{accountIndex + 1}
|
||||
<span className="mt-1">
|
||||
{!readOnly && (
|
||||
<span className="badge bg-danger-soft me-1">Writable</span>
|
||||
)}
|
||||
<span className="badge bg-gray-soft">Address Table Lookup</span>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="text-lg-end">
|
||||
<AddressFromLookupTableWithContext
|
||||
lookupTableKey={lookupTableKey}
|
||||
lookupTableIndex={lookupTableIndex}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
function AccountRow({
|
||||
accountIndex,
|
||||
publicKey,
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
import React from "react";
|
||||
import { PublicKey, VersionedMessage } from "@solana/web3.js";
|
||||
import { Address } from "components/common/Address";
|
||||
import { useAddressLookupTable, useFetchAccountInfo } from "providers/accounts";
|
||||
|
||||
export function AddressTableLookupsCard({
|
||||
message,
|
||||
}: {
|
||||
message: VersionedMessage;
|
||||
}) {
|
||||
const [expanded, setExpanded] = React.useState(true);
|
||||
|
||||
const lookupRows = React.useMemo(() => {
|
||||
let key = 0;
|
||||
return message.addressTableLookups.flatMap((lookup) => {
|
||||
const indexes = [
|
||||
...lookup.writableIndexes.map((index) => ({ index, readOnly: false })),
|
||||
...lookup.readonlyIndexes.map((index) => ({ index, readOnly: true })),
|
||||
];
|
||||
|
||||
indexes.sort((a, b) => (a.index < b.index ? -1 : 1));
|
||||
|
||||
return indexes.map(({ index, readOnly }) => {
|
||||
const props = {
|
||||
lookupTableKey: lookup.accountKey,
|
||||
lookupTableIndex: index,
|
||||
readOnly,
|
||||
};
|
||||
return <LookupRow key={key++} {...props} />;
|
||||
});
|
||||
});
|
||||
}, [message]);
|
||||
|
||||
if (message.version === "legacy") return null;
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<h3 className="card-header-title">Address Table Lookup(s)</h3>
|
||||
<button
|
||||
className={`btn btn-sm d-flex ${
|
||||
expanded ? "btn-black active" : "btn-white"
|
||||
}`}
|
||||
onClick={() => setExpanded((e) => !e)}
|
||||
>
|
||||
{expanded ? "Collapse" : "Expand"}
|
||||
</button>
|
||||
</div>
|
||||
{expanded && (
|
||||
<div className="table-responsive mb-0">
|
||||
<table className="table table-sm table-nowrap card-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-muted">Address Lookup Table Address</th>
|
||||
<th className="text-muted">Table Index</th>
|
||||
<th className="text-muted">Resolved Address</th>
|
||||
<th className="text-muted">Details</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{lookupRows.length > 0 ? (
|
||||
<tbody className="list">{lookupRows}</tbody>
|
||||
) : (
|
||||
<div className="card-footer">
|
||||
<div className="text-muted text-center">No entries found</div>
|
||||
</div>
|
||||
)}
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LookupRow({
|
||||
lookupTableKey,
|
||||
lookupTableIndex,
|
||||
readOnly,
|
||||
}: {
|
||||
lookupTableKey: PublicKey;
|
||||
lookupTableIndex: number;
|
||||
readOnly: boolean;
|
||||
}) {
|
||||
const lookupTable = useAddressLookupTable(lookupTableKey.toBase58());
|
||||
const fetchAccountInfo = useFetchAccountInfo();
|
||||
React.useEffect(() => {
|
||||
if (!lookupTable) fetchAccountInfo(lookupTableKey);
|
||||
}, [lookupTableKey, lookupTable, fetchAccountInfo]);
|
||||
|
||||
let resolvedKeyComponent;
|
||||
if (!lookupTable) {
|
||||
resolvedKeyComponent = (
|
||||
<span className="text-muted">
|
||||
<span className="spinner-grow spinner-grow-sm me-2"></span>
|
||||
Loading
|
||||
</span>
|
||||
);
|
||||
} else if (typeof lookupTable === "string") {
|
||||
resolvedKeyComponent = (
|
||||
<span className="text-muted">Invalid Lookup Table</span>
|
||||
);
|
||||
} else if (lookupTableIndex < lookupTable.state.addresses.length) {
|
||||
const resolvedKey = lookupTable.state.addresses[lookupTableIndex];
|
||||
resolvedKeyComponent = <Address pubkey={resolvedKey} link />;
|
||||
} else {
|
||||
resolvedKeyComponent = (
|
||||
<span className="text-muted">Invalid Lookup Table Index</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td className="text-lg-end">
|
||||
<Address pubkey={lookupTableKey} link />
|
||||
</td>
|
||||
<td className="text-lg-end">{lookupTableIndex}</td>
|
||||
<td className="text-lg-end">{resolvedKeyComponent}</td>
|
||||
<td>
|
||||
{!readOnly && <span className="badge bg-info-soft me-1">Writable</span>}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
|
@ -4,6 +4,7 @@ import { Address } from "components/common/Address";
|
|||
import {
|
||||
Account,
|
||||
useAccountInfo,
|
||||
useAddressLookupTable,
|
||||
useFetchAccountInfo,
|
||||
} from "providers/accounts";
|
||||
import { ClusterStatus, useCluster } from "providers/cluster";
|
||||
|
@ -36,6 +37,43 @@ export const programValidator = (account: Account): string | undefined => {
|
|||
return;
|
||||
};
|
||||
|
||||
export function AddressFromLookupTableWithContext({
|
||||
lookupTableKey,
|
||||
lookupTableIndex,
|
||||
}: {
|
||||
lookupTableKey: PublicKey;
|
||||
lookupTableIndex: number;
|
||||
}) {
|
||||
const lookupTable = useAddressLookupTable(lookupTableKey.toBase58());
|
||||
const fetchAccountInfo = useFetchAccountInfo();
|
||||
React.useEffect(() => {
|
||||
if (!lookupTable) fetchAccountInfo(lookupTableKey);
|
||||
}, [lookupTableKey, lookupTable, fetchAccountInfo]);
|
||||
|
||||
let pubkey;
|
||||
if (!lookupTable) {
|
||||
return (
|
||||
<span className="text-muted">
|
||||
<span className="spinner-grow spinner-grow-sm me-2"></span>
|
||||
Loading
|
||||
</span>
|
||||
);
|
||||
} else if (typeof lookupTable === "string") {
|
||||
return <div>Invalid Lookup Table</div>;
|
||||
} else if (lookupTableIndex < lookupTable.state.addresses.length) {
|
||||
pubkey = lookupTable.state.addresses[lookupTableIndex];
|
||||
} else {
|
||||
return <div>Invalid Lookup Table Index</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="d-flex align-items-end flex-column">
|
||||
<Address pubkey={pubkey} link />
|
||||
<AccountInfo pubkey={pubkey} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function AddressWithContext({
|
||||
pubkey,
|
||||
validator,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react";
|
||||
import { Message, PACKET_DATA_SIZE } from "@solana/web3.js";
|
||||
import { PACKET_DATA_SIZE, VersionedMessage } from "@solana/web3.js";
|
||||
|
||||
import { TableCardBody } from "components/common/TableCardBody";
|
||||
import { SolBalance } from "utils";
|
||||
|
@ -14,6 +14,7 @@ import { LoadingCard } from "components/common/LoadingCard";
|
|||
import { ErrorCard } from "components/common/ErrorCard";
|
||||
import { TransactionSignatures } from "./SignaturesCard";
|
||||
import { AccountsCard } from "./AccountsCard";
|
||||
import { AddressTableLookupsCard } from "./AddressTableLookupsCard";
|
||||
import {
|
||||
AddressWithContext,
|
||||
createFeePayerValidator,
|
||||
|
@ -25,7 +26,7 @@ import base58 from "bs58";
|
|||
|
||||
export type TransactionData = {
|
||||
rawMessage: Uint8Array;
|
||||
message: Message;
|
||||
message: VersionedMessage;
|
||||
signatures?: (string | null)[];
|
||||
};
|
||||
|
||||
|
@ -117,7 +118,7 @@ function decodeUrlParams(
|
|||
throw new Error("message buffer is too short");
|
||||
}
|
||||
|
||||
const message = Message.from(buffer);
|
||||
const message = VersionedMessage.deserialize(buffer);
|
||||
const data = {
|
||||
message,
|
||||
rawMessage: buffer,
|
||||
|
@ -280,6 +281,7 @@ function LoadedView({
|
|||
/>
|
||||
)}
|
||||
<AccountsCard message={message} />
|
||||
<AddressTableLookupsCard message={message} />
|
||||
<InstructionsSection message={message} />
|
||||
</>
|
||||
);
|
||||
|
@ -294,7 +296,7 @@ function OverviewCard({
|
|||
raw,
|
||||
onClear,
|
||||
}: {
|
||||
message: Message;
|
||||
message: VersionedMessage;
|
||||
raw: Uint8Array;
|
||||
onClear: () => void;
|
||||
}) {
|
||||
|
@ -354,11 +356,11 @@ function OverviewCard({
|
|||
</div>
|
||||
</td>
|
||||
<td className="text-end">
|
||||
{message.accountKeys.length === 0 ? (
|
||||
{message.staticAccountKeys.length === 0 ? (
|
||||
"No Fee Payer"
|
||||
) : (
|
||||
<AddressWithContext
|
||||
pubkey={message.accountKeys[0]}
|
||||
pubkey={message.staticAccountKeys[0]}
|
||||
validator={feePayerValidator}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -1,18 +1,25 @@
|
|||
import React from "react";
|
||||
import bs58 from "bs58";
|
||||
import { CompiledInstruction, Message } from "@solana/web3.js";
|
||||
import { MessageCompiledInstruction, VersionedMessage } from "@solana/web3.js";
|
||||
import { TableCardBody } from "components/common/TableCardBody";
|
||||
import { AddressWithContext, programValidator } from "./AddressWithContext";
|
||||
import {
|
||||
AddressFromLookupTableWithContext,
|
||||
AddressWithContext,
|
||||
programValidator,
|
||||
} from "./AddressWithContext";
|
||||
import { useCluster } from "providers/cluster";
|
||||
import { getProgramName } from "utils/tx";
|
||||
import { HexData } from "components/common/HexData";
|
||||
import getInstructionCardScrollAnchorId from "utils/get-instruction-card-scroll-anchor-id";
|
||||
import { useScrollAnchor } from "providers/scroll-anchor";
|
||||
|
||||
export function InstructionsSection({ message }: { message: Message }) {
|
||||
export function InstructionsSection({
|
||||
message,
|
||||
}: {
|
||||
message: VersionedMessage;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
{message.instructions.map((ix, index) => {
|
||||
{message.compiledInstructions.map((ix, index) => {
|
||||
return <InstructionCard key={index} {...{ message, ix, index }} />;
|
||||
})}
|
||||
</>
|
||||
|
@ -24,17 +31,31 @@ function InstructionCard({
|
|||
ix,
|
||||
index,
|
||||
}: {
|
||||
message: Message;
|
||||
ix: CompiledInstruction;
|
||||
message: VersionedMessage;
|
||||
ix: MessageCompiledInstruction;
|
||||
index: number;
|
||||
}) {
|
||||
const [expanded, setExpanded] = React.useState(false);
|
||||
const { cluster } = useCluster();
|
||||
const programId = message.accountKeys[ix.programIdIndex];
|
||||
const programId = message.staticAccountKeys[ix.programIdIndex];
|
||||
const programName = getProgramName(programId.toBase58(), cluster);
|
||||
const scrollAnchorRef = useScrollAnchor(
|
||||
getInstructionCardScrollAnchorId([index + 1])
|
||||
);
|
||||
const lookupsForAccountKeyIndex = [
|
||||
...message.addressTableLookups.flatMap((lookup) =>
|
||||
lookup.writableIndexes.map((index) => ({
|
||||
lookupTableKey: lookup.accountKey,
|
||||
lookupTableIndex: index,
|
||||
}))
|
||||
),
|
||||
...message.addressTableLookups.flatMap((lookup) =>
|
||||
lookup.readonlyIndexes.map((index) => ({
|
||||
lookupTableKey: lookup.accountKey,
|
||||
lookupTableIndex: index,
|
||||
}))
|
||||
),
|
||||
];
|
||||
return (
|
||||
<div className="card" key={index} ref={scrollAnchorRef}>
|
||||
<div className={`card-header${!expanded ? " border-bottom-none" : ""}`}>
|
||||
|
@ -58,12 +79,19 @@ function InstructionCard({
|
|||
<td>Program</td>
|
||||
<td className="text-lg-end">
|
||||
<AddressWithContext
|
||||
pubkey={message.accountKeys[ix.programIdIndex]}
|
||||
pubkey={message.staticAccountKeys[ix.programIdIndex]}
|
||||
validator={programValidator}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
{ix.accounts.map((accountIndex, index) => {
|
||||
{ix.accountKeyIndexes.map((accountIndex, index) => {
|
||||
let lookup;
|
||||
if (accountIndex >= message.staticAccountKeys.length) {
|
||||
const lookupIndex =
|
||||
accountIndex - message.staticAccountKeys.length;
|
||||
lookup = lookupsForAccountKeyIndex[lookupIndex];
|
||||
}
|
||||
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td>
|
||||
|
@ -82,9 +110,16 @@ function InstructionCard({
|
|||
</div>
|
||||
</td>
|
||||
<td className="text-lg-end">
|
||||
<AddressWithContext
|
||||
pubkey={message.accountKeys[accountIndex]}
|
||||
/>
|
||||
{lookup === undefined ? (
|
||||
<AddressWithContext
|
||||
pubkey={message.staticAccountKeys[accountIndex]}
|
||||
/>
|
||||
) : (
|
||||
<AddressFromLookupTableWithContext
|
||||
lookupTableKey={lookup.lookupTableKey}
|
||||
lookupTableIndex={lookup.lookupTableIndex}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
@ -94,7 +129,7 @@ function InstructionCard({
|
|||
Instruction Data <span className="text-muted">(Hex)</span>
|
||||
</td>
|
||||
<td className="text-lg-end">
|
||||
<HexData raw={bs58.decode(ix.data)} />
|
||||
<HexData raw={Buffer.from(ix.data)} />
|
||||
</td>
|
||||
</tr>
|
||||
</TableCardBody>
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import React from "react";
|
||||
import { Message } from "@solana/web3.js";
|
||||
import { VersionedMessage } from "@solana/web3.js";
|
||||
import type { TransactionData } from "./InspectorPage";
|
||||
import { useQuery } from "utils/url";
|
||||
import { useHistory, useLocation } from "react-router";
|
||||
import base58 from "bs58";
|
||||
|
||||
function deserializeTransaction(bytes: Uint8Array): {
|
||||
message: Message;
|
||||
message: VersionedMessage;
|
||||
signatures: string[];
|
||||
} | null {
|
||||
const SIGNATURE_LENGTH = 64;
|
||||
|
@ -19,17 +19,12 @@ function deserializeTransaction(bytes: Uint8Array): {
|
|||
bytes = bytes.slice(SIGNATURE_LENGTH);
|
||||
signatures.push(base58.encode(rawSignature));
|
||||
}
|
||||
|
||||
const requiredSignatures = bytes[0];
|
||||
if (requiredSignatures !== signaturesLen) {
|
||||
throw new Error("Signature length mismatch");
|
||||
}
|
||||
} catch (err) {
|
||||
// Errors above indicate that the bytes do not encode a transaction.
|
||||
return null;
|
||||
}
|
||||
|
||||
const message = Message.from(bytes);
|
||||
const message = VersionedMessage.deserialize(bytes);
|
||||
return { message, signatures };
|
||||
}
|
||||
|
||||
|
@ -103,7 +98,7 @@ export function RawInput({
|
|||
signatures: tx.signatures,
|
||||
});
|
||||
} else {
|
||||
const message = Message.from(buffer);
|
||||
const message = VersionedMessage.deserialize(buffer);
|
||||
setTransactionData({
|
||||
rawMessage: buffer,
|
||||
message,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from "react";
|
||||
import bs58 from "bs58";
|
||||
import * as nacl from "tweetnacl";
|
||||
import { Message, PublicKey } from "@solana/web3.js";
|
||||
import { PublicKey, VersionedMessage } from "@solana/web3.js";
|
||||
import { Signature } from "components/common/Signature";
|
||||
import { Address } from "components/common/Address";
|
||||
|
||||
|
@ -11,12 +11,12 @@ export function TransactionSignatures({
|
|||
rawMessage,
|
||||
}: {
|
||||
signatures: (string | null)[];
|
||||
message: Message;
|
||||
message: VersionedMessage;
|
||||
rawMessage: Uint8Array;
|
||||
}) {
|
||||
const signatureRows = React.useMemo(() => {
|
||||
return signatures.map((signature, index) => {
|
||||
const publicKey = message.accountKeys[index];
|
||||
const publicKey = message.staticAccountKeys[index];
|
||||
|
||||
let verified;
|
||||
if (signature) {
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import React from "react";
|
||||
import bs58 from "bs58";
|
||||
import { Connection, Message, Transaction } from "@solana/web3.js";
|
||||
import {
|
||||
Connection,
|
||||
VersionedMessage,
|
||||
VersionedTransaction,
|
||||
} from "@solana/web3.js";
|
||||
import { useCluster } from "providers/cluster";
|
||||
import { InstructionLogs, parseProgramLogs } from "utils/program-logs";
|
||||
import { ProgramLogsCardBody } from "components/ProgramLogsCardBody";
|
||||
|
||||
const DEFAULT_SIGNATURE = bs58.encode(Buffer.alloc(64).fill(0));
|
||||
|
||||
export function SimulatorCard({ message }: { message: Message }) {
|
||||
export function SimulatorCard({ message }: { message: VersionedMessage }) {
|
||||
const { cluster, url } = useCluster();
|
||||
const {
|
||||
simulate,
|
||||
|
@ -77,7 +78,7 @@ export function SimulatorCard({ message }: { message: Message }) {
|
|||
);
|
||||
}
|
||||
|
||||
function useSimulator(message: Message) {
|
||||
function useSimulator(message: VersionedMessage) {
|
||||
const { cluster, url } = useCluster();
|
||||
const [simulating, setSimulating] = React.useState(false);
|
||||
const [logs, setLogs] = React.useState<Array<InstructionLogs> | null>(null);
|
||||
|
@ -97,15 +98,10 @@ function useSimulator(message: Message) {
|
|||
const connection = new Connection(url, "confirmed");
|
||||
(async () => {
|
||||
try {
|
||||
const tx = Transaction.populate(
|
||||
message,
|
||||
new Array(message.header.numRequiredSignatures).fill(
|
||||
DEFAULT_SIGNATURE
|
||||
)
|
||||
);
|
||||
|
||||
// Simulate without signers to skip signer verification
|
||||
const resp = await connection.simulateTransaction(tx);
|
||||
const resp = await connection.simulateTransaction(
|
||||
new VersionedTransaction(message)
|
||||
);
|
||||
if (resp.value.logs === null) {
|
||||
throw new Error("Expected to receive logs from simulation");
|
||||
}
|
||||
|
|
|
@ -109,7 +109,9 @@ async function fetchParsedTransactions(
|
|||
0,
|
||||
MAX_TRANSACTION_BATCH_SIZE
|
||||
);
|
||||
const fetched = await connection.getParsedTransactions(signatures);
|
||||
const fetched = await connection.getParsedTransactions(signatures, {
|
||||
maxSupportedTransactionVersion: 0,
|
||||
});
|
||||
fetched.forEach(
|
||||
(
|
||||
transactionWithMeta: ParsedTransactionWithMeta | null,
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
import React from "react";
|
||||
import { pubkeyToString } from "utils";
|
||||
import { PublicKey, Connection, StakeActivationData } from "@solana/web3.js";
|
||||
import {
|
||||
PublicKey,
|
||||
Connection,
|
||||
StakeActivationData,
|
||||
AddressLookupTableAccount,
|
||||
AddressLookupTableProgram,
|
||||
} from "@solana/web3.js";
|
||||
import { useCluster, Cluster } from "../cluster";
|
||||
import { HistoryProvider } from "./history";
|
||||
import { TokensProvider } from "./tokens";
|
||||
|
@ -19,6 +25,7 @@ import { VoteAccount } from "validators/accounts/vote";
|
|||
import { NonceAccount } from "validators/accounts/nonce";
|
||||
import { SysvarAccount } from "validators/accounts/sysvar";
|
||||
import { ConfigAccount } from "validators/accounts/config";
|
||||
import { ParsedAddressLookupTableAccount } from "validators/accounts/address-lookup-table";
|
||||
import { FlaggedAccountsProvider } from "./flagged-accounts";
|
||||
import {
|
||||
ProgramDataAccount,
|
||||
|
@ -76,6 +83,11 @@ export type ConfigProgramData = {
|
|||
parsed: ConfigAccount;
|
||||
};
|
||||
|
||||
export type AddressLookupTableProgramData = {
|
||||
program: "address-lookup-table";
|
||||
parsed: ParsedAddressLookupTableAccount;
|
||||
};
|
||||
|
||||
export type ProgramData =
|
||||
| UpgradeableLoaderAccountData
|
||||
| StakeProgramData
|
||||
|
@ -83,7 +95,8 @@ export type ProgramData =
|
|||
| VoteProgramData
|
||||
| NonceProgramData
|
||||
| SysvarProgramData
|
||||
| ConfigProgramData;
|
||||
| ConfigProgramData
|
||||
| AddressLookupTableProgramData;
|
||||
|
||||
export interface Details {
|
||||
executable: boolean;
|
||||
|
@ -238,6 +251,17 @@ async function fetchAccountInfo(
|
|||
};
|
||||
break;
|
||||
|
||||
case "address-lookup-table": {
|
||||
const parsed = create(info, ParsedAddressLookupTableAccount);
|
||||
|
||||
data = {
|
||||
program: result.data.program,
|
||||
parsed,
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "spl-token":
|
||||
const parsed = create(info, TokenAccount);
|
||||
let nftData;
|
||||
|
@ -428,6 +452,40 @@ export function useTokenAccountInfo(
|
|||
}
|
||||
}
|
||||
|
||||
export function useAddressLookupTable(
|
||||
address: string | undefined
|
||||
): AddressLookupTableAccount | undefined | string {
|
||||
const accountInfo = useAccountInfo(address);
|
||||
if (address === undefined) return;
|
||||
if (accountInfo?.data?.details === undefined) return;
|
||||
if (accountInfo.data.lamports === 0) return "Lookup Table Not Found";
|
||||
const { data, rawData } = accountInfo.data.details;
|
||||
|
||||
const key = new PublicKey(address);
|
||||
if (data && data.program === "address-lookup-table") {
|
||||
if (data.parsed.type === "lookupTable") {
|
||||
return new AddressLookupTableAccount({
|
||||
key,
|
||||
state: data.parsed.info,
|
||||
});
|
||||
} else if (data.parsed.type === "uninitialized") {
|
||||
return "Lookup Table Uninitialized";
|
||||
}
|
||||
} else if (
|
||||
rawData &&
|
||||
accountInfo.data.details.owner.equals(AddressLookupTableProgram.programId)
|
||||
) {
|
||||
try {
|
||||
return new AddressLookupTableAccount({
|
||||
key,
|
||||
state: AddressLookupTableAccount.deserialize(rawData),
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return "Invalid Lookup Table";
|
||||
}
|
||||
|
||||
export function useFetchAccountInfo() {
|
||||
const dispatch = React.useContext(DispatchContext);
|
||||
if (!dispatch) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from "react";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import * as Cache from "providers/cache";
|
||||
import { Connection, BlockResponse, PublicKey } from "@solana/web3.js";
|
||||
import { Connection, PublicKey, VersionedBlockResponse } from "@solana/web3.js";
|
||||
import { useCluster, Cluster } from "./cluster";
|
||||
|
||||
export enum FetchStatus {
|
||||
|
@ -16,7 +16,7 @@ export enum ActionType {
|
|||
}
|
||||
|
||||
type Block = {
|
||||
block?: BlockResponse;
|
||||
block?: VersionedBlockResponse;
|
||||
blockLeader?: PublicKey;
|
||||
childSlot?: number;
|
||||
childLeader?: PublicKey;
|
||||
|
@ -76,7 +76,9 @@ export async function fetchBlock(
|
|||
|
||||
try {
|
||||
const connection = new Connection(url, "confirmed");
|
||||
const block = await connection.getBlock(slot);
|
||||
const block = await connection.getBlock(slot, {
|
||||
maxSupportedTransactionVersion: 0,
|
||||
});
|
||||
if (block === null) {
|
||||
data = {};
|
||||
status = FetchStatus.Fetched;
|
||||
|
|
|
@ -57,7 +57,7 @@ async function fetchDetails(
|
|||
try {
|
||||
transactionWithMeta = await new Connection(url).getParsedTransaction(
|
||||
signature,
|
||||
"confirmed"
|
||||
{ commitment: "confirmed", maxSupportedTransactionVersion: 0 }
|
||||
);
|
||||
fetchStatus = FetchStatus.Fetched;
|
||||
} catch (error) {
|
||||
|
|
|
@ -2,8 +2,9 @@ import React from "react";
|
|||
import {
|
||||
Connection,
|
||||
TransactionSignature,
|
||||
Transaction,
|
||||
Message,
|
||||
TransactionMessage,
|
||||
DecompileArgs,
|
||||
VersionedMessage,
|
||||
} from "@solana/web3.js";
|
||||
import { useCluster, Cluster } from "../cluster";
|
||||
import * as Cache from "providers/cache";
|
||||
|
@ -12,8 +13,8 @@ import { reportError } from "utils/sentry";
|
|||
|
||||
export interface Details {
|
||||
raw?: {
|
||||
transaction: Transaction;
|
||||
message: Message;
|
||||
transaction: TransactionMessage;
|
||||
message: VersionedMessage;
|
||||
signatures: string[];
|
||||
} | null;
|
||||
}
|
||||
|
@ -66,17 +67,22 @@ async function fetchRawTransaction(
|
|||
) {
|
||||
let fetchStatus;
|
||||
try {
|
||||
const response = await new Connection(url).getTransaction(signature);
|
||||
const response = await new Connection(url).getTransaction(signature, {
|
||||
maxSupportedTransactionVersion: 0,
|
||||
});
|
||||
fetchStatus = FetchStatus.Fetched;
|
||||
|
||||
let data: Details = { raw: null };
|
||||
if (response !== null) {
|
||||
const { message, signatures } = response.transaction;
|
||||
const accountKeysFromLookups = response.meta?.loadedAddresses;
|
||||
const decompileArgs: DecompileArgs | undefined =
|
||||
accountKeysFromLookups && { accountKeysFromLookups };
|
||||
data = {
|
||||
raw: {
|
||||
message,
|
||||
signatures,
|
||||
transaction: Transaction.populate(message, signatures),
|
||||
transaction: TransactionMessage.decompile(message, decompileArgs),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/* eslint-disable @typescript-eslint/no-redeclare */
|
||||
|
||||
import { Infer, number, enums, type, array, optional } from "superstruct";
|
||||
import { PublicKeyFromString } from "validators/pubkey";
|
||||
import { BigIntFromString, NumberFromString } from "validators/number";
|
||||
|
||||
export type AddressLookupTableAccountType = Infer<
|
||||
typeof AddressLookupTableAccountType
|
||||
>;
|
||||
export const AddressLookupTableAccountType = enums([
|
||||
"uninitialized",
|
||||
"lookupTable",
|
||||
]);
|
||||
|
||||
export type AddressLookupTableAccountInfo = Infer<
|
||||
typeof AddressLookupTableAccountInfo
|
||||
>;
|
||||
export const AddressLookupTableAccountInfo = type({
|
||||
deactivationSlot: BigIntFromString,
|
||||
lastExtendedSlot: NumberFromString,
|
||||
lastExtendedSlotStartIndex: number(),
|
||||
authority: optional(PublicKeyFromString),
|
||||
addresses: array(PublicKeyFromString),
|
||||
});
|
||||
|
||||
export type ParsedAddressLookupTableAccount = Infer<
|
||||
typeof ParsedAddressLookupTableAccount
|
||||
>;
|
||||
export const ParsedAddressLookupTableAccount = type({
|
||||
type: AddressLookupTableAccountType,
|
||||
info: AddressLookupTableAccountInfo,
|
||||
});
|
Loading…
Reference in New Issue