@solana/instruction-plans 6.3.1 → 6.3.2-canary-20260313112147

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solana/instruction-plans",
3
- "version": "6.3.1",
3
+ "version": "6.3.2-canary-20260313112147",
4
4
  "description": "Construct, plan and execute transactions from multiple instructions.",
5
5
  "homepage": "https://www.solanakit.com/api#solanainstruction-plans",
6
6
  "exports": {
@@ -33,7 +33,8 @@
33
33
  "types": "./dist/types/index.d.ts",
34
34
  "type": "commonjs",
35
35
  "files": [
36
- "./dist/"
36
+ "./dist/",
37
+ "./src/"
37
38
  ],
38
39
  "sideEffects": false,
39
40
  "keywords": [
@@ -55,12 +56,12 @@
55
56
  "maintained node versions"
56
57
  ],
57
58
  "dependencies": {
58
- "@solana/errors": "6.3.1",
59
- "@solana/keys": "6.3.1",
60
- "@solana/instructions": "6.3.1",
61
- "@solana/promises": "6.3.1",
62
- "@solana/transaction-messages": "6.3.1",
63
- "@solana/transactions": "6.3.1"
59
+ "@solana/errors": "6.3.2-canary-20260313112147",
60
+ "@solana/instructions": "6.3.2-canary-20260313112147",
61
+ "@solana/keys": "6.3.2-canary-20260313112147",
62
+ "@solana/promises": "6.3.2-canary-20260313112147",
63
+ "@solana/transaction-messages": "6.3.2-canary-20260313112147",
64
+ "@solana/transactions": "6.3.2-canary-20260313112147"
64
65
  },
65
66
  "peerDependencies": {
66
67
  "typescript": "^5.0.0"
@@ -0,0 +1,90 @@
1
+ import { SOLANA_ERROR__INVARIANT_VIOLATION__INVALID_INSTRUCTION_PLAN_KIND, SolanaError } from '@solana/errors';
2
+ import { type Instruction } from '@solana/instructions';
3
+ import {
4
+ appendTransactionMessageInstruction,
5
+ appendTransactionMessageInstructions,
6
+ TransactionMessage,
7
+ TransactionMessageWithFeePayer,
8
+ } from '@solana/transaction-messages';
9
+
10
+ import { flattenInstructionPlan, InstructionPlan } from './instruction-plan';
11
+
12
+ /**
13
+ * A helper type to append instructions to a transaction message
14
+ * without losing type information about the current instructions.
15
+ */
16
+
17
+ type AppendTransactionMessageInstructions<TTransactionMessage extends TransactionMessage> = ReturnType<
18
+ typeof appendTransactionMessageInstructions<TTransactionMessage, Instruction[]>
19
+ >;
20
+
21
+ /**
22
+ * Appends all instructions from an instruction plan to a transaction message.
23
+ *
24
+ * This function flattens the instruction plan into its leaf plans and sequentially
25
+ * appends each instruction to the provided transaction message. It handles both
26
+ * single instructions and message packer plans.
27
+ *
28
+ * Note that any {@link MessagePackerInstructionPlan} is assumed to only append
29
+ * instructions. If it modifies other properties of the transaction message, the
30
+ * type of the returned transaction message may not accurately reflect those changes.
31
+ *
32
+ * @typeParam TTransactionMessage - The type of transaction message being modified.
33
+ *
34
+ * @param transactionMessage - The transaction message to append instructions to.
35
+ * @param instructionPlan - The instruction plan containing the instructions to append.
36
+ * @returns The transaction message with all instructions from the plan appended.
37
+ *
38
+ * @example
39
+ * Appending a simple instruction plan to a transaction message.
40
+ * ```ts
41
+ * import { appendTransactionMessageInstructionPlan } from '@solana/instruction-plans';
42
+ * import { createTransactionMessage, setTransactionMessageFeePayer } from '@solana/transaction-messages';
43
+ *
44
+ * const message = setTransactionMessageFeePayer(feePayer, createTransactionMessage({ version: 0 }));
45
+ * const plan = singleInstructionPlan(myInstruction);
46
+ *
47
+ * const messageWithInstructions = appendTransactionMessageInstructionPlan(message, plan);
48
+ * ```
49
+ *
50
+ * @example
51
+ * Appending a sequential instruction plan.
52
+ * ```ts
53
+ * const plan = sequentialInstructionPlan([instructionA, instructionB, instructionC]);
54
+ * const messageWithInstructions = appendTransactionMessageInstructionPlan(message, plan);
55
+ * ```
56
+ *
57
+ * @see {@link InstructionPlan}
58
+ * @see {@link flattenInstructionPlan}
59
+ */
60
+ export function appendTransactionMessageInstructionPlan<
61
+ TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer,
62
+ >(
63
+ instructionPlan: InstructionPlan,
64
+ transactionMessage: TTransactionMessage,
65
+ ): AppendTransactionMessageInstructions<TTransactionMessage> {
66
+ type Out = AppendTransactionMessageInstructions<TTransactionMessage>;
67
+
68
+ const leafInstructionPlans = flattenInstructionPlan(instructionPlan);
69
+
70
+ return leafInstructionPlans.reduce(
71
+ (messageSoFar, plan) => {
72
+ const kind = plan.kind;
73
+ if (kind === 'single') {
74
+ return appendTransactionMessageInstruction(plan.instruction, messageSoFar) as unknown as Out;
75
+ }
76
+ if (kind === 'messagePacker') {
77
+ const messagerPacker = plan.getMessagePacker();
78
+ let nextMessage: Out = messageSoFar;
79
+ while (!messagerPacker.done()) {
80
+ nextMessage = messagerPacker.packMessageToCapacity(nextMessage) as Out;
81
+ }
82
+ return nextMessage;
83
+ }
84
+ throw new SolanaError(SOLANA_ERROR__INVARIANT_VIOLATION__INVALID_INSTRUCTION_PLAN_KIND, {
85
+ kind,
86
+ });
87
+ },
88
+ transactionMessage as unknown as Out,
89
+ );
90
+ }
package/src/index.ts ADDED
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Instruction plans describe operations that go beyond a
3
+ * single instruction and may even span multiple transactions.
4
+ *
5
+ * They define a set of instructions that must be executed following a specific order.
6
+ * For instance, imagine we wanted to create an instruction plan for a simple escrow
7
+ * transfer between Alice and Bob. First, both would need to deposit their assets
8
+ * into a vault. This could happen in any order. Then and only then, the vault can be
9
+ * activated to switch the assets. Alice and Bob can now both withdraw each other’s assets
10
+ * (again, in any order). Here’s how we could describe an instruction plan for such an operation.
11
+ *
12
+ * ```typescript
13
+ * const instructionPlan = sequentialInstructionPlan([
14
+ * parallelInstructionPlan([
15
+ * depositFromAlice,
16
+ * depositFromBob,
17
+ * ]),
18
+ * activateVault,
19
+ * parallelInstructionPlan([
20
+ * withdrawToAlice,
21
+ * withdrawToBob,
22
+ * ]),
23
+ * ]);
24
+ * ```
25
+ *
26
+ * As you can see, instruction plans don’t concern themselves with:
27
+ * - Adding structural instructions — e.g. compute budget limits and prices.
28
+ * - Building transaction messages from these instructions.
29
+ * That is planning how many can fit into a single instruction, adding a fee payer, a lifetime, etc.
30
+ * - Compiling, signing and sending transactions to the network.
31
+ *
32
+ * Instead, they solely focus on describing operations and delegate
33
+ * all that to two components introduced in this package:
34
+ * - **Transaction planner**: builds transaction messages from an
35
+ * instruction plan and returns an appropriate transaction plan.
36
+ * - **Transaction plan executor**: compiles, signs and sends
37
+ * transaction plans and returns a detailed result of this operation.
38
+ *
39
+ * ```typescript
40
+ * // Plan instructions into transactions.
41
+ * const transactionPlan = await transactionPlanner(instructionPlan);
42
+ *
43
+ * // Execute transactions.
44
+ * const transactionPlanResult = await transactionPlanExecutor(transactionPlan);
45
+ * ```
46
+ *
47
+ * This separation of concerns not only improves the developer experience but also
48
+ * allows program maintainers to offer helper functions that go beyond a single instruction,
49
+ * while leaving their consumers to decide how they want these operations to materialise.
50
+ *
51
+ * @packageDocumentation
52
+ */
53
+ export * from './append-instruction-plan';
54
+ export * from './instruction-plan';
55
+ export * from './instruction-plan-input';
56
+ export * from './transaction-plan';
57
+ export * from './transaction-plan-errors';
58
+ export * from './transaction-plan-executor';
59
+ export * from './transaction-plan-result';
60
+ export * from './transaction-planner';
@@ -0,0 +1,258 @@
1
+ import type { Instruction } from '@solana/instructions';
2
+
3
+ import {
4
+ type InstructionPlan,
5
+ isInstructionPlan,
6
+ sequentialInstructionPlan,
7
+ singleInstructionPlan,
8
+ } from './instruction-plan';
9
+ import {
10
+ isTransactionPlan,
11
+ sequentialTransactionPlan,
12
+ SingleTransactionPlan,
13
+ singleTransactionPlan,
14
+ TransactionPlan,
15
+ } from './transaction-plan';
16
+
17
+ /**
18
+ * A flexible input type that can be used to create an {@link InstructionPlan}.
19
+ *
20
+ * This type accepts:
21
+ * - A single {@link Instruction}.
22
+ * - An existing {@link InstructionPlan}.
23
+ * - An array of instructions and/or instruction plans.
24
+ *
25
+ * Use the {@link parseInstructionPlanInput} function to convert this input
26
+ * into a proper {@link InstructionPlan}.
27
+ *
28
+ * @example
29
+ * Using a single instruction.
30
+ * ```ts
31
+ * const input: InstructionPlanInput = myInstruction;
32
+ * ```
33
+ *
34
+ * @example
35
+ * Use as argument type in a function that will parse it into an InstructionPlan.
36
+ * ```ts
37
+ * function myFunction(input: InstructionPlanInput) {
38
+ * const plan = parseInstructionPlanInput(input);
39
+ * // Use the plan...
40
+ * }
41
+ * ```
42
+ *
43
+ * @see {@link parseInstructionPlanInput}
44
+ * @see {@link InstructionPlan}
45
+ */
46
+ export type InstructionPlanInput = Instruction | InstructionPlan | readonly (Instruction | InstructionPlan)[];
47
+
48
+ /**
49
+ * Parses an {@link InstructionPlanInput} and returns an {@link InstructionPlan}.
50
+ *
51
+ * This function handles the following input types:
52
+ * - A single {@link Instruction} is wrapped in a {@link SingleInstructionPlan}.
53
+ * - An existing {@link InstructionPlan} is returned as-is.
54
+ * - An array with a single element is unwrapped and parsed recursively.
55
+ * - An array with multiple elements is wrapped in a divisible {@link SequentialInstructionPlan}.
56
+ *
57
+ * @param input - The input to parse into an instruction plan.
58
+ * @return The parsed instruction plan.
59
+ *
60
+ * @example
61
+ * Parsing a single instruction.
62
+ * ```ts
63
+ * const plan = parseInstructionPlanInput(myInstruction);
64
+ * // Equivalent to: singleInstructionPlan(myInstruction)
65
+ * ```
66
+ *
67
+ * @example
68
+ * Parsing an array of instructions.
69
+ * ```ts
70
+ * const plan = parseInstructionPlanInput([instructionA, instructionB]);
71
+ * // Equivalent to: sequentialInstructionPlan([instructionA, instructionB])
72
+ * ```
73
+ *
74
+ * @example
75
+ * Parsing a mixed array with nested plans.
76
+ * ```ts
77
+ * const plan = parseInstructionPlanInput([
78
+ * instructionA,
79
+ * parallelInstructionPlan([instructionB, instructionC]),
80
+ * ]);
81
+ * // Returns a sequential plan containing:
82
+ * // - A single instruction plan for instructionA.
83
+ * // - The parallel plan for instructionB and instructionC.
84
+ * ```
85
+ *
86
+ * @example
87
+ * Single-element arrays are unwrapped.
88
+ * ```ts
89
+ * const plan = parseInstructionPlanInput([myInstruction]);
90
+ * // Equivalent to: singleInstructionPlan(myInstruction)
91
+ * ```
92
+ *
93
+ * @see {@link InstructionPlanInput}
94
+ * @see {@link InstructionPlan}
95
+ */
96
+ export function parseInstructionPlanInput(input: InstructionPlanInput): InstructionPlan {
97
+ if (Array.isArray(input) && input.length === 1) {
98
+ return parseInstructionPlanInput(input[0]);
99
+ }
100
+ if (Array.isArray(input)) {
101
+ return sequentialInstructionPlan(input.map(parseInstructionPlanInput));
102
+ }
103
+ return isInstructionPlan(input) ? input : singleInstructionPlan(input as Instruction);
104
+ }
105
+
106
+ /**
107
+ * A flexible input type that can be used to create a {@link TransactionPlan}.
108
+ *
109
+ * This type accepts:
110
+ * - A single {@link TransactionMessage} with a fee payer.
111
+ * - An existing {@link TransactionPlan}.
112
+ * - An array of transaction messages and/or transaction plans.
113
+ *
114
+ * Use the {@link parseTransactionPlanInput} function to convert this input
115
+ * into a proper {@link TransactionPlan}.
116
+ *
117
+ * @example
118
+ * Using a single transaction message.
119
+ * ```ts
120
+ * const input: TransactionPlanInput = myTransactionMessage;
121
+ * ```
122
+ *
123
+ * @example
124
+ * Use as argument type in a function that will parse it into a TransactionPlan.
125
+ * ```ts
126
+ * function myFunction(input: TransactionPlanInput) {
127
+ * const plan = parseTransactionPlanInput(input);
128
+ * // Use the plan...
129
+ * }
130
+ * ```
131
+ *
132
+ * @see {@link parseTransactionPlanInput}
133
+ * @see {@link TransactionPlan}
134
+ */
135
+ export type TransactionPlanInput =
136
+ | SingleTransactionPlan['message']
137
+ | TransactionPlan
138
+ | readonly (SingleTransactionPlan['message'] | TransactionPlan)[];
139
+
140
+ /**
141
+ * Parses a {@link TransactionPlanInput} and returns a {@link TransactionPlan}.
142
+ *
143
+ * This function handles the following input types:
144
+ * - A single {@link TransactionMessage} is wrapped in a {@link SingleTransactionPlan}.
145
+ * - An existing {@link TransactionPlan} is returned as-is.
146
+ * - An array with a single element is unwrapped and parsed recursively.
147
+ * - An array with multiple elements is wrapped in a divisible {@link SequentialTransactionPlan}.
148
+ *
149
+ * @param input - The input to parse into a transaction plan.
150
+ * @return The parsed transaction plan.
151
+ *
152
+ * @example
153
+ * Parsing a single transaction message.
154
+ * ```ts
155
+ * const plan = parseTransactionPlanInput(myTransactionMessage);
156
+ * // Equivalent to: singleTransactionPlan(myTransactionMessage)
157
+ * ```
158
+ *
159
+ * @example
160
+ * Parsing an array of transaction messages.
161
+ * ```ts
162
+ * const plan = parseTransactionPlanInput([messageA, messageB]);
163
+ * // Equivalent to: sequentialTransactionPlan([messageA, messageB])
164
+ * ```
165
+ *
166
+ * @example
167
+ * Parsing a mixed array with nested plans.
168
+ * ```ts
169
+ * const plan = parseTransactionPlanInput([
170
+ * messageA,
171
+ * parallelTransactionPlan([messageB, messageC]),
172
+ * ]);
173
+ * // Returns a sequential plan containing:
174
+ * // - A single transaction plan for messageA.
175
+ * // - The parallel plan for messageB and messageC.
176
+ * ```
177
+ *
178
+ * @example
179
+ * Single-element arrays are unwrapped.
180
+ * ```ts
181
+ * const plan = parseTransactionPlanInput([myTransactionMessage]);
182
+ * // Equivalent to: singleTransactionPlan(myTransactionMessage)
183
+ * ```
184
+ *
185
+ * @see {@link TransactionPlanInput}
186
+ * @see {@link TransactionPlan}
187
+ */
188
+ export function parseTransactionPlanInput(input: TransactionPlanInput): TransactionPlan {
189
+ if (Array.isArray(input) && input.length === 1) {
190
+ return parseTransactionPlanInput(input[0]);
191
+ }
192
+ if (Array.isArray(input)) {
193
+ return sequentialTransactionPlan(input.map(item => parseTransactionPlanInput(item)));
194
+ }
195
+ return isTransactionPlan(input) ? input : singleTransactionPlan(input as SingleTransactionPlan['message']);
196
+ }
197
+
198
+ /**
199
+ * Parses an {@link InstructionPlanInput} or {@link TransactionPlanInput} and
200
+ * returns the appropriate plan type.
201
+ *
202
+ * This function automatically detects whether the input represents instructions
203
+ * or transactions and delegates to the appropriate parser:
204
+ * - If the input is a transaction message or transaction plan, it delegates
205
+ * to {@link parseTransactionPlanInput}.
206
+ * - Otherwise, it delegates to {@link parseInstructionPlanInput}.
207
+ *
208
+ * @param input - The input to parse, which can be either an instruction-based
209
+ * or transaction-based input.
210
+ * @returns The parsed plan, either an {@link InstructionPlan} or a {@link TransactionPlan}.
211
+ *
212
+ * @example
213
+ * Parsing an instruction input.
214
+ * ```ts
215
+ * const plan = parseInstructionOrTransactionPlanInput(myInstruction);
216
+ * // Returns an InstructionPlan
217
+ * ```
218
+ *
219
+ * @example
220
+ * Parsing a transaction message input.
221
+ * ```ts
222
+ * const plan = parseInstructionOrTransactionPlanInput(myTransactionMessage);
223
+ * // Returns a TransactionPlan
224
+ * ```
225
+ *
226
+ * @see {@link parseInstructionPlanInput}
227
+ * @see {@link parseTransactionPlanInput}
228
+ * @see {@link InstructionPlanInput}
229
+ * @see {@link TransactionPlanInput}
230
+ */
231
+ export function parseInstructionOrTransactionPlanInput(
232
+ input: InstructionPlanInput | TransactionPlanInput,
233
+ ): InstructionPlan | TransactionPlan {
234
+ if (Array.isArray(input) && input.length === 0) {
235
+ return parseTransactionPlanInput(input);
236
+ }
237
+ if (Array.isArray(input) && isTransactionPlanInput(input[0])) {
238
+ return parseTransactionPlanInput(input as TransactionPlanInput);
239
+ }
240
+ if (isTransactionPlanInput(input)) {
241
+ return parseTransactionPlanInput(input as TransactionPlanInput);
242
+ }
243
+ return parseInstructionPlanInput(input as InstructionPlanInput);
244
+ }
245
+
246
+ function isTransactionPlanInput(value: unknown): value is SingleTransactionPlan['message'] | TransactionPlan {
247
+ return isTransactionPlan(value) || isTransactionMessage(value);
248
+ }
249
+
250
+ function isTransactionMessage(value: unknown): value is SingleTransactionPlan['message'] {
251
+ return (
252
+ typeof value === 'object' &&
253
+ value !== null &&
254
+ 'instructions' in value &&
255
+ Array.isArray(value.instructions) &&
256
+ 'version' in value
257
+ );
258
+ }