Explorer: Batch account fetching (#28415)
* Bump @solana/web3.js to 1.66.0 * Explorer: Add batched account fetcher to reduce RPC rate limiting
This commit is contained in:
parent
831ed96730
commit
d42e5725fe
|
@ -20,7 +20,7 @@
|
|||
"@solana/buffer-layout": "^3.0.0",
|
||||
"@solana/spl-token": "^0.0.13",
|
||||
"@solana/spl-token-registry": "^0.2.3736",
|
||||
"@solana/web3.js": "^1.63.1",
|
||||
"@solana/web3.js": "^1.66.0",
|
||||
"axios": "^0.27.2",
|
||||
"bignumber.js": "^9.0.2",
|
||||
"bn.js": "^5.2.0",
|
||||
|
@ -4490,6 +4490,14 @@
|
|||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@metaplex/js/node_modules/@types/bn.js": {
|
||||
"version": "4.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz",
|
||||
"integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@metaplex/js/node_modules/axios": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz",
|
||||
|
@ -4498,6 +4506,17 @@
|
|||
"follow-redirects": "^1.14.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@metaplex/js/node_modules/borsh": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/borsh/-/borsh-0.4.0.tgz",
|
||||
"integrity": "sha512-aX6qtLya3K0AkT66CmYWCCDr77qsE9arV05OmdFpmat9qu8Pg9J5tBUPDztAW5fNh/d/MyVG/OYziP52Ndzx1g==",
|
||||
"dependencies": {
|
||||
"@types/bn.js": "^4.11.5",
|
||||
"bn.js": "^5.0.0",
|
||||
"bs58": "^4.0.0",
|
||||
"text-encoding-utf-8": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@metaplex/js/node_modules/buffer": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||
|
@ -5154,9 +5173,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@solana/web3.js": {
|
||||
"version": "1.63.1",
|
||||
"resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.63.1.tgz",
|
||||
"integrity": "sha512-wgEdGVK5FTS2zENxbcGSvKpGZ0jDS6BUdGu8Gn6ns0CzgJkK83u4ip3THSnBPEQ5i/jrqukg998BwV1H67+qiQ==",
|
||||
"version": "1.66.0",
|
||||
"resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.66.0.tgz",
|
||||
"integrity": "sha512-hQCzWd9u100Ba3da52u7GeDRqSRwyFZtZkUj4j08GKSK3c3+ZQ6CQoN3HBXzfyjVKMTyRGKT0FlPA+hOX3kmOQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@noble/ed25519": "^1.7.0",
|
||||
|
@ -5220,16 +5239,6 @@
|
|||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@solana/web3.js/node_modules/borsh": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz",
|
||||
"integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==",
|
||||
"dependencies": {
|
||||
"bn.js": "^5.2.0",
|
||||
"bs58": "^4.0.0",
|
||||
"text-encoding-utf-8": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@solana/web3.js/node_modules/superstruct": {
|
||||
"version": "0.14.2",
|
||||
"resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.14.2.tgz",
|
||||
|
@ -8145,24 +8154,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/borsh": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/borsh/-/borsh-0.4.0.tgz",
|
||||
"integrity": "sha512-aX6qtLya3K0AkT66CmYWCCDr77qsE9arV05OmdFpmat9qu8Pg9J5tBUPDztAW5fNh/d/MyVG/OYziP52Ndzx1g==",
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz",
|
||||
"integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==",
|
||||
"dependencies": {
|
||||
"@types/bn.js": "^4.11.5",
|
||||
"bn.js": "^5.0.0",
|
||||
"bn.js": "^5.2.0",
|
||||
"bs58": "^4.0.0",
|
||||
"text-encoding-utf-8": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/borsh/node_modules/@types/bn.js": {
|
||||
"version": "4.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz",
|
||||
"integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
|
@ -30706,6 +30706,14 @@
|
|||
"dotenv": "10.0.0"
|
||||
}
|
||||
},
|
||||
"@types/bn.js": {
|
||||
"version": "4.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz",
|
||||
"integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz",
|
||||
|
@ -30714,6 +30722,17 @@
|
|||
"follow-redirects": "^1.14.7"
|
||||
}
|
||||
},
|
||||
"borsh": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/borsh/-/borsh-0.4.0.tgz",
|
||||
"integrity": "sha512-aX6qtLya3K0AkT66CmYWCCDr77qsE9arV05OmdFpmat9qu8Pg9J5tBUPDztAW5fNh/d/MyVG/OYziP52Ndzx1g==",
|
||||
"requires": {
|
||||
"@types/bn.js": "^4.11.5",
|
||||
"bn.js": "^5.0.0",
|
||||
"bs58": "^4.0.0",
|
||||
"text-encoding-utf-8": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||
|
@ -31160,9 +31179,9 @@
|
|||
}
|
||||
},
|
||||
"@solana/web3.js": {
|
||||
"version": "1.63.1",
|
||||
"resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.63.1.tgz",
|
||||
"integrity": "sha512-wgEdGVK5FTS2zENxbcGSvKpGZ0jDS6BUdGu8Gn6ns0CzgJkK83u4ip3THSnBPEQ5i/jrqukg998BwV1H67+qiQ==",
|
||||
"version": "1.66.0",
|
||||
"resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.66.0.tgz",
|
||||
"integrity": "sha512-hQCzWd9u100Ba3da52u7GeDRqSRwyFZtZkUj4j08GKSK3c3+ZQ6CQoN3HBXzfyjVKMTyRGKT0FlPA+hOX3kmOQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@noble/ed25519": "^1.7.0",
|
||||
|
@ -31208,16 +31227,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"borsh": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz",
|
||||
"integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==",
|
||||
"requires": {
|
||||
"bn.js": "^5.2.0",
|
||||
"bs58": "^4.0.0",
|
||||
"text-encoding-utf-8": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"superstruct": {
|
||||
"version": "0.14.2",
|
||||
"resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.14.2.tgz",
|
||||
|
@ -33506,24 +33515,13 @@
|
|||
"requires": {}
|
||||
},
|
||||
"borsh": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/borsh/-/borsh-0.4.0.tgz",
|
||||
"integrity": "sha512-aX6qtLya3K0AkT66CmYWCCDr77qsE9arV05OmdFpmat9qu8Pg9J5tBUPDztAW5fNh/d/MyVG/OYziP52Ndzx1g==",
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz",
|
||||
"integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==",
|
||||
"requires": {
|
||||
"@types/bn.js": "^4.11.5",
|
||||
"bn.js": "^5.0.0",
|
||||
"bn.js": "^5.2.0",
|
||||
"bs58": "^4.0.0",
|
||||
"text-encoding-utf-8": "^1.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/bn.js": {
|
||||
"version": "4.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz",
|
||||
"integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"brace-expansion": {
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
"@solana/buffer-layout": "^3.0.0",
|
||||
"@solana/spl-token": "^0.0.13",
|
||||
"@solana/spl-token-registry": "^0.2.3736",
|
||||
"@solana/web3.js": "^1.63.1",
|
||||
"@solana/web3.js": "^1.66.0",
|
||||
"axios": "^0.27.2",
|
||||
"bignumber.js": "^9.0.2",
|
||||
"bn.js": "^5.2.0",
|
||||
|
|
|
@ -58,7 +58,7 @@ function StakeConfigCard({
|
|||
<div className="card">
|
||||
<AccountHeader
|
||||
title="Stake Config"
|
||||
refresh={() => refresh(account.pubkey)}
|
||||
refresh={() => refresh(account.pubkey, "parsed")}
|
||||
/>
|
||||
|
||||
<TableCardBody>
|
||||
|
@ -91,7 +91,7 @@ function ValidatorInfoCard({
|
|||
<div className="card">
|
||||
<AccountHeader
|
||||
title="Validator Info"
|
||||
refresh={() => refresh(account.pubkey)}
|
||||
refresh={() => refresh(account.pubkey, "parsed")}
|
||||
/>
|
||||
|
||||
<TableCardBody>
|
||||
|
|
|
@ -25,7 +25,7 @@ export function MetaplexNFTHeader({
|
|||
|
||||
React.useEffect(() => {
|
||||
if (collectionAddress && !collectionMintInfo) {
|
||||
fetchAccountInfo(new PublicKey(collectionAddress));
|
||||
fetchAccountInfo(new PublicKey(collectionAddress), "parsed");
|
||||
}
|
||||
}, [fetchAccountInfo, collectionAddress]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ export function NonceAccountSection({
|
|||
<div className="card">
|
||||
<AccountHeader
|
||||
title="Nonce Account"
|
||||
refresh={() => refresh(account.pubkey)}
|
||||
refresh={() => refresh(account.pubkey, "parsed")}
|
||||
/>
|
||||
|
||||
<TableCardBody>
|
||||
|
|
|
@ -108,7 +108,7 @@ function OverviewCard({
|
|||
</h3>
|
||||
<button
|
||||
className="btn btn-white btn-sm"
|
||||
onClick={() => refresh(account.pubkey)}
|
||||
onClick={() => refresh(account.pubkey, "parsed")}
|
||||
>
|
||||
<span className="fe fe-refresh-cw me-2"></span>
|
||||
Refresh
|
||||
|
|
|
@ -107,7 +107,7 @@ function SysvarAccountRecentBlockhashesCard({
|
|||
<div className="card">
|
||||
<AccountHeader
|
||||
title="Sysvar: Recent Blockhashes"
|
||||
refresh={() => refresh(account.pubkey)}
|
||||
refresh={() => refresh(account.pubkey, "parsed")}
|
||||
/>
|
||||
|
||||
<TableCardBody>
|
||||
|
@ -129,7 +129,7 @@ function SysvarAccountSlotHashes({
|
|||
<div className="card">
|
||||
<AccountHeader
|
||||
title="Sysvar: Slot Hashes"
|
||||
refresh={() => refresh(account.pubkey)}
|
||||
refresh={() => refresh(account.pubkey, "parsed")}
|
||||
/>
|
||||
|
||||
<TableCardBody>
|
||||
|
@ -158,7 +158,7 @@ function SysvarAccountSlotHistory({
|
|||
<div className="card">
|
||||
<AccountHeader
|
||||
title="Sysvar: Slot History"
|
||||
refresh={() => refresh(account.pubkey)}
|
||||
refresh={() => refresh(account.pubkey, "parsed")}
|
||||
/>
|
||||
|
||||
<TableCardBody>
|
||||
|
@ -194,7 +194,7 @@ function SysvarAccountStakeHistory({
|
|||
<div className="card">
|
||||
<AccountHeader
|
||||
title="Sysvar: Stake History"
|
||||
refresh={() => refresh(account.pubkey)}
|
||||
refresh={() => refresh(account.pubkey, "parsed")}
|
||||
/>
|
||||
|
||||
<TableCardBody>
|
||||
|
@ -217,7 +217,7 @@ function SysvarAccountFeesCard({
|
|||
<div className="card">
|
||||
<AccountHeader
|
||||
title="Sysvar: Fees"
|
||||
refresh={() => refresh(account.pubkey)}
|
||||
refresh={() => refresh(account.pubkey, "parsed")}
|
||||
/>
|
||||
|
||||
<TableCardBody>
|
||||
|
@ -247,7 +247,7 @@ function SysvarAccountEpochScheduleCard({
|
|||
<div className="card">
|
||||
<AccountHeader
|
||||
title="Sysvar: Epoch Schedule"
|
||||
refresh={() => refresh(account.pubkey)}
|
||||
refresh={() => refresh(account.pubkey, "parsed")}
|
||||
/>
|
||||
|
||||
<TableCardBody>
|
||||
|
@ -301,7 +301,7 @@ function SysvarAccountClockCard({
|
|||
<div className="card">
|
||||
<AccountHeader
|
||||
title="Sysvar: Clock"
|
||||
refresh={() => refresh(account.pubkey)}
|
||||
refresh={() => refresh(account.pubkey, "parsed")}
|
||||
/>
|
||||
|
||||
<TableCardBody>
|
||||
|
@ -352,7 +352,7 @@ function SysvarAccountRentCard({
|
|||
<div className="card">
|
||||
<AccountHeader
|
||||
title="Sysvar: Rent"
|
||||
refresh={() => refresh(account.pubkey)}
|
||||
refresh={() => refresh(account.pubkey, "parsed")}
|
||||
/>
|
||||
|
||||
<TableCardBody>
|
||||
|
@ -401,7 +401,7 @@ function SysvarAccountRewardsCard({
|
|||
<div className="card">
|
||||
<AccountHeader
|
||||
title="Sysvar: Rewards"
|
||||
refresh={() => refresh(account.pubkey)}
|
||||
refresh={() => refresh(account.pubkey, "parsed")}
|
||||
/>
|
||||
|
||||
<TableCardBody>
|
||||
|
|
|
@ -95,7 +95,7 @@ function FungibleTokenMintAccountCard({
|
|||
const { tokenRegistry } = useTokenRegistry();
|
||||
const mintAddress = account.pubkey.toBase58();
|
||||
const fetchInfo = useFetchAccountInfo();
|
||||
const refresh = () => fetchInfo(account.pubkey);
|
||||
const refresh = () => fetchInfo(account.pubkey, "parsed");
|
||||
const tokenInfo = tokenRegistry.get(mintAddress);
|
||||
|
||||
const bridgeContractAddress = getEthAddress(
|
||||
|
@ -303,7 +303,7 @@ function NonFungibleTokenMintAccountCard({
|
|||
mintInfo: MintAccountInfo;
|
||||
}) {
|
||||
const fetchInfo = useFetchAccountInfo();
|
||||
const refresh = () => fetchInfo(account.pubkey);
|
||||
const refresh = () => fetchInfo(account.pubkey, "parsed");
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
|
@ -437,7 +437,7 @@ function TokenAccountCard({
|
|||
</h3>
|
||||
<button
|
||||
className="btn btn-white btn-sm"
|
||||
onClick={() => refresh(account.pubkey)}
|
||||
onClick={() => refresh(account.pubkey, "parsed")}
|
||||
>
|
||||
<span className="fe fe-refresh-cw me-2"></span>
|
||||
Refresh
|
||||
|
@ -516,7 +516,7 @@ function MultisigAccountCard({
|
|||
</h3>
|
||||
<button
|
||||
className="btn btn-white btn-sm"
|
||||
onClick={() => refresh(account.pubkey)}
|
||||
onClick={() => refresh(account.pubkey, "parsed")}
|
||||
>
|
||||
<span className="fe fe-refresh-cw me-2"></span>
|
||||
Refresh
|
||||
|
|
|
@ -38,10 +38,12 @@ export function UnknownAccountCard({ account }: { account: Account }) {
|
|||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Allocated Data Size</td>
|
||||
<td className="text-lg-end">{account.space} byte(s)</td>
|
||||
</tr>
|
||||
{account.space !== undefined && (
|
||||
<tr>
|
||||
<td>Allocated Data Size</td>
|
||||
<td className="text-lg-end">{account.space} byte(s)</td>
|
||||
</tr>
|
||||
)}
|
||||
|
||||
<tr>
|
||||
<td>Assigned Program Id</td>
|
||||
|
|
|
@ -81,7 +81,7 @@ export function UpgradeableProgramSection({
|
|||
</h3>
|
||||
<button
|
||||
className="btn btn-white btn-sm"
|
||||
onClick={() => refresh(account.pubkey)}
|
||||
onClick={() => refresh(account.pubkey, "parsed")}
|
||||
>
|
||||
<span className="fe fe-refresh-cw me-2"></span>
|
||||
Refresh
|
||||
|
@ -218,7 +218,7 @@ export function UpgradeableProgramDataSection({
|
|||
</h3>
|
||||
<button
|
||||
className="btn btn-white btn-sm"
|
||||
onClick={() => refresh(account.pubkey)}
|
||||
onClick={() => refresh(account.pubkey, "parsed")}
|
||||
>
|
||||
<span className="fe fe-refresh-cw me-2"></span>
|
||||
Refresh
|
||||
|
@ -238,17 +238,19 @@ export function UpgradeableProgramDataSection({
|
|||
<SolBalance lamports={account.lamports} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Data Size (Bytes)</td>
|
||||
<td className="text-lg-end">
|
||||
<Downloadable
|
||||
data={programData.data[0]}
|
||||
filename={`${account.pubkey.toString()}.bin`}
|
||||
>
|
||||
<span className="me-2">{account.space}</span>
|
||||
</Downloadable>
|
||||
</td>
|
||||
</tr>
|
||||
{account.space !== undefined && (
|
||||
<tr>
|
||||
<td>Data Size (Bytes)</td>
|
||||
<td className="text-lg-end">
|
||||
<Downloadable
|
||||
data={programData.data[0]}
|
||||
filename={`${account.pubkey.toString()}.bin`}
|
||||
>
|
||||
<span className="me-2">{account.space}</span>
|
||||
</Downloadable>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td>Upgradeable</td>
|
||||
<td className="text-lg-end">
|
||||
|
@ -290,7 +292,7 @@ export function UpgradeableProgramBufferSection({
|
|||
</h3>
|
||||
<button
|
||||
className="btn btn-white btn-sm"
|
||||
onClick={() => refresh(account.pubkey)}
|
||||
onClick={() => refresh(account.pubkey, "parsed")}
|
||||
>
|
||||
<span className="fe fe-refresh-cw me-2"></span>
|
||||
Refresh
|
||||
|
@ -310,10 +312,12 @@ export function UpgradeableProgramBufferSection({
|
|||
<SolBalance lamports={account.lamports} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Data Size (Bytes)</td>
|
||||
<td className="text-lg-end">{account.space}</td>
|
||||
</tr>
|
||||
{account.space !== undefined && (
|
||||
<tr>
|
||||
<td>Data Size (Bytes)</td>
|
||||
<td className="text-lg-end">{account.space}</td>
|
||||
</tr>
|
||||
)}
|
||||
{programBuffer.authority !== null && (
|
||||
<tr>
|
||||
<td>Deploy Authority</td>
|
||||
|
|
|
@ -24,7 +24,7 @@ export function VoteAccountSection({
|
|||
<div className="card">
|
||||
<AccountHeader
|
||||
title="Vote Account"
|
||||
refresh={() => refresh(account.pubkey)}
|
||||
refresh={() => refresh(account.pubkey, "parsed")}
|
||||
/>
|
||||
|
||||
<TableCardBody>
|
||||
|
|
|
@ -39,7 +39,7 @@ export function AddressLookupTableAccountSection(
|
|||
</h3>
|
||||
<button
|
||||
className="btn btn-white btn-sm"
|
||||
onClick={() => refresh(account.pubkey)}
|
||||
onClick={() => refresh(account.pubkey, "parsed")}
|
||||
>
|
||||
<span className="fe fe-refresh-cw me-2"></span>
|
||||
Refresh
|
||||
|
|
|
@ -28,7 +28,7 @@ export function NFTokenAccountSection({ account }: { account: Account }) {
|
|||
|
||||
const NFTCard = ({ nft }: { nft: NftokenTypes.NftAccount }) => {
|
||||
const fetchInfo = useFetchAccountInfo();
|
||||
const refresh = () => fetchInfo(new PublicKey(nft.address));
|
||||
const refresh = () => fetchInfo(new PublicKey(nft.address), "parsed");
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
|
@ -163,7 +163,7 @@ const CollectionCard = ({
|
|||
collection: NftokenTypes.CollectionAccount;
|
||||
}) => {
|
||||
const fetchInfo = useFetchAccountInfo();
|
||||
const refresh = () => fetchInfo(new PublicKey(collection.address));
|
||||
const refresh = () => fetchInfo(new PublicKey(collection.address), "parsed");
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
|
|
|
@ -96,13 +96,13 @@ function TokenInstruction(props: InfoProps) {
|
|||
|
||||
React.useEffect(() => {
|
||||
if (tokenAddress && !tokenInfo) {
|
||||
fetchAccountInfo(new PublicKey(tokenAddress));
|
||||
fetchAccountInfo(new PublicKey(tokenAddress), "parsed");
|
||||
}
|
||||
}, [fetchAccountInfo, tokenAddress]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
React.useEffect(() => {
|
||||
if (mintAddress && !mintInfo) {
|
||||
fetchAccountInfo(new PublicKey(mintAddress));
|
||||
fetchAccountInfo(new PublicKey(mintAddress), "parsed");
|
||||
}
|
||||
}, [fetchAccountInfo, mintAddress]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
|
|
|
@ -173,7 +173,7 @@ export function AccountDetailsPage({ address, tab }: Props) {
|
|||
// Fetch account on load
|
||||
React.useEffect(() => {
|
||||
if (!info && status === ClusterStatus.Connected && pubkey) {
|
||||
fetchAccount(pubkey);
|
||||
fetchAccount(pubkey, "parsed");
|
||||
}
|
||||
}, [address, status]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
|
@ -303,7 +303,12 @@ function DetailsSections({
|
|||
info.status === FetchStatus.FetchFailed ||
|
||||
info.data?.lamports === undefined
|
||||
) {
|
||||
return <ErrorCard retry={() => fetchAccount(pubkey)} text="Fetch Failed" />;
|
||||
return (
|
||||
<ErrorCard
|
||||
retry={() => fetchAccount(pubkey, "parsed")}
|
||||
text="Fetch Failed"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const account = info.data;
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import React from "react";
|
||||
import { PublicKey, VersionedMessage } from "@solana/web3.js";
|
||||
import { Address } from "components/common/Address";
|
||||
import { useAddressLookupTable, useFetchAccountInfo } from "providers/accounts";
|
||||
import { useAddressLookupTable } from "providers/accounts";
|
||||
import { FetchStatus } from "providers/cache";
|
||||
|
||||
export function AddressTableLookupsCard({
|
||||
message,
|
||||
|
@ -80,31 +81,38 @@ function LookupRow({
|
|||
lookupTableIndex: number;
|
||||
readOnly: boolean;
|
||||
}) {
|
||||
const lookupTable = useAddressLookupTable(lookupTableKey.toBase58());
|
||||
const fetchAccountInfo = useFetchAccountInfo();
|
||||
React.useEffect(() => {
|
||||
if (!lookupTable) fetchAccountInfo(lookupTableKey);
|
||||
}, [lookupTableKey, lookupTable, fetchAccountInfo]);
|
||||
const lookupTableInfo = useAddressLookupTable(lookupTableKey.toBase58());
|
||||
|
||||
const loadingComponent = (
|
||||
<span className="text-muted">
|
||||
<span className="spinner-grow spinner-grow-sm me-2"></span>
|
||||
Loading
|
||||
</span>
|
||||
);
|
||||
|
||||
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 />;
|
||||
if (!lookupTableInfo) {
|
||||
resolvedKeyComponent = loadingComponent;
|
||||
} else {
|
||||
resolvedKeyComponent = (
|
||||
<span className="text-muted">Invalid Lookup Table Index</span>
|
||||
);
|
||||
const [lookupTable, status] = lookupTableInfo;
|
||||
if (status === FetchStatus.Fetching) {
|
||||
resolvedKeyComponent = loadingComponent;
|
||||
} else if (status === FetchStatus.FetchFailed || !lookupTable) {
|
||||
resolvedKeyComponent = (
|
||||
<span className="text-muted">Failed to fetch Lookup Table</span>
|
||||
);
|
||||
} else if (typeof lookupTable === "string") {
|
||||
resolvedKeyComponent = (
|
||||
<span className="text-muted">Invalid Lookup Table</span>
|
||||
);
|
||||
} else if (lookupTableIndex >= lookupTable.state.addresses.length) {
|
||||
resolvedKeyComponent = (
|
||||
<span className="text-muted">Invalid Lookup Table Index</span>
|
||||
);
|
||||
} else {
|
||||
const resolvedKey = lookupTable.state.addresses[lookupTableIndex];
|
||||
resolvedKeyComponent = <Address pubkey={resolvedKey} link />;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -20,8 +20,6 @@ export const createFeePayerValidator = (
|
|||
if (account.lamports === 0) return "Account doesn't exist";
|
||||
if (!account.owner.equals(SystemProgram.programId))
|
||||
return "Only system-owned accounts can pay fees";
|
||||
// TODO: Actually nonce accounts can pay fees too
|
||||
if (account.space > 0) return "Only unallocated accounts can pay fees";
|
||||
if (account.lamports < feeLamports) {
|
||||
return "Insufficient funds for fees";
|
||||
}
|
||||
|
@ -42,13 +40,8 @@ export function AddressFromLookupTableWithContext({
|
|||
lookupTableKey: PublicKey;
|
||||
lookupTableIndex: number;
|
||||
}) {
|
||||
const lookupTable = useAddressLookupTable(lookupTableKey.toBase58());
|
||||
const fetchAccountInfo = useFetchAccountInfo();
|
||||
React.useEffect(() => {
|
||||
if (!lookupTable) fetchAccountInfo(lookupTableKey);
|
||||
}, [lookupTableKey, lookupTable, fetchAccountInfo]);
|
||||
|
||||
let pubkey;
|
||||
const lookupTableInfo = useAddressLookupTable(lookupTableKey.toBase58());
|
||||
const lookupTable = lookupTableInfo && lookupTableInfo[0];
|
||||
if (!lookupTable) {
|
||||
return (
|
||||
<span className="text-muted">
|
||||
|
@ -58,18 +51,17 @@ export function AddressFromLookupTableWithContext({
|
|||
);
|
||||
} else if (typeof lookupTable === "string") {
|
||||
return <div>Invalid Lookup Table</div>;
|
||||
} else if (lookupTableIndex < lookupTable.state.addresses.length) {
|
||||
pubkey = lookupTable.state.addresses[lookupTableIndex];
|
||||
} else {
|
||||
} else if (lookupTableIndex >= lookupTable.state.addresses.length) {
|
||||
return <div>Invalid Lookup Table Index</div>;
|
||||
} else {
|
||||
const pubkey = lookupTable.state.addresses[lookupTableIndex];
|
||||
return (
|
||||
<div className="d-flex align-items-end flex-column">
|
||||
<Address pubkey={pubkey} link />
|
||||
<AccountInfo pubkey={pubkey} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="d-flex align-items-end flex-column">
|
||||
<Address pubkey={pubkey} link />
|
||||
<AccountInfo pubkey={pubkey} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function AddressWithContext({
|
||||
|
@ -102,7 +94,7 @@ function AccountInfo({
|
|||
// Fetch account on load
|
||||
React.useEffect(() => {
|
||||
if (!info && status === ClusterStatus.Connected && pubkey) {
|
||||
fetchAccount(pubkey);
|
||||
fetchAccount(pubkey, "skip");
|
||||
}
|
||||
}, [address, status]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
|
@ -129,9 +121,10 @@ function AccountInfo({
|
|||
<span className="text-muted">
|
||||
{`Owned by ${ownerLabel || ownerAddress}.`}
|
||||
{` Balance is ${lamportsToSolString(account.lamports)} SOL.`}
|
||||
{` Size is ${new Intl.NumberFormat("en-US").format(
|
||||
account.space
|
||||
)} byte(s).`}
|
||||
{account.space !== undefined &&
|
||||
` Size is ${new Intl.NumberFormat("en-US").format(
|
||||
account.space
|
||||
)} byte(s).`}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import { SimulatorCard } from "./SimulatorCard";
|
|||
import { MIN_MESSAGE_LENGTH, RawInput } from "./RawInputCard";
|
||||
import { InstructionsSection } from "./InstructionsSection";
|
||||
import base58 from "bs58";
|
||||
import { useFetchAccountInfo } from "providers/accounts";
|
||||
|
||||
export type TransactionData = {
|
||||
rawMessage: Uint8Array;
|
||||
|
@ -269,6 +270,13 @@ function LoadedView({
|
|||
}) {
|
||||
const { message, rawMessage, signatures } = transaction;
|
||||
|
||||
const fetchAccountInfo = useFetchAccountInfo();
|
||||
React.useEffect(() => {
|
||||
for (let lookup of message.addressTableLookups) {
|
||||
fetchAccountInfo(lookup.accountKey, "parsed");
|
||||
}
|
||||
}, [message, fetchAccountInfo]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<OverviewCard message={message} raw={rawMessage} onClear={onClear} />
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
AddressLookupTableAccount,
|
||||
AddressLookupTableProgram,
|
||||
SystemProgram,
|
||||
ParsedAccountData,
|
||||
} from "@solana/web3.js";
|
||||
import { useCluster, Cluster } from "../cluster";
|
||||
import { HistoryProvider } from "./history";
|
||||
|
@ -109,242 +110,324 @@ export interface Account {
|
|||
lamports: number;
|
||||
executable: boolean;
|
||||
owner: PublicKey;
|
||||
space: number;
|
||||
space?: number;
|
||||
data: AccountData;
|
||||
}
|
||||
|
||||
type State = Cache.State<Account>;
|
||||
type Dispatch = Cache.Dispatch<Account>;
|
||||
type Fetchers = { [mode in FetchAccountDataMode]: MultipleAccountFetcher };
|
||||
|
||||
const FetchersContext = React.createContext<Fetchers | undefined>(undefined);
|
||||
const StateContext = React.createContext<State | undefined>(undefined);
|
||||
const DispatchContext = React.createContext<Dispatch | undefined>(undefined);
|
||||
|
||||
class MultipleAccountFetcher {
|
||||
pubkeys: PublicKey[] = [];
|
||||
fetchTimeout?: NodeJS.Timeout;
|
||||
|
||||
constructor(
|
||||
private dispatch: Dispatch,
|
||||
private cluster: Cluster,
|
||||
private url: string,
|
||||
private dataMode: FetchAccountDataMode
|
||||
) {}
|
||||
fetch = (pubkey: PublicKey) => {
|
||||
if (this.pubkeys !== undefined) this.pubkeys.push(pubkey);
|
||||
if (this.fetchTimeout === undefined) {
|
||||
this.fetchTimeout = setTimeout(() => {
|
||||
this.fetchTimeout = undefined;
|
||||
if (this.pubkeys !== undefined) {
|
||||
const pubkeys = this.pubkeys;
|
||||
this.pubkeys = [];
|
||||
|
||||
const { dispatch, cluster, url, dataMode } = this;
|
||||
fetchMultipleAccounts({ dispatch, pubkeys, cluster, url, dataMode });
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export type FetchAccountDataMode = "parsed" | "raw" | "skip";
|
||||
|
||||
type AccountsProviderProps = { children: React.ReactNode };
|
||||
export function AccountsProvider({ children }: AccountsProviderProps) {
|
||||
const { url } = useCluster();
|
||||
const { cluster, url } = useCluster();
|
||||
const [state, dispatch] = Cache.useReducer<Account>(url);
|
||||
const [fetchers, setFetchers] = React.useState<Fetchers>(() => ({
|
||||
skip: new MultipleAccountFetcher(dispatch, cluster, url, "skip"),
|
||||
raw: new MultipleAccountFetcher(dispatch, cluster, url, "raw"),
|
||||
parsed: new MultipleAccountFetcher(dispatch, cluster, url, "parsed"),
|
||||
}));
|
||||
|
||||
// Clear accounts cache whenever cluster is changed
|
||||
React.useEffect(() => {
|
||||
dispatch({ type: ActionType.Clear, url });
|
||||
}, [dispatch, url]);
|
||||
setFetchers({
|
||||
skip: new MultipleAccountFetcher(dispatch, cluster, url, "skip"),
|
||||
raw: new MultipleAccountFetcher(dispatch, cluster, url, "raw"),
|
||||
parsed: new MultipleAccountFetcher(dispatch, cluster, url, "parsed"),
|
||||
});
|
||||
}, [dispatch, cluster, url]);
|
||||
|
||||
return (
|
||||
<StateContext.Provider value={state}>
|
||||
<DispatchContext.Provider value={dispatch}>
|
||||
<TokensProvider>
|
||||
<HistoryProvider>
|
||||
<RewardsProvider>
|
||||
<FlaggedAccountsProvider>{children}</FlaggedAccountsProvider>
|
||||
</RewardsProvider>
|
||||
</HistoryProvider>
|
||||
</TokensProvider>
|
||||
<FetchersContext.Provider value={fetchers}>
|
||||
<TokensProvider>
|
||||
<HistoryProvider>
|
||||
<RewardsProvider>
|
||||
<FlaggedAccountsProvider>{children}</FlaggedAccountsProvider>
|
||||
</RewardsProvider>
|
||||
</HistoryProvider>
|
||||
</TokensProvider>
|
||||
</FetchersContext.Provider>
|
||||
</DispatchContext.Provider>
|
||||
</StateContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
async function fetchAccountInfo(
|
||||
dispatch: Dispatch,
|
||||
pubkey: PublicKey,
|
||||
cluster: Cluster,
|
||||
url: string
|
||||
) {
|
||||
dispatch({
|
||||
type: ActionType.Update,
|
||||
key: pubkey.toBase58(),
|
||||
status: Cache.FetchStatus.Fetching,
|
||||
url,
|
||||
});
|
||||
async function fetchMultipleAccounts({
|
||||
dispatch,
|
||||
pubkeys,
|
||||
dataMode,
|
||||
cluster,
|
||||
url,
|
||||
}: {
|
||||
dispatch: Dispatch;
|
||||
pubkeys: PublicKey[];
|
||||
dataMode: FetchAccountDataMode;
|
||||
cluster: Cluster;
|
||||
url: string;
|
||||
}) {
|
||||
for (let pubkey of pubkeys) {
|
||||
dispatch({
|
||||
type: ActionType.Update,
|
||||
key: pubkey.toBase58(),
|
||||
status: Cache.FetchStatus.Fetching,
|
||||
url,
|
||||
});
|
||||
}
|
||||
|
||||
let data;
|
||||
let fetchStatus;
|
||||
try {
|
||||
const connection = new Connection(url, "confirmed");
|
||||
const result = (await connection.getParsedAccountInfo(pubkey)).value;
|
||||
const BATCH_SIZE = 100;
|
||||
const connection = new Connection(url, "confirmed");
|
||||
|
||||
let account: Account;
|
||||
if (result === null) {
|
||||
account = {
|
||||
pubkey,
|
||||
lamports: 0,
|
||||
owner: SystemProgram.programId,
|
||||
space: 0,
|
||||
executable: false,
|
||||
data: { raw: Buffer.alloc(0) },
|
||||
};
|
||||
} else {
|
||||
// Only save data in memory if we can decode it
|
||||
let space: number;
|
||||
if (!("parsed" in result.data)) {
|
||||
space = result.data.length;
|
||||
let nextBatchStart = 0;
|
||||
while (nextBatchStart < pubkeys.length) {
|
||||
const batch = pubkeys.slice(nextBatchStart, nextBatchStart + BATCH_SIZE);
|
||||
nextBatchStart += BATCH_SIZE;
|
||||
|
||||
try {
|
||||
let results;
|
||||
if (dataMode === "parsed") {
|
||||
results = (await connection.getMultipleParsedAccounts(batch)).value;
|
||||
} else if (dataMode === "raw") {
|
||||
results = await connection.getMultipleAccountsInfo(batch);
|
||||
} else {
|
||||
space = result.data.space;
|
||||
results = await connection.getMultipleAccountsInfo(batch, {
|
||||
dataSlice: { length: 0, offset: 0 },
|
||||
});
|
||||
}
|
||||
|
||||
let parsedData: ParsedData | undefined;
|
||||
if ("parsed" in result.data) {
|
||||
try {
|
||||
const info = create(result.data.parsed, ParsedInfo);
|
||||
switch (result.data.program) {
|
||||
case "bpf-upgradeable-loader": {
|
||||
const parsed = create(info, UpgradeableLoaderAccount);
|
||||
for (let i = 0; i < batch.length; i++) {
|
||||
const pubkey = batch[i];
|
||||
const result = results[i];
|
||||
|
||||
// Fetch program data to get program upgradeability info
|
||||
let programData: ProgramDataAccountInfo | undefined;
|
||||
if (parsed.type === "program") {
|
||||
const result = (
|
||||
await connection.getParsedAccountInfo(parsed.info.programData)
|
||||
).value;
|
||||
if (
|
||||
result &&
|
||||
"parsed" in result.data &&
|
||||
result.data.program === "bpf-upgradeable-loader"
|
||||
) {
|
||||
const info = create(result.data.parsed, ParsedInfo);
|
||||
programData = create(info, ProgramDataAccount).info;
|
||||
}
|
||||
}
|
||||
|
||||
parsedData = {
|
||||
program: result.data.program,
|
||||
parsed,
|
||||
programData,
|
||||
};
|
||||
|
||||
break;
|
||||
let account: Account;
|
||||
if (result === null) {
|
||||
account = {
|
||||
pubkey,
|
||||
lamports: 0,
|
||||
owner: SystemProgram.programId,
|
||||
space: 0,
|
||||
executable: false,
|
||||
data: { raw: Buffer.alloc(0) },
|
||||
};
|
||||
} else {
|
||||
let space: number | undefined = undefined;
|
||||
let parsedData: ParsedData | undefined;
|
||||
if ("parsed" in result.data) {
|
||||
const accountData: ParsedAccountData = result.data;
|
||||
space = result.data.space;
|
||||
try {
|
||||
parsedData = await handleParsedAccountData(
|
||||
connection,
|
||||
pubkey,
|
||||
accountData
|
||||
);
|
||||
} catch (error) {
|
||||
reportError(error, { url, address: pubkey.toBase58() });
|
||||
}
|
||||
case "stake": {
|
||||
const parsed = create(info, StakeAccount);
|
||||
const isDelegated = parsed.type === "delegated";
|
||||
const activation = isDelegated
|
||||
? await connection.getStakeActivation(pubkey)
|
||||
: undefined;
|
||||
|
||||
parsedData = {
|
||||
program: result.data.program,
|
||||
parsed,
|
||||
activation,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "vote":
|
||||
parsedData = {
|
||||
program: result.data.program,
|
||||
parsed: create(info, VoteAccount),
|
||||
};
|
||||
break;
|
||||
case "nonce":
|
||||
parsedData = {
|
||||
program: result.data.program,
|
||||
parsed: create(info, NonceAccount),
|
||||
};
|
||||
break;
|
||||
case "sysvar":
|
||||
parsedData = {
|
||||
program: result.data.program,
|
||||
parsed: create(info, SysvarAccount),
|
||||
};
|
||||
break;
|
||||
case "config":
|
||||
parsedData = {
|
||||
program: result.data.program,
|
||||
parsed: create(info, ConfigAccount),
|
||||
};
|
||||
break;
|
||||
|
||||
case "address-lookup-table": {
|
||||
const parsed = create(info, ParsedAddressLookupTableAccount);
|
||||
|
||||
parsedData = {
|
||||
program: result.data.program,
|
||||
parsed,
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "spl-token":
|
||||
const parsed = create(info, TokenAccount);
|
||||
let nftData;
|
||||
|
||||
try {
|
||||
// Generate a PDA and check for a Metadata Account
|
||||
if (parsed.type === "mint") {
|
||||
const metadata = await Metadata.load(
|
||||
connection,
|
||||
await Metadata.getPDA(pubkey)
|
||||
);
|
||||
if (metadata) {
|
||||
// We have a valid Metadata account. Try and pull edition data.
|
||||
const editionInfo = await getEditionInfo(
|
||||
metadata,
|
||||
connection
|
||||
);
|
||||
const id = pubkeyToString(pubkey);
|
||||
const metadataJSON = await getMetaDataJSON(
|
||||
id,
|
||||
metadata.data
|
||||
);
|
||||
nftData = {
|
||||
metadata: metadata.data,
|
||||
json: metadataJSON,
|
||||
editionInfo,
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// unable to find NFT metadata account
|
||||
}
|
||||
|
||||
parsedData = {
|
||||
program: result.data.program,
|
||||
parsed,
|
||||
nftData,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
parsedData = undefined;
|
||||
}
|
||||
} catch (error) {
|
||||
reportError(error, { url, address: pubkey.toBase58() });
|
||||
|
||||
// If we cannot parse account layout as native spl account
|
||||
// then keep raw data for other components to decode
|
||||
let rawData: Buffer | undefined;
|
||||
if (
|
||||
!parsedData &&
|
||||
!("parsed" in result.data) &&
|
||||
dataMode !== "skip"
|
||||
) {
|
||||
space = result.data.length;
|
||||
rawData = result.data;
|
||||
}
|
||||
|
||||
account = {
|
||||
pubkey,
|
||||
lamports: result.lamports,
|
||||
executable: result.executable,
|
||||
owner: result.owner,
|
||||
space,
|
||||
data: {
|
||||
parsed: parsedData,
|
||||
raw: rawData,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: ActionType.Update,
|
||||
status: FetchStatus.Fetched,
|
||||
data: account,
|
||||
key: pubkey.toBase58(),
|
||||
url,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
if (cluster !== Cluster.Custom) {
|
||||
reportError(error, { url });
|
||||
}
|
||||
|
||||
for (let pubkey of batch) {
|
||||
dispatch({
|
||||
type: ActionType.Update,
|
||||
status: FetchStatus.FetchFailed,
|
||||
key: pubkey.toBase58(),
|
||||
url,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleParsedAccountData(
|
||||
connection: Connection,
|
||||
accountKey: PublicKey,
|
||||
accountData: ParsedAccountData
|
||||
): Promise<ParsedData | undefined> {
|
||||
const info = create(accountData.parsed, ParsedInfo);
|
||||
switch (accountData.program) {
|
||||
case "bpf-upgradeable-loader": {
|
||||
const parsed = create(info, UpgradeableLoaderAccount);
|
||||
|
||||
// Fetch program data to get program upgradeability info
|
||||
let programData: ProgramDataAccountInfo | undefined;
|
||||
if (parsed.type === "program") {
|
||||
const result = (
|
||||
await connection.getParsedAccountInfo(parsed.info.programData)
|
||||
).value;
|
||||
if (
|
||||
result &&
|
||||
"parsed" in result.data &&
|
||||
result.data.program === "bpf-upgradeable-loader"
|
||||
) {
|
||||
const info = create(result.data.parsed, ParsedInfo);
|
||||
programData = create(info, ProgramDataAccount).info;
|
||||
}
|
||||
}
|
||||
|
||||
// If we cannot parse account layout as native spl account
|
||||
// then keep raw data for other components to decode
|
||||
let rawData: Buffer | undefined;
|
||||
if (!parsedData && !("parsed" in result.data)) {
|
||||
rawData = result.data;
|
||||
}
|
||||
|
||||
account = {
|
||||
pubkey,
|
||||
lamports: result.lamports,
|
||||
space,
|
||||
executable: result.executable,
|
||||
owner: result.owner,
|
||||
data: {
|
||||
parsed: parsedData,
|
||||
raw: rawData,
|
||||
},
|
||||
return {
|
||||
program: accountData.program,
|
||||
parsed,
|
||||
programData,
|
||||
};
|
||||
}
|
||||
data = account;
|
||||
fetchStatus = FetchStatus.Fetched;
|
||||
} catch (error) {
|
||||
if (cluster !== Cluster.Custom) {
|
||||
reportError(error, { url });
|
||||
|
||||
case "stake": {
|
||||
const parsed = create(info, StakeAccount);
|
||||
const isDelegated = parsed.type === "delegated";
|
||||
const activation = isDelegated
|
||||
? await connection.getStakeActivation(accountKey)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
program: accountData.program,
|
||||
parsed,
|
||||
activation,
|
||||
};
|
||||
}
|
||||
|
||||
case "vote": {
|
||||
return {
|
||||
program: accountData.program,
|
||||
parsed: create(info, VoteAccount),
|
||||
};
|
||||
}
|
||||
|
||||
case "nonce": {
|
||||
return {
|
||||
program: accountData.program,
|
||||
parsed: create(info, NonceAccount),
|
||||
};
|
||||
}
|
||||
|
||||
case "sysvar": {
|
||||
return {
|
||||
program: accountData.program,
|
||||
parsed: create(info, SysvarAccount),
|
||||
};
|
||||
}
|
||||
|
||||
case "config": {
|
||||
return {
|
||||
program: accountData.program,
|
||||
parsed: create(info, ConfigAccount),
|
||||
};
|
||||
}
|
||||
|
||||
case "address-lookup-table": {
|
||||
const parsed = create(info, ParsedAddressLookupTableAccount);
|
||||
return {
|
||||
program: accountData.program,
|
||||
parsed,
|
||||
};
|
||||
}
|
||||
|
||||
case "spl-token": {
|
||||
const parsed = create(info, TokenAccount);
|
||||
let nftData;
|
||||
|
||||
try {
|
||||
// Generate a PDA and check for a Metadata Account
|
||||
if (parsed.type === "mint") {
|
||||
const metadata = await Metadata.load(
|
||||
connection,
|
||||
await Metadata.getPDA(accountKey)
|
||||
);
|
||||
if (metadata) {
|
||||
// We have a valid Metadata account. Try and pull edition data.
|
||||
const editionInfo = await getEditionInfo(metadata, connection);
|
||||
const id = pubkeyToString(accountKey);
|
||||
const metadataJSON = await getMetaDataJSON(id, metadata.data);
|
||||
nftData = {
|
||||
metadata: metadata.data,
|
||||
json: metadataJSON,
|
||||
editionInfo,
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// unable to find NFT metadata account
|
||||
}
|
||||
|
||||
return {
|
||||
program: accountData.program,
|
||||
parsed,
|
||||
nftData,
|
||||
};
|
||||
}
|
||||
fetchStatus = FetchStatus.FetchFailed;
|
||||
}
|
||||
dispatch({
|
||||
type: ActionType.Update,
|
||||
status: fetchStatus,
|
||||
data,
|
||||
key: pubkey.toBase58(),
|
||||
url,
|
||||
});
|
||||
}
|
||||
|
||||
const IMAGE_MIME_TYPE_REGEX = /data:image\/(svg\+xml|png|jpeg|gif)/g;
|
||||
|
@ -447,72 +530,83 @@ export function useTokenAccountInfo(
|
|||
address: string | undefined
|
||||
): TokenAccountInfo | undefined {
|
||||
const accountInfo = useAccountInfo(address);
|
||||
if (address === undefined || accountInfo?.data === undefined) return;
|
||||
const account = accountInfo.data;
|
||||
return React.useMemo(() => {
|
||||
if (address === undefined || accountInfo?.data === undefined) return;
|
||||
const account = accountInfo.data;
|
||||
|
||||
try {
|
||||
const parsedData = account.data.parsed;
|
||||
if (!parsedData) return;
|
||||
if (
|
||||
parsedData.program !== "spl-token" ||
|
||||
parsedData.parsed.type !== "account"
|
||||
) {
|
||||
return;
|
||||
try {
|
||||
const parsedData = account.data.parsed;
|
||||
if (!parsedData) return;
|
||||
if (
|
||||
parsedData.program !== "spl-token" ||
|
||||
parsedData.parsed.type !== "account"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
return create(parsedData.parsed.info, TokenAccountInfo);
|
||||
} catch (err) {
|
||||
reportError(err, { address });
|
||||
}
|
||||
|
||||
return create(parsedData.parsed.info, TokenAccountInfo);
|
||||
} catch (err) {
|
||||
reportError(err, { address });
|
||||
}
|
||||
}, [address, accountInfo]);
|
||||
}
|
||||
|
||||
export function useAddressLookupTable(
|
||||
address: string | undefined
|
||||
): AddressLookupTableAccount | undefined | string {
|
||||
address: string
|
||||
): [AddressLookupTableAccount | string | undefined, FetchStatus] | undefined {
|
||||
const accountInfo = useAccountInfo(address);
|
||||
if (address === undefined || accountInfo?.data === undefined) return;
|
||||
const account = accountInfo.data;
|
||||
if (account.lamports === 0) return "Lookup Table Not Found";
|
||||
const { parsed: parsedData, raw: rawData } = account.data;
|
||||
return React.useMemo(() => {
|
||||
if (accountInfo === undefined) return;
|
||||
const account = accountInfo.data;
|
||||
if (account === undefined) return [account, accountInfo.status];
|
||||
if (account.lamports === 0)
|
||||
return ["Lookup Table Not Found", accountInfo.status];
|
||||
const { parsed: parsedData, raw: rawData } = account.data;
|
||||
|
||||
const key = new PublicKey(address);
|
||||
if (parsedData && parsedData.program === "address-lookup-table") {
|
||||
if (parsedData.parsed.type === "lookupTable") {
|
||||
return new AddressLookupTableAccount({
|
||||
key,
|
||||
state: parsedData.parsed.info,
|
||||
});
|
||||
} else if (parsedData.parsed.type === "uninitialized") {
|
||||
return "Lookup Table Uninitialized";
|
||||
const key = new PublicKey(address);
|
||||
if (parsedData && parsedData.program === "address-lookup-table") {
|
||||
if (parsedData.parsed.type === "lookupTable") {
|
||||
return [
|
||||
new AddressLookupTableAccount({
|
||||
key,
|
||||
state: parsedData.parsed.info,
|
||||
}),
|
||||
accountInfo.status,
|
||||
];
|
||||
} else if (parsedData.parsed.type === "uninitialized") {
|
||||
return ["Lookup Table Uninitialized", accountInfo.status];
|
||||
}
|
||||
} else if (
|
||||
rawData &&
|
||||
account.owner.equals(AddressLookupTableProgram.programId)
|
||||
) {
|
||||
try {
|
||||
return [
|
||||
new AddressLookupTableAccount({
|
||||
key,
|
||||
state: AddressLookupTableAccount.deserialize(rawData),
|
||||
}),
|
||||
accountInfo.status,
|
||||
];
|
||||
} catch {}
|
||||
}
|
||||
} else if (
|
||||
rawData &&
|
||||
account.owner.equals(AddressLookupTableProgram.programId)
|
||||
) {
|
||||
try {
|
||||
return new AddressLookupTableAccount({
|
||||
key,
|
||||
state: AddressLookupTableAccount.deserialize(rawData),
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return "Invalid Lookup Table";
|
||||
return ["Invalid Lookup Table", accountInfo.status];
|
||||
}, [address, accountInfo]);
|
||||
}
|
||||
|
||||
export function useFetchAccountInfo() {
|
||||
const dispatch = React.useContext(DispatchContext);
|
||||
if (!dispatch) {
|
||||
const fetchers = React.useContext(FetchersContext);
|
||||
if (!fetchers) {
|
||||
throw new Error(
|
||||
`useFetchAccountInfo must be used within a AccountsProvider`
|
||||
);
|
||||
}
|
||||
|
||||
const { cluster, url } = useCluster();
|
||||
return React.useCallback(
|
||||
(pubkey: PublicKey) => {
|
||||
fetchAccountInfo(dispatch, pubkey, cluster, url);
|
||||
(pubkey: PublicKey, dataMode: FetchAccountDataMode) => {
|
||||
fetchers[dataMode].fetch(pubkey);
|
||||
},
|
||||
[dispatch, cluster, url]
|
||||
[fetchers]
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue