diff --git a/src/components/SignTransactionFormContent.js b/src/components/SignTransactionFormContent.js
index 34840c4..6a3de27 100644
--- a/src/components/SignTransactionFormContent.js
+++ b/src/components/SignTransactionFormContent.js
@@ -9,6 +9,7 @@ import { useConnection, useSolanaExplorerUrlSuffix } from '../utils/connection';
import { useWallet, useWalletPublicKeys } from '../utils/wallet';
import NewOrder from './instructions/NewOrder';
import UnknownInstruction from './instructions/UnknownInstruction';
+import StakeInstruction from '../components/instructions/StakeInstruction';
import SystemInstruction from '../components/instructions/SystemInstruction';
import DexInstruction from '../components/instructions/DexInstruction';
import TokenInstruction from '../components/instructions/TokenInstruction';
@@ -215,6 +216,19 @@ export default function SignTransactionFormContent({
onOpenAddress={onOpenAddress}
/>
);
+ case 'stakeAuthorizeWithSeed':
+ case 'stakeAuthorize':
+ case 'stakeDeactivate':
+ case 'stakeDelegate':
+ case 'stakeInitialize':
+ case 'stakeSplit':
+ case 'stakeWithdraw':
+ return (
+
+ );
case 'newOrder':
return (
diff --git a/src/components/instructions/StakeInstruction.js b/src/components/instructions/StakeInstruction.js
new file mode 100644
index 0000000..b04aca2
--- /dev/null
+++ b/src/components/instructions/StakeInstruction.js
@@ -0,0 +1,65 @@
+import React from 'react';
+import Typography from '@material-ui/core/Typography';
+import LabelValue from './LabelValue';
+
+const TYPE_LABELS = {
+ stakeAuthorizeWithSeed: 'Stake authorize with seed',
+ stakeAuthorize: 'Stake authorize',
+ stakeDeactivate: 'Deactivate stake',
+ stakeDelegate: 'Delegate stake',
+ stakeInitialize: 'Initialize stake',
+ stakeSplit: 'Split stake',
+ stakeWithdraw: 'Withdraw stake',
+};
+
+const DATA_LABELS = {
+ stakePubkey: { label: 'Stake', address: true },
+ authorizedStaker: { label: 'Authorized staker', address: true },
+ authorizedWithdrawer: { label: 'Authorized withdrawer', address: true },
+ lockup: { label: 'Lockup', address: false },
+ authorizedPubkey: { label: 'Authorized', address: true },
+ votePubkey: { label: 'Vote', address: true },
+ authorizedSeed: { label: 'Seed', address: false },
+ noncePubkey: { label: 'Nonce', address: true },
+ authorityBase: { label: 'Authority base', address: true },
+ authoritySeed: { label: 'Authority seed', address: false },
+ authorityOwner: { label: 'Authority owner', address: true },
+ newAuthorizedPubkey: { label: 'New authorized', address: true },
+ stakeAuthorizationType: { label: 'Stake authorization type', address: false, transform: () => JSON.stringify },
+ custodianPubkey: { label: 'Custodian', address: true },
+ splitStakePubkey: { label: 'Split to', address: true },
+ lamports: { label: 'Lamports', address: false },
+};
+
+export default function StakeInstruction({ instruction, onOpenAddress }) {
+ const { type, data } = instruction;
+
+ return (
+ <>
+
+ {TYPE_LABELS[type]}
+
+ {data &&
+ Object.entries(data).map(([key, value]) => {
+ const dataLabel = DATA_LABELS[key];
+ if (!dataLabel) {
+ return null;
+ }
+ const { label, address, transform } = dataLabel;
+ return (
+ address && onOpenAddress(value?.toBase58())}
+ />
+ );
+ })}
+ >
+ );
+}
diff --git a/src/utils/transactions.js b/src/utils/transactions.js
index 62f23c3..1130201 100644
--- a/src/utils/transactions.js
+++ b/src/utils/transactions.js
@@ -1,5 +1,5 @@
import bs58 from 'bs58';
-import { Message, SystemInstruction, SystemProgram } from '@solana/web3.js';
+import { Message, StakeInstruction, StakeProgram, SystemInstruction, SystemProgram } from '@solana/web3.js';
import {
decodeInstruction,
decodeTokenInstructionData,
@@ -96,6 +96,9 @@ const toInstruction = async (
if (programId.equals(SystemProgram.programId)) {
console.log('[' + index + '] Handled as system instruction');
return handleSystemInstruction(publicKey, instruction, accountKeys);
+ } else if (programId.equals(StakeProgram.programId)) {
+ console.log('[' + index + '] Handled as stake instruction');
+ return handleStakeInstruction(publicKey, instruction, accountKeys);
} else if (programId.equals(TOKEN_PROGRAM_ID)) {
console.log('[' + index + '] Handled as token instruction');
let decodedInstruction = decodeTokenInstruction(decoded);
@@ -155,7 +158,9 @@ const toInstruction = async (
programId,
};
}
- } catch {}
+ } catch (e) {
+ console.log(`Failed to decode instruction: ${e}`);
+ }
// all decodings failed
console.log('[' + index + '] Failed, data: ' + JSON.stringify(decoded));
@@ -380,6 +385,74 @@ const handleSystemInstruction = (publicKey, instruction, accountKeys) => {
};
};
+const handleStakeInstruction = (publicKey, instruction, accountKeys) => {
+ const { programIdIndex, accounts, data } = instruction;
+ if (!programIdIndex || !accounts || !data) {
+ return;
+ }
+
+ // construct stake instruction
+ const stakeInstruction = {
+ programId: accountKeys[programIdIndex],
+ keys: accounts.map((accountIndex) => ({
+ pubkey: accountKeys[accountIndex],
+ })),
+ data: bs58.decode(data),
+ };
+
+ let decoded;
+ const type = StakeInstruction.decodeInstructionType(stakeInstruction);
+ switch (type) {
+ case 'AuthorizeWithSeed':
+ decoded = StakeInstruction.decodeAuthorizeWithSeed(stakeInstruction);
+ break;
+ case 'Authorize':
+ decoded = StakeInstruction.decodeAuthorize(stakeInstruction);
+ break;
+ case 'Deactivate':
+ decoded = StakeInstruction.decodeDeactivate(stakeInstruction);
+ break;
+ case 'Delegate':
+ decoded = StakeInstruction.decodeDelegate(stakeInstruction);
+ break;
+ case 'Initialize':
+ decoded = StakeInstruction.decodeInitialize(stakeInstruction);
+ // Lockup inactive if all zeroes
+ const lockup = decoded.lockup;
+ if (lockup && lockup.unixTimestamp === 0 && lockup.epoch === 0 && lockup.custodian.equals(PublicKey.default)) {
+ decoded.lockup = 'Inactive';
+ }
+ else {
+ decoded.lockup = `unixTimestamp: ${lockup.unixTimestamp}, custodian: ${lockup.epoch}, custodian: ${lockup.custodian.toBase58()}`;
+ }
+ // flatten authorized to allow address render
+ decoded.authorizedStaker = decoded.authorized.staker
+ decoded.authorizedWithdrawer = decoded.authorized.withdrawer
+ delete decoded.authorized
+ break;
+ case 'Split':
+ decoded = StakeInstruction.decodeSplit(stakeInstruction);
+ break;
+ case 'Withdraw':
+ decoded = StakeInstruction.decodeWithdraw(stakeInstruction);
+ break;
+ default:
+ return;
+ }
+
+ if (
+ !decoded ||
+ (decoded.fromPubkey && !publicKey.equals(decoded.fromPubkey))
+ ) {
+ return;
+ }
+
+ return {
+ type: 'stake' + type,
+ data: decoded,
+ };
+};
+
const handleTokenInstruction = (
publicKey,
accounts,