Skip to content

feat(jito): implements staking deactivate #6664

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions examples/ts/sol/stake-jito.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,14 @@ async function main() {
.sender(account.publicKey.toBase58())
.stakingAddress(JITO_STAKE_POOL_ADDRESS)
.validator(JITO_STAKE_POOL_ADDRESS)
.isJito(true)
.stakingTypeParams({
type: 'JITO',
stakePoolData: {
managerFeeAccount: stakePoolAccount.account.data.managerFeeAccount.toString(),
poolMint: stakePoolAccount.account.data.poolMint.toString(),
reserveStake: stakePoolAccount.account.data.toString(),
}
})
.nonce(recentBlockhash.blockhash)
txBuilder.sign({ key: account.secretKey })
const tx = await txBuilder.build()
Expand All @@ -85,7 +92,7 @@ const getAccount = () => {
const secretKey = process.env.ACCOUNT_SECRET_KEY
if (publicKey === undefined || secretKey === undefined) {
const { publicKey, secretKey } = Keypair.generate()
console.log('Here is a new account to save into your .env file.')
console.log('# Here is a new account to save into your .env file.')
console.log(`ACCOUNT_PUBLIC_KEY=${publicKey.toBase58()}`)
console.log(`ACCOUNT_SECRET_KEY=${bs58.encode(secretKey)}`)
throw new Error("Missing account information")
Expand Down
13 changes: 13 additions & 0 deletions modules/sdk-coin-sol/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export enum ValidInstructionTypesEnum {
MintTo = 'MintTo',
Burn = 'Burn',
DepositSol = 'DepositSol',
WithdrawStake = 'WithdrawStake',
Approve = 'Approve',
}

// Internal instructions types
Expand All @@ -58,6 +60,8 @@ export enum InstructionBuilderTypes {
MintTo = 'MintTo',
Burn = 'Burn',
CustomInstruction = 'CustomInstruction',
Approve = 'Approve',
WithdrawStake = 'WithdrawStake',
}

export const VALID_SYSTEM_INSTRUCTION_TYPES: ValidInstructionTypes[] = [
Expand All @@ -80,7 +84,9 @@ export const VALID_SYSTEM_INSTRUCTION_TYPES: ValidInstructionTypes[] = [
ValidInstructionTypesEnum.SetPriorityFee,
ValidInstructionTypesEnum.MintTo,
ValidInstructionTypesEnum.Burn,
ValidInstructionTypesEnum.Approve,
ValidInstructionTypesEnum.DepositSol,
ValidInstructionTypesEnum.WithdrawStake,
];

/** Const to check the order of the Wallet Init instructions when decode */
Expand Down Expand Up @@ -111,6 +117,13 @@ export const jitoStakingActivateInstructionsIndexes = {
DepositSol: 1,
} as const;

/** Const to check the order of the Jito Staking Activate instructions when decode */
export const jitoStakingDeactivateInstructionsIndexes = {
Approve: 0,
Create: 1,
WithdrawStake: 2,
} as const;

/** Const to check the order of the Staking Authorize instructions when decode */
export const stakingAuthorizeInstructionsIndexes = {
Authorize: 0,
Expand Down
43 changes: 39 additions & 4 deletions modules/sdk-coin-sol/src/lib/iface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { DecodedCloseAccountInstruction } from '@solana/spl-token';
import { Blockhash, StakeInstructionType, SystemInstructionType, TransactionSignature } from '@solana/web3.js';
import { InstructionBuilderTypes } from './constants';
import { StakePoolInstructionType } from '@solana/spl-stake-pool';
import { DepositSolStakePoolData, WithdrawStakeStakePoolData } from './jitoStakePoolOperations';

// TODO(STLX-9890): Add the interfaces for validityWindow and SequenceId
export interface SolanaKeys {
Expand Down Expand Up @@ -41,6 +42,7 @@ export type InstructionParams =
| StakingDelegate
| MintTo
| Burn
| Approve
| CustomInstruction;

export interface Memo {
Expand Down Expand Up @@ -107,15 +109,38 @@ export interface Burn {
};
}

export interface Approve {
type: InstructionBuilderTypes.Approve;
params: {
accountAddress: string;
delegateAddress: string;
ownerAddress: string;
amount: string;
programId?: string;
};
}

export enum StakingType {
NATIVE = 'NATIVE',
MARINADE = 'MARINADE',
JITO = 'JITO',
}

export interface JitoStakingActivateParams {
stakePoolData: DepositSolStakePoolData;
}

export type StakingActivateExtraParams = JitoStakingActivateParams;

export interface StakingActivate {
type: InstructionBuilderTypes.StakingActivate;
params: {
fromAddress: string;
stakingAddress: string;
amount: string;
validator: string;
isMarinade?: boolean;
isJito?: boolean;
stakingType: StakingType;
extraParams?: StakingActivateExtraParams;
};
}

Expand All @@ -124,14 +149,23 @@ export interface StakingDelegate {
params: { stakingAddress: string; fromAddress: string; validator: string };
}

export interface JitoStakingDeactivateParams {
stakePoolData: WithdrawStakeStakePoolData;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we just expand required field here?

poolMint: string
validatorList: string
managerFeeAccount: string

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also do we need to pass validatorList here? Isn't the caller(staking service) already selecting the validator (validatorAddress below)?

Copy link
Contributor Author

@haritkapadia haritkapadia Aug 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validatorList is the address of the account that stores the validator list data. It's derived from parsing the stake pool account data. We need the public key as part of the withdraw stake instruction.

Edit: and yes, validatorAddress is selected by the caller.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we just expand required field here?

We could, but I think that would make it less clear that those fields come from parsing the stake pool account data.

With the current setup, you can do something like this:

const account = await connection.getAccountInfo("J1to.....")
const stakePoolData = StakePoolLayout.decode(account.data)
const txBuilder = new StakingDeactivateBulder()
txBuilder.extraParams({
  stakePoolData, // notice we can pass the entire parsed object instead of having to select fields
  validatorAddress: await getValidatorFrom(connection, stakePoolData.validatorList)
})

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we just expand required field here?

If SOL has schema changes of those unused fields, the fields defined here might not be consistent and others might find it confusing in the future. But I am also okay with keeping them. Your call

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validatorList is the address of the account that stores the validator list data

If validatorList is an account address, the name looks confusing. Could we have a more accurate name?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can change the validatorList name

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If SOL has schema changes of those unused fields, the fields defined here might not be consistent and others might find it confusing in the future. But I am also okay with keeping them. Your call

If this happens then we'll have to update the instruction encoder/decoder in jitoStakePoolOperations.ts, i.e. the file that exports WithdrawStakeStakePoolData. I think it won't be confusing because it's linked together by the compiler, so I will keep it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed validatorList name

validatorAddress: string;
transferAuthorityAddress: string;
}

export type StakingDeactivateExtraParams = JitoStakingDeactivateParams;

export interface StakingDeactivate {
type: InstructionBuilderTypes.StakingDeactivate;
params: {
fromAddress: string;
stakingAddress: string;
amount?: string;
unstakingAddress?: string;
isMarinade?: boolean;
stakingType: StakingType;
extraParams?: StakingDeactivateExtraParams;
recipients?: Recipient[];
};
}
Expand Down Expand Up @@ -187,7 +221,8 @@ export type ValidInstructionTypes =
| 'TokenTransfer'
| 'SetPriorityFee'
| 'MintTo'
| 'Burn';
| 'Burn'
| 'Approve';

export type StakingAuthorizeParams = {
stakingAddress: string;
Expand Down
Loading
Loading