Skip to content
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
1 change: 1 addition & 0 deletions modules/sdk-coin-sol/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export enum InstructionBuilderTypes {
SetPriorityFee = 'SetPriorityFee',
MintTo = 'MintTo',
Burn = 'Burn',
CustomInstruction = 'CustomInstruction',
Copy link
Contributor

Choose a reason for hiding this comment

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

Lets remove Burn / minto, we can follow up

}

export const VALID_SYSTEM_INSTRUCTION_TYPES: ValidInstructionTypes[] = [
Expand Down
119 changes: 119 additions & 0 deletions modules/sdk-coin-sol/src/lib/customInstructionBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { BuildTransactionError, TransactionType } from '@bitgo/sdk-core';
import { TransactionInstruction } from '@solana/web3.js';
import { Transaction } from './transaction';
import { TransactionBuilder } from './transactionBuilder';
import { InstructionBuilderTypes } from './constants';
import { CustomInstruction } from './iface';
import assert from 'assert';

/**
* Transaction builder for custom Solana instructions.
* Allows building transactions with any set of raw Solana instructions.
*/
export class CustomInstructionBuilder extends TransactionBuilder {
private _customInstructions: CustomInstruction[] = [];

constructor(_coinConfig: Readonly<CoinConfig>) {
super(_coinConfig);
}

protected get transactionType(): TransactionType {
return TransactionType.Send;
}

/**
* Initialize the builder from an existing transaction
*/
initBuilder(tx: Transaction): void {
super.initBuilder(tx);

for (const instruction of this._instructionsData) {
if (instruction.type === InstructionBuilderTypes.CustomInstruction) {
const customInstruction = instruction as CustomInstruction;
this.addCustomInstruction(customInstruction.params.instruction);
}
}
}

/**
* Add a custom Solana instruction to the transaction
*
* @param instruction - The raw Solana TransactionInstruction
* @returns This transaction builder
*/
addCustomInstruction(instruction: TransactionInstruction): this {
if (!instruction) {
throw new BuildTransactionError('Instruction cannot be null or undefined');
}

if (!instruction.programId) {
throw new BuildTransactionError('Instruction must have a valid programId');
}

if (!instruction.keys || !Array.isArray(instruction.keys)) {
throw new BuildTransactionError('Instruction must have valid keys array');
}

if (!instruction.data || !Buffer.isBuffer(instruction.data)) {
throw new BuildTransactionError('Instruction must have valid data buffer');
}

const customInstruction: CustomInstruction = {
type: InstructionBuilderTypes.CustomInstruction,
params: {
instruction,
},
};

this._customInstructions.push(customInstruction);
return this;
}

/**
* Add multiple custom Solana instructions to the transaction
*
* @param instructions - Array of raw Solana TransactionInstructions
* @returns This transaction builder
*/
addCustomInstructions(instructions: TransactionInstruction[]): this {
if (!Array.isArray(instructions)) {
throw new BuildTransactionError('Instructions must be an array');
}

for (const instruction of instructions) {
this.addCustomInstruction(instruction);
}

return this;
}

/**
* Clear all custom instructions
*
* @returns This transaction builder
*/
clearInstructions(): this {
this._customInstructions = [];
return this;
}

/**
* Get the current custom instructions
*
* @returns Array of custom instructions
*/
getInstructions(): CustomInstruction[] {
return [...this._customInstructions];
}

/** @inheritdoc */
protected async buildImplementation(): Promise<Transaction> {
assert(this._customInstructions.length > 0, 'At least one custom instruction must be specified');

// Set the instructions data to our custom instructions
this._instructionsData = [...this._customInstructions];

return await super.buildImplementation();
}
}
18 changes: 16 additions & 2 deletions modules/sdk-coin-sol/src/lib/iface.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { TransactionExplanation as BaseTransactionExplanation, Recipient } from '@bitgo/sdk-core';
import { DecodedCloseAccountInstruction } from '@solana/spl-token';
import { Blockhash, StakeInstructionType, SystemInstructionType, TransactionSignature } from '@solana/web3.js';
import {
Blockhash,
StakeInstructionType,
SystemInstructionType,
TransactionInstruction,
TransactionSignature,
} from '@solana/web3.js';
import { InstructionBuilderTypes } from './constants';

// TODO(STLX-9890): Add the interfaces for validityWindow and SequenceId
Expand Down Expand Up @@ -40,7 +46,8 @@ export type InstructionParams =
| StakingAuthorize
| StakingDelegate
| MintTo
| Burn;
| Burn
| CustomInstruction;

export interface Memo {
type: InstructionBuilderTypes.Memo;
Expand Down Expand Up @@ -201,6 +208,13 @@ export type StakingDelegateParams = {
validator: string;
};

export interface CustomInstruction {
type: InstructionBuilderTypes.CustomInstruction;
params: {
instruction: TransactionInstruction;
};
}

export interface TransactionExplanation extends BaseTransactionExplanation {
type: string;
blockhash: Blockhash;
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 @@ -3,6 +3,7 @@ import * as Utils from './utils';

export { AtaInitializationBuilder } from './ataInitializationBuilder';
export { CloseAtaBuilder } from './closeAtaBuilder';
export { CustomInstructionBuilder } from './customInstructionBuilder';
export { KeyPair } from './keyPair';
export { StakingActivateBuilder } from './stakingActivateBuilder';
export { StakingAuthorizeBuilder } from './stakingAuthorizeBuilder';
Expand Down
17 changes: 17 additions & 0 deletions modules/sdk-coin-sol/src/lib/solInstructionFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
Transfer,
WalletInit,
SetPriorityFee,
CustomInstruction,
} from './iface';
import { getSolTokenFromTokenName } from './utils';

Expand Down Expand Up @@ -79,6 +80,8 @@ export function solInstructionFactory(instructionToBuild: InstructionParams): Tr
return mintToInstruction(instructionToBuild);
case InstructionBuilderTypes.Burn:
return burnInstruction(instructionToBuild);
case InstructionBuilderTypes.CustomInstruction:
return customInstruction(instructionToBuild);
default:
throw new Error(`Invalid instruction type or not supported`);
}
Expand Down Expand Up @@ -546,3 +549,17 @@ function burnInstruction(data: Burn): TransactionInstruction[] {

return [burnInstr];
}

/**
* Process custom instruction - simply returns the raw instruction
*
* @param {CustomInstruction} data - the data containing the custom instruction
* @returns {TransactionInstruction[]} An array containing the custom instruction
*/
function customInstruction(data: InstructionParams): TransactionInstruction[] {
const {
params: { instruction },
} = data as CustomInstruction;
assert(instruction, 'Missing instruction param');
return [instruction];
}
8 changes: 8 additions & 0 deletions modules/sdk-coin-sol/src/lib/transactionBuilderFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { BaseTransactionBuilderFactory, InvalidTransactionError, TransactionType
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { AtaInitializationBuilder } from './ataInitializationBuilder';
import { CloseAtaBuilder } from './closeAtaBuilder';
import { CustomInstructionBuilder } from './customInstructionBuilder';
import { StakingActivateBuilder } from './stakingActivateBuilder';
import { StakingAuthorizeBuilder } from './stakingAuthorizeBuilder';
import { StakingDeactivateBuilder } from './stakingDeactivateBuilder';
Expand Down Expand Up @@ -175,6 +176,13 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
return this.initializeBuilder(tx, new CloseAtaBuilder(this._coinConfig));
}

/**
* Returns the builder to create transactions with custom Solana instructions.
*/
getCustomInstructionBuilder(tx?: Transaction): CustomInstructionBuilder {
return this.initializeBuilder(tx, new CustomInstructionBuilder(this._coinConfig));
}

/**
* Initialize the builder with the given transaction
*
Expand Down
Loading