Skip to content
Draft
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
64 changes: 64 additions & 0 deletions clients/js/src/batch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { AccountMeta, Address, Instruction, InstructionWithData, ReadonlyUint8Array } from '@solana/kit';
import {
BATCH_DISCRIMINATOR,
BatchInstruction as GeneratedBatchInstruction,
getBatchInstruction as generatedGetBatchInstruction,
parseBatchInstruction as generatedParseBatchInstruction,
ParsedBatchInstruction,
ParsedToken2022Instruction,
parseToken2022Instruction,
TOKEN_2022_PROGRAM_ADDRESS,
} from './generated';

declare const nonBatchable: '__non_batchable:@solana-program/token-2022';

type BatchableInstruction<TProgramAddress extends string = string> = Instruction<TProgramAddress> & {
readonly [nonBatchable]?: never;
};

export type BatchInstruction<
TProgram extends string = typeof TOKEN_2022_PROGRAM_ADDRESS,
TRemainingAccounts extends readonly AccountMeta<string>[] = [],
> = GeneratedBatchInstruction<TProgram, TRemainingAccounts> & { readonly [nonBatchable]: true };

export function getBatchInstruction<TProgramAddress extends Address = typeof TOKEN_2022_PROGRAM_ADDRESS>(
instructions: BatchableInstruction<TProgramAddress>[],
config?: { programAddress?: TProgramAddress },
): BatchInstruction<TProgramAddress, AccountMeta<string>[]> {
const programAddress = config?.programAddress ?? TOKEN_2022_PROGRAM_ADDRESS;
const hasNestedBatchInstruction = instructions.some(
instruction => instruction.programAddress === programAddress && instruction.data?.[0] === BATCH_DISCRIMINATOR,
);
if (hasNestedBatchInstruction) {
throw new Error('Batch instructions cannot be nested within other batch instructions.');
}

const accounts = instructions.flatMap(instruction => instruction.accounts ?? []);
const data = instructions.map(instruction => ({
numberOfAccounts: instruction.accounts?.length ?? 0,
instructionData: instruction.data ?? new Uint8Array(),
}));

return Object.freeze({
...generatedGetBatchInstruction<TProgramAddress>({ data }, config),
accounts,
}) as BatchInstruction<TProgramAddress, AccountMeta<string>[]>;
}

export function parseBatchInstruction<TProgram extends string>(
instruction: Instruction<TProgram> & InstructionWithData<ReadonlyUint8Array>,
): ParsedBatchInstruction<TProgram> & { instructions: ParsedToken2022Instruction<TProgram>[] } {
const rawBatchInstruction = generatedParseBatchInstruction(instruction);
let accountOffset = 0;
const instructions = rawBatchInstruction.data.data.map(({ numberOfAccounts, instructionData }) => {
const innerInstruction = parseToken2022Instruction<TProgram>({
programAddress: instruction.programAddress,
data: instructionData,
accounts: instruction.accounts?.slice(accountOffset, accountOffset + numberOfAccounts) ?? [],
});
accountOffset += numberOfAccounts;
return innerInstruction;
});

return { ...rawBatchInstruction, instructions };
}
3 changes: 3 additions & 0 deletions clients/js/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
export * from './generated';

// Generated overrides (must be re-exported explicitly).
export { type BatchInstruction, getBatchInstruction, parseBatchInstruction } from './batch';

export * from './amountToUiAmount';
export * from './getInitializeInstructionsForExtensions';
export * from './getTokenSize';
Expand Down
Loading