Skip to content

Commit

Permalink
[wip]: Introduce InstructionPlan helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
lorisleiva committed Jan 22, 2025
1 parent 809817c commit cd37f25
Show file tree
Hide file tree
Showing 4 changed files with 259 additions and 97 deletions.
133 changes: 82 additions & 51 deletions clients/js/src/createMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import {
getTransferSolInstruction,
SYSTEM_PROGRAM_ADDRESS,
} from '@solana-program/system';
import { IInstruction, Lamports } from '@solana/web3.js';
import { getTransferSolInstruction } from '@solana-program/system';
import { Lamports } from '@solana/web3.js';
import {
getAllocateInstruction,
getInitializeInstruction,
Expand All @@ -11,8 +8,9 @@ import {
} from './generated';
import {
getPdaDetails,
InstructionPlan,
PdaDetails,
sendInstructionsInSequentialTransactions,
sendInstructionPlan,
} from './internals';
import { getAccountSize, MetadataInput } from './utils';

Expand All @@ -31,8 +29,8 @@ export async function createMetadata(input: MetadataInput) {
? { use: 'buffer', extractLastTransaction: false }
: { use: 'instructionData' };
const extendedInput = { rent, strategy, ...input, ...pdaDetails };
const instructions = getCreateMetadataInstructions(extendedInput);
await sendInstructionsInSequentialTransactions({ instructions, ...input });
const plan = getCreateMetadataInstructions(extendedInput);
await sendInstructionPlan(plan);
return extendedInput.metadata;
}

Expand All @@ -49,61 +47,94 @@ export function getCreateMetadataInstructions(
rent: Lamports;
strategy: CreateMetadataStrategy;
}
) {
const instructions: IInstruction[][] = [];
let currentInstructionBatch: IInstruction[] = [];
currentInstructionBatch.push(
getTransferSolInstruction({
source: input.payer,
destination: input.metadata,
amount: input.rent,
})
);
): InstructionPlan {
return input.strategy.use === 'instructionData'
? getCreateMetadataInstructionsUsingInstructionData(input)
: getCreateMetadataInstructionsUsingBuffer({
...input,
chunkSize: WRITE_CHUNK_SIZE,
closeBuffer: false,
});
}

export function getCreateMetadataInstructionsUsingInstructionData(
input: Omit<MetadataInput, 'rpc' | 'rpcSubscriptions'> &
PdaDetails & {
rent: Lamports;
}
): InstructionPlan {
return {
kind: 'message',
instructions: [
getTransferSolInstruction({
source: input.payer,
destination: input.metadata,
amount: input.rent,
}),
getInitializeInstruction({
...input,
programData: input.isCanonical ? input.programData : undefined,
}),
],
};
}

if (input.strategy.use === 'buffer') {
currentInstructionBatch.push(
export function getCreateMetadataInstructionsUsingBuffer(
input: Omit<MetadataInput, 'rpc' | 'rpcSubscriptions'> &
PdaDetails & {
rent: Lamports;
chunkSize: number;
closeBuffer: boolean; // TODO: use this.
}
): InstructionPlan {
const mainPlan: InstructionPlan = { kind: 'sequential', plans: [] };

mainPlan.plans.push({
kind: 'message',
instructions: [
getTransferSolInstruction({
source: input.payer,
destination: input.metadata,
amount: input.rent,
}),
getAllocateInstruction({
buffer: input.metadata,
authority: input.authority,
program: input.program,
programData: input.isCanonical ? input.programData : undefined,
seed: input.seed,
})
);
instructions.push(currentInstructionBatch);
currentInstructionBatch = [];
let offset = 0;
while (offset < input.data.length) {
instructions.push([
}),
// TODO: Extend buffer account.
],
});

let offset = 0;
const writePlan: InstructionPlan = { kind: 'parallel', plans: [] };
while (offset < input.data.length) {
writePlan.plans.push({
kind: 'message',
instructions: [
getWriteInstruction({
buffer: input.metadata,
authority: input.authority,
data: input.data.slice(offset, offset + WRITE_CHUNK_SIZE),
data: input.data.slice(offset, offset + input.chunkSize),
}),
]);
offset += WRITE_CHUNK_SIZE;
}
],
});
offset += input.chunkSize;
}
mainPlan.plans.push(writePlan);

currentInstructionBatch.push(
getInitializeInstruction({
metadata: input.metadata,
authority: input.authority,
program: input.program,
programData: input.isCanonical ? input.programData : undefined,
seed: input.seed,
encoding: input.encoding,
compression: input.compression,
format: input.format,
dataSource: input.dataSource,
data: input.strategy.use === 'buffer' ? null : input.data,
system:
input.strategy.use === 'buffer'
? PROGRAM_METADATA_PROGRAM_ADDRESS
: SYSTEM_PROGRAM_ADDRESS,
})
);
mainPlan.plans.push({
kind: 'message',
instructions: [
getInitializeInstruction({
...input,
programData: input.isCanonical ? input.programData : undefined,
system: PROGRAM_METADATA_PROGRAM_ADDRESS,
}),
],
});

instructions.push(currentInstructionBatch);
return instructions;
return mainPlan;
}
70 changes: 70 additions & 0 deletions clients/js/src/internals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,73 @@ async function signAndSendTransaction(
const tx = await signTransactionMessageWithSigners(transactionMessage);
await sendAndConfirm(tx, { commitment });
}

export type InstructionPlan =
| { kind: 'sequential'; plans: InstructionPlan[] }
| { kind: 'parallel'; plans: InstructionPlan[] }
| { kind: 'message'; instructions: IInstruction[] };

export async function sendInstructionPlan(
plan: InstructionPlan,
createMessage: () => Promise<
CompilableTransactionMessage & TransactionMessageWithBlockhashLifetime
>,
sendAndConfirm: ReturnType<typeof sendAndConfirmTransactionFactory>
) {
switch (plan.kind) {
case 'sequential':
return await sendSequentialInstructionPlan(
plan,
createMessage,
sendAndConfirm
);
case 'parallel':
return await sendParallelInstructionPlan(
plan,
createMessage,
sendAndConfirm
);
case 'message':
return await sendMessageInstructionPlan(
plan,
createMessage,
sendAndConfirm
);
default:
throw new Error('Unsupported instruction plan');
}
}

async function sendSequentialInstructionPlan(
plan: InstructionPlan & { kind: 'sequential' },
createMessage: Parameters<typeof sendInstructionPlan>[1],
sendAndConfirm: Parameters<typeof sendInstructionPlan>[2]
) {
for (const subPlan of plan.plans) {
await sendInstructionPlan(subPlan, createMessage, sendAndConfirm);
}
}

async function sendParallelInstructionPlan(
plan: InstructionPlan & { kind: 'parallel' },
createMessage: Parameters<typeof sendInstructionPlan>[1],
sendAndConfirm: Parameters<typeof sendInstructionPlan>[2]
) {
await Promise.all(
plan.plans.map((subPlan) =>
sendInstructionPlan(subPlan, createMessage, sendAndConfirm)
)
);
}

async function sendMessageInstructionPlan(
plan: InstructionPlan & { kind: 'message' },
createMessage: Parameters<typeof sendInstructionPlan>[1],
sendAndConfirm: Parameters<typeof sendInstructionPlan>[2]
) {
await pipe(
await createMessage(),
(tx) => appendTransactionMessageInstructions(plan.instructions, tx),
(tx) => signAndSendTransaction(tx, sendAndConfirm)
);
}
Loading

0 comments on commit cd37f25

Please sign in to comment.