Skip to content

Commit

Permalink
feat(sdk-coin-sol): add tx builder for delegate and deactivate
Browse files Browse the repository at this point in the history
support delegate and multi delegate
enhance deactivate to support multi deactivate

TICKET: EA-1479
  • Loading branch information
noel-bitgo committed Sep 11, 2023
1 parent 674c72e commit 461b0b6
Show file tree
Hide file tree
Showing 15 changed files with 525 additions and 49 deletions.
6 changes: 6 additions & 0 deletions modules/sdk-coin-sol/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export enum InstructionBuilderTypes {
CreateAssociatedTokenAccount = 'CreateAssociatedTokenAccount',
TokenTransfer = 'TokenTransfer',
StakingAuthorize = 'Authorize',
StakingDelegate = 'Delegate',
}

export const VALID_SYSTEM_INSTRUCTION_TYPES: ValidInstructionTypes[] = [
Expand Down Expand Up @@ -79,6 +80,11 @@ export const stakingAuthorizeInstructionsIndexes = {
Authorize: 0,
} as const;

/** Const to check the order of the Staking Authorize instructions when decode */
export const stakingDelegateInstructionsIndexes = {
Delegate: 0,
} as const;

/** Const to check the order of the Staking Deactivate instructions when decode */
export const stakingDeactivateInstructionsIndexes = {
Deactivate: 0,
Expand Down
15 changes: 14 additions & 1 deletion modules/sdk-coin-sol/src/lib/iface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ export type InstructionParams =
| StakingWithdraw
| AtaInit
| TokenTransfer
| StakingAuthorize;
| StakingAuthorize
| StakingDelegate;

export interface Memo {
type: InstructionBuilderTypes.Memo;
Expand Down Expand Up @@ -76,6 +77,11 @@ export interface StakingActivate {
params: { fromAddress: string; stakingAddress: string; amount: string; validator: string };
}

export interface StakingDelegate {
type: InstructionBuilderTypes.StakingDelegate;
params: { stakingAddress: string; fromAddress: string; validator: string };
}

export interface StakingDeactivate {
type: InstructionBuilderTypes.StakingDeactivate;
params: { fromAddress: string; stakingAddress: string; amount?: string; unstakingAddress?: string };
Expand Down Expand Up @@ -116,13 +122,20 @@ export type StakingAuthorizeParams = {
custodianAddress?: string;
};

export type StakingDelegateParams = {
stakingAddress: string;
fromAddress: string;
validator: string;
};

export interface TransactionExplanation extends BaseTransactionExplanation {
type: string;
blockhash: Blockhash;
// only populated if blockhash is from a nonce account
durableNonce?: DurableNonceParams;
memo?: string;
stakingAuthorize?: StakingAuthorizeParams;
stakingDelegate?: StakingDelegateParams;
}

export class TokenAssociateRecipient {
Expand Down
1 change: 1 addition & 0 deletions modules/sdk-coin-sol/src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ export { StakingActivateBuilder } from './stakingActivateBuilder';
export { StakingDeactivateBuilder } from './stakingDeactivateBuilder';
export { StakingWithdrawBuilder } from './stakingWithdrawBuilder';
export { StakingAuthorizeBuilder } from './stakingAuthorizeBuilder';
export { StakingDelegateBuilder } from './stakingDelegateBuilder';
export { StakingRawMsgAuthorizeBuilder } from './stakingRawMsgAuthorizeBuilder';
export { Utils, Interface };
121 changes: 102 additions & 19 deletions modules/sdk-coin-sol/src/lib/instructionParamsFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
StakingWithdraw,
TokenTransfer,
StakingAuthorize,
StakingDelegate,
} from './iface';
import { getInstructionType } from './utils';
import assert from 'assert';
Expand Down Expand Up @@ -61,6 +62,8 @@ export function instructionParamsFactory(
return parseStakingAuthorizeInstructions(instructions);
case TransactionType.StakingAuthorizeRaw:
return parseStakingAuthorizeRawInstructions(instructions);
case TransactionType.StakingDelegate:
return parseStakingDelegateInstructions(instructions);
default:
throw new NotSupported('Invalid transaction, transaction type not supported: ' + type);
}
Expand Down Expand Up @@ -239,6 +242,46 @@ function parseStakingActivateInstructions(

return instructionData;
}
/**
* Parses Solana instructions to create delegate tx
* Only supports Nonce, StakingDelegate
*
* @param {TransactionInstruction[]} instructions - an array of supported Solana instructions
* @returns {InstructionParams[]} An array containing instruction params for staking delegate tx
*/
function parseStakingDelegateInstructions(instructions: TransactionInstruction[]): Array<Nonce | StakingDelegate> {
const instructionData: Array<Nonce | StakingDelegate> = [];
for (const instruction of instructions) {
const type = getInstructionType(instruction);
switch (type) {
case ValidInstructionTypesEnum.AdvanceNonceAccount:
const advanceNonceInstruction = SystemInstruction.decodeNonceAdvance(instruction);
const nonce: Nonce = {
type: InstructionBuilderTypes.NonceAdvance,
params: {
walletNonceAddress: advanceNonceInstruction.noncePubkey.toString(),
authWalletAddress: advanceNonceInstruction.authorizedPubkey.toString(),
},
};
instructionData.push(nonce);
break;

case ValidInstructionTypesEnum.StakingDelegate:
const stakingDelegateParams = StakeInstruction.decodeDelegate(instruction);
const stakingDelegate: StakingDelegate = {
type: InstructionBuilderTypes.StakingDelegate,
params: {
fromAddress: stakingDelegateParams.authorizedPubkey.toString() || '',
stakingAddress: stakingDelegateParams.stakePubkey.toString() || '',
validator: stakingDelegateParams.votePubkey.toString() || '',
},
};
instructionData.push(stakingDelegate);
break;
}
}
return instructionData;
}

interface StakingInstructions {
create?: CreateAccountParams;
Expand Down Expand Up @@ -275,7 +318,7 @@ function parseStakingDeactivateInstructions(
instructions: TransactionInstruction[]
): Array<Nonce | StakingDeactivate | Memo> {
const instructionData: Array<Nonce | StakingDeactivate | Memo> = [];
const unstakingInstructions = {} as UnstakingInstructions;
const unstakingInstructions: UnstakingInstructions[] = [];
for (const instruction of instructions) {
const type = getInstructionType(instruction);
switch (type) {
Expand All @@ -300,37 +343,77 @@ function parseStakingDeactivateInstructions(
break;

case ValidInstructionTypesEnum.Allocate:
unstakingInstructions.allocate = SystemInstruction.decodeAllocate(instruction);
if (
unstakingInstructions.length > 0 &&
unstakingInstructions[unstakingInstructions.length - 1].allocate === undefined
) {
unstakingInstructions[unstakingInstructions.length - 1].allocate =
SystemInstruction.decodeAllocate(instruction);
} else {
unstakingInstructions.push({
allocate: SystemInstruction.decodeAllocate(instruction),
});
}
break;

case ValidInstructionTypesEnum.Assign:
unstakingInstructions.assign = SystemInstruction.decodeAssign(instruction);
if (
unstakingInstructions.length > 0 &&
unstakingInstructions[unstakingInstructions.length - 1].assign === undefined
) {
unstakingInstructions[unstakingInstructions.length - 1].assign = SystemInstruction.decodeAssign(instruction);
} else {
unstakingInstructions.push({
assign: SystemInstruction.decodeAssign(instruction),
});
}
break;

case ValidInstructionTypesEnum.Split:
unstakingInstructions.split = StakeInstruction.decodeSplit(instruction);
if (
unstakingInstructions.length > 0 &&
unstakingInstructions[unstakingInstructions.length - 1].split === undefined
) {
unstakingInstructions[unstakingInstructions.length - 1].split = StakeInstruction.decodeSplit(instruction);
} else {
unstakingInstructions.push({
split: StakeInstruction.decodeSplit(instruction),
});
}
break;

case ValidInstructionTypesEnum.StakingDeactivate:
unstakingInstructions.deactivate = StakeInstruction.decodeDeactivate(instruction);
if (
unstakingInstructions.length > 0 &&
unstakingInstructions[unstakingInstructions.length - 1].deactivate === undefined
) {
unstakingInstructions[unstakingInstructions.length - 1].deactivate =
StakeInstruction.decodeDeactivate(instruction);
} else {
unstakingInstructions.push({
deactivate: StakeInstruction.decodeDeactivate(instruction),
});
}
break;
}
}

validateUnstakingInstructions(unstakingInstructions);
const stakingDeactivate: StakingDeactivate = {
type: InstructionBuilderTypes.StakingDeactivate,
params: {
fromAddress: unstakingInstructions.deactivate?.authorizedPubkey.toString() || '',
stakingAddress:
unstakingInstructions.split?.stakePubkey.toString() ||
unstakingInstructions.deactivate?.stakePubkey.toString() ||
'',
amount: unstakingInstructions.split?.lamports.toString(),
unstakingAddress: unstakingInstructions.split?.splitStakePubkey.toString(),
},
};
instructionData.push(stakingDeactivate);
for (const unstakingInstruction of unstakingInstructions) {
validateUnstakingInstructions(unstakingInstruction);
const stakingDeactivate: StakingDeactivate = {
type: InstructionBuilderTypes.StakingDeactivate,
params: {
fromAddress: unstakingInstruction.deactivate?.authorizedPubkey.toString() || '',
stakingAddress:
unstakingInstruction.split?.stakePubkey.toString() ||
unstakingInstruction.deactivate?.stakePubkey.toString() ||
'',
amount: unstakingInstruction.split?.lamports.toString(),
unstakingAddress: unstakingInstruction.split?.splitStakePubkey.toString(),
},
};
instructionData.push(stakingDeactivate);
}

return instructionData;
}
Expand Down
27 changes: 27 additions & 0 deletions modules/sdk-coin-sol/src/lib/solInstructionFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
StakingActivate,
StakingAuthorize,
StakingDeactivate,
StakingDelegate,
StakingWithdraw,
TokenTransfer,
Transfer,
Expand Down Expand Up @@ -55,6 +56,8 @@ export function solInstructionFactory(instructionToBuild: InstructionParams): Tr
return createATAInstruction(instructionToBuild);
case InstructionBuilderTypes.StakingAuthorize:
return stakingAuthorizeInstruction(instructionToBuild);
case InstructionBuilderTypes.StakingDelegate:
return stakingDelegateInstruction(instructionToBuild);
default:
throw new Error(`Invalid instruction type or not supported`);
}
Expand Down Expand Up @@ -347,3 +350,27 @@ function stakingAuthorizeInstruction(data: StakingAuthorize): TransactionInstruc

return tx.instructions;
}

/**
* Construct Delegate Solana instructions
*
* @param {StakingActivate} data - the data to build the instruction
* @returns {TransactionInstruction[]} An array containing Delegate Solana instructions
*/
function stakingDelegateInstruction(data: StakingDelegate): TransactionInstruction[] {
const {
params: { fromAddress, stakingAddress, validator },
} = data;
assert(fromAddress, 'Missing fromAddress param');
assert(stakingAddress, 'Missing stakingAddress param');
assert(validator, 'Missing validator param');
const tx = new Transaction();
const delegateStaking = StakeProgram.delegate({
stakePubkey: new PublicKey(stakingAddress),
authorizedPubkey: new PublicKey(fromAddress),
votePubkey: new PublicKey(validator),
});
tx.add(delegateStaking);

return tx.instructions;
}
4 changes: 2 additions & 2 deletions modules/sdk-coin-sol/src/lib/stakingActivateBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export class StakingActivateBuilder extends TransactionBuilder {
* The address of the staking account.
*
* @param {string} stakingAddress public address of the staking account.
* @returns {StakeBuilder} This staking builder.
* @returns {StakingActivateBuilder} This staking builder.
*
* @see https://docs.solana.com/staking/stake-accounts#account-address
*/
Expand All @@ -68,7 +68,7 @@ export class StakingActivateBuilder extends TransactionBuilder {
* Set validator address to delegate funds to.
*
* @param {string} validator Validator address to delegate funds to.
* @returns {StakeBuilder} This staking builder.
* @returns {StakingActivateBuilder} This staking builder.
*
*/
validator(validator: string): this {
Expand Down
Loading

0 comments on commit 461b0b6

Please sign in to comment.