@solana/kit
v8.0.0 (2026-08-21)
Major Changes
-
[
@solana/instruction-plans] #191380368ebThanks @mcintyre94! - Stop writing to the execution context increateTransactionPlanExecutorThe
executeTransactionMessagecallback can no longer return aSignatureor aTransaction. Those return values were deprecated when the callback gained the ability to return the context that a successful result should carry, and they are now gone: that context, a completeTContext, is the only thing the callback returns. Nothing is written to it on your behalf.const transactionPlanExecutor = createTransactionPlanExecutor({ executeTransactionMessage: async (context, message) => { const transaction = await signTransactionMessageWithSigners(message); context.transaction = transaction; + const signature = getSignatureFromTransaction(transaction); await sendAndConfirmTransaction(transaction, { commitment: 'confirmed' }); - return transaction; + return { signature, transaction }; }, });The mutable
contextargument is still there, and still serves the failure path: whatever the callback stores on it before it throws is preserved in the resultingFailedSingleTransactionPlanResult. The two channels differ only in which outcome they feed. Mutating the context makes a value available to a failed result; returning it makes a value available to a successful one. On success the two are merged, with the returned value taking precedence, so a property stored but not returned is still reported.Note that the callback cannot simply return the context it was given — every property on it is optional, so it does not satisfy
TContext. Build the return value from the values you have instead. This is the point of the return type: a callback that declares a context with a requiredsignatureand never produces one now fails to compile, rather than yielding a successful result whosecontext.signatureis typed butundefinedat runtime.This unblocks executors that never obtain a fee payer signature. Previously the executor derived
context.signatureby callinggetSignatureFromTransactionon a returned transaction, and on any transaction found on the context while handling a failure. That call throwsSOLANA_ERROR__TRANSACTION__FEE_PAYER_SIGNATURE_MISSINGwhen the fee payer slot is empty, so an executor that deliberately produces partially signed transactions — signed by an authority, to be paid for and submitted by a relayer later — could not succeed, and one that stored such a transaction before failing had its original error replaced by that one. Neither derivation exists any more, so both cases now work. Declare a context type that does not require a signature and store just the transaction:const transactionPlanExecutor = createTransactionPlanExecutor<{ transaction: Transaction }>({ executeTransactionMessage: async (_context, message) => { return { transaction: await signTransactionMessageWithSigners(message) }; }, });
Signatures are no longer added behind your back. An executor whose
TContextrequires asignature— including the defaultTransactionPlanResultContextWithSignature— must now produce one itself, and the compiler holds it to that. Failed results carry only what the callback stored on the context before it threw; asignatureis no longer recovered from a stored transaction.BaseTransactionPlanResultContextis removed. It described the fields the executor used to write on your behalf, and nothing writes them any more — what a context holds is entirelyTContext's business. UseTransactionPlanResultContextWithSignaturewhere you want the signature guarantee, or declare the optionalmessage/signature/transactionfields your own context actually needs.successfulSingleTransactionPlanResultFromTransactionis removed. It was the last place that derived asignatureon your behalf — by callinggetSignatureFromTransaction, with the same fee-payer-signature requirement described above — and the executor no longer uses it. Construct results withsuccessfulSingleTransactionPlanResultinstead, passing the context explicitly:- successfulSingleTransactionPlanResultFromTransaction(message, transaction); + successfulSingleTransactionPlanResult(message, { + signature: getSignatureFromTransaction(transaction), + transaction, + });
-
[
@solana/instruction-plans,@solana/kit,@solana/rpc-transformers,@solana/transactions] #194834568a9Thanks @mcintyre94! - Remove APIs that were deprecated in previous versions: the compute-unit-limit estimation helpers in@solana/kit, thegetBigIntDowncastRequestTransformerin@solana/rpc-transformers, the fixed transaction size constants in@solana/transactions, and theSuccessfulBaseTransactionPlanResultContexttype in@solana/instruction-plans.BREAKING CHANGES
estimateComputeUnitLimitFactoryremoved from@solana/kit. UseestimateResourceLimitsFactoryinstead. The resource-limits estimator returns both the compute unit limit and (for version 1 transactions) the loaded accounts data size limit from a single simulation call.- const estimateComputeUnitLimit = estimateComputeUnitLimitFactory({ rpc }); - const computeUnitLimit = await estimateComputeUnitLimit(transactionMessage); + const estimateResourceLimits = estimateResourceLimitsFactory({ rpc }); + const { computeUnitLimit } = await estimateResourceLimits(transactionMessage);
estimateAndSetComputeUnitLimitFactoryremoved from@solana/kit. UseestimateAndSetResourceLimitsFactoryinstead, which additionally sets the loaded accounts data size limit for version 1 transactions.- const estimateAndSet = estimateAndSetComputeUnitLimitFactory(estimateComputeUnitLimitFactory({ rpc })); + const estimateAndSet = estimateAndSetResourceLimitsFactory(estimateResourceLimitsFactory({ rpc })); const updatedMessage = await estimateAndSet(transactionMessage);
fillTransactionMessageProvisoryComputeUnitLimitremoved from@solana/kit. UsefillTransactionMessageProvisoryResourceLimitsinstead, which additionally reserves space for the loaded accounts data size limit on version 1 transactions.- const filledMessage = fillTransactionMessageProvisoryComputeUnitLimit(transactionMessage); + const filledMessage = fillTransactionMessageProvisoryResourceLimits(transactionMessage);
getBigIntDowncastRequestTransformerremoved from@solana/rpc-transformers. This transformer was no longer used by the default Solana RPC request transformer. The Solana RPC transport serializesbigintvalues losslessly as large integer literals, and Agave parses JSON integers across the fullu64range without precision loss, so downcastingbigints to (potentially lossy)numbers is unnecessary. If you still need this behavior, recreate it withgetTreeWalkerRequestTransformer.TRANSACTION_PACKET_SIZE,TRANSACTION_PACKET_HEADER, andTRANSACTION_SIZE_LIMITremoved from@solana/transactions. Transaction size is no longer constant, as version 1 transactions have a larger size limit. UsegetTransactionSizeLimitto get the size limit for a specific transaction based on its version, or theLEGACY_TRANSACTION_SIZE_LIMITandV1_TRANSACTION_SIZE_LIMITconstants for a specific version.- const numFreeBytes = TRANSACTION_SIZE_LIMIT - getTransactionSize(transaction); + const numFreeBytes = getTransactionSizeLimit(transaction) - getTransactionSize(transaction);
SuccessfulBaseTransactionPlanResultContextremoved from@solana/instruction-plans. UseTransactionPlanResultContextWithSignatureinstead as the context type argument.- function processResult(result: SuccessfulSingleTransactionPlanResult<SuccessfulBaseTransactionPlanResultContext>) { + function processResult(result: SuccessfulSingleTransactionPlanResult<TransactionPlanResultContextWithSignature>) {
-
[
@solana/instruction-plans] #19107b983baThanks @mcintyre94! - LetTContextdecide what a transaction plan result context containsThe context attached to a transaction plan result was never entirely controlled by the executor.
SuccessfulSingleTransactionPlanResulthardcoded it asSuccessfulBaseTransactionPlanResultContext & TContext, andcreateTransactionPlanExecutormixed further properties into both the callback's context and the executor's result type. Because intersections only ever narrow, no choice ofTContextcould relax the requiredsignature— which made it impossible to type an executor that partially signs transactions for a relayer to submit later.TContextis now the only thing that says what a context contains. The signature guarantee moved out of the structure of the result types and into the default value ofTContext, so every zero-type-argument spelling behaves exactly as it did before.A new context type.
TransactionPlanResultContextWithSignatureguarantees asignatureand is the new default everywhere.SuccessfulBaseTransactionPlanResultContextis deprecated in favour of it.Explicit context types must be migrated. Intersect the new default to keep the signature guarantee:
- SingleTransactionPlanResult<{ startedAt: number }> + SingleTransactionPlanResult<TransactionPlanResultContextWithSignature & { startedAt: number }>
The executor callback now receives
Partial<TContext>. A fresh, empty context is created for every transaction message and filling it in is the callback's job, but its properties used to be typed as required — so a callback could read one before writing it, be told a value was there, and getundefinedat runtime. Everything is optional on entry now. Writing still narrows, so a read aftercontext.custom = 'value'gives the non-optional type.One consequence is that a callback can no longer annotate its own parameter with required properties, which was a common way to infer a custom context. Use a type argument instead:
- createTransactionPlanExecutor({ - executeTransactionMessage: async (context: { startedAt: number }) => { /* ... */ }, - }) + createTransactionPlanExecutor<TransactionPlanResultContextWithSignature & { startedAt: number }>({ + executeTransactionMessage: async context => { /* ... */ }, + })
A returned context no longer has to carry a
signature. TheexecuteTransactionMessagecallback may return the context that a successful result should carry, and that context used to need a signature because the result type hardcoded one.TContextdecides now: the default still requires it, and a customTContextthat omits it is accepted — which is what makes the relayer case above expressible end to end, rather than merely possible at runtime. Consequently the executor no longer tells a returned context apart from a returnedTransactionby looking for asignatureon it, since a context may not have one; it looks for thesignaturesmap that only aTransactionhas.Failed and canceled results type their context as
Readonly<Partial<TContext>>. Those branches never guaranteed custom properties at runtime — the context is built incrementally and the callback may throw at any point — so the types now say so.Inference from object literals is narrower, since the result constructors no longer add properties you did not pass.
successfulSingleTransactionPlanResult(message, { signature })infersReadonly<{ signature: Signature }>rather than a context that also carried optionalmessageandtransactionfields plus an index signature, so readingresult.context.messageoff it is now an error. On the failed and canceled constructors thePartialgoes further: even a field you did pass comes back optional, sofailedSingleTransactionPlanResult(message, error, { transaction }).context.transactionisTransaction | undefined. Pass an explicit type argument wherever you need a field to type as required. Relatedly,successfulSingleTransactionPlanResultFromTransaction's optionalcontextparameter changes fromOmit<BaseTransactionPlanResultContext, 'signature' | 'transaction'> & TContexttoTContext;signatureandtransactionare still derived from thetransactionargument and intersected into the result's context type.Helpers that consume results now accept any context.
passthroughFailedTransactionPlanExecution,createFailedToSendTransactionError,createFailedToSendTransactionsErrorandcreateFailedToExecuteTransactionPlanErrorwere non-generic, so they implicitly demanded the default signature-guaranteeing context and rejected results parameterised with anything else. They are now generic overTContextand preserve it. Where they read a signature to build an error message they narrow it at runtime, so a result without one simply omits it from the message.Execution behaviour is unchanged. The executor still populates
context.signaturefrom the signature or transaction your callback returns, and still recovers one for failed results from atransactionleft on the context. Those writes just no longer show up in the result type unless yourTContextasked for them. The behavioural changes are the two described above: an error message built from a context whosesignatureis absent, or is not a string, now omits it rather than interpolating whatever was there, and a returned context is recognised by the absence of asignaturesmap rather than the presence of asignature.
Minor Changes
-
[
@solana/errors,@solana/instruction-plans] #1902cb09af6Thanks @mcintyre94! - AddSOLANA_ERROR__FAILED_TO_SIGN_TRANSACTIONandSOLANA_ERROR__FAILED_TO_SIGN_TRANSACTIONSerror codes, together with thecreateFailedToSignTransactionErrorandcreateFailedToSignTransactionsErrorfactories that raise them. These are the signing counterparts to the existing failed-to-send codes and factories, intended for high-level wrappers that sign transactions without submitting them: such a wrapper can now translate the low-levelSOLANA_ERROR__INSTRUCTION_PLANS__FAILED_TO_EXECUTE_TRANSACTION_PLANthrown by an executor into a user-facing error, exactly as the sending wrappers already do.The signing errors carry the same context as their sending counterparts, including the non-enumerable
transactionPlanResultand the optional simulationlogsandpreflightData. Those simulation fields are populated for signing too, because executors typically estimate resource limits by simulating before they sign, so a failed estimation reaches the error the same way it does when sending.They differ from the sending errors in one respect: the message carries no indicator of where the failure happened. That indicator exists to locate a failure relative to network submission —
(preflight)before it, or the transaction signature after it — and signing never submits, so neither applies. A signature would be particularly misleading, since quoting one implies the transaction reached the network when it never did. ThelogsandpreflightDatacontext properties are still populated whenever a simulation was responsible, and those logs still appear in the message, so only the prefix is dropped. -
[
@solana/plugin-interfaces] #189982a88d8Thanks @mcintyre94! - Add aClientWithTransactionSigninginterface providingsignTransactionandsignTransactions. These accept the same flexible inputs as theirClientWithTransactionSendingcounterparts, but hand back the signed transactions instead of submitting them. The interface is parameterised over the context attached to its results and makes no default guarantees about that context: what it contains is entirely decided by the plugin providing the capability — typically acontext.transactionon successful results.ClientWithTransactionSendingnow also accepts an optionalTContexttype parameter that flows through to the results ofsendTransactionandsendTransactions. Unlike the signing interface, it defaults toTransactionPlanResultContextWithSignaturefor backward compatibility, so existing usage keeps the requiredcontext.signatureon successful results. -
[
@solana/rpc-api,@solana/rpc-graphql,@solana/rpc-transformers,@solana/rpc-types,@solana/transaction-messages] #195194adb60Thanks @amilz! - Fill in several gaps in transaction v1 (SIMD-0385) support.@solana/transaction-messagesnow exports itsv1-transaction-configmodule, sosetTransactionMessageConfig, theV1TransactionConfigtype and the transaction config bit-mask helpers are importable. Previously the module was built and shipped but omitted from the package index, forcing consumers onto the four single-field setters and to derive the config type by hand.The
V1TransactionConfig.computeUnitLimitdocstring incorrectly described the legacy fallback of 200,000 compute units per instruction. On version 1 an unsetcomputeUnitLimitresolves to zero and the transaction fails at execution, so the docstring now says so, as does the one forloadedAccountsDataSizeLimit.@solana/rpc-typestransaction message types now carry thetransactionConfigthat the server returns for version 1 transactions, so reading a transaction's compute budget no longer requires a cast. Its threeu32fields are typed and transformed asnumberrather than being upcast tobigint, leavingpriorityFeeas the onlyLamportsamong them.@solana/rpc-apicarries the same field on the message shape it uses for thejsonandjsonParsedencodings ofgetTransactionandgetTransactionsForAddress. The same field is exposed on theTransactionMessagetype in@solana/rpc-graphql. -
[
@solana/transaction-messages] #1950ca01807Thanks @mcintyre94! - Add support for version 1 transaction messages tocreateTransactionMessage. You can now pass{ version: 1 }to create an empty v1 transaction message.This means that code using version 1 transaction messages will now type check.
Patch Changes
-
[
@solana/codecs-core] #19571b30374Thanks @mcintyre94! - FixtoArrayBufferreturning the entire backing buffer when given aUint8Arrayview that starts at byte offset zero but is shorter than its underlyingArrayBuffer. This causedsignBytesandverifySignatureto operate on the wrong bytes — andgetBase64Decoder().decode()to include trailing data — for such views, most notably themessageBytesof a decoded version 1 transaction, whose wire envelope places the message first and the signatures last -
[
@solana/transactions] #19585d526f7Thanks @mcintyre94! - FixgetTransactionSizeLimitmisclassifying single-signer legacy transactions as version 1. A legacy message has no version byte, so its first byte is the number of required signatures —1for every single-signer transaction — which was mistaken for a v1 version byte. As a result,isTransactionWithinSizeLimit,assertIsTransactionWithinSizeLimit, andassertIsSendableTransactionaccepted legacy transactions of up to 4096 bytes that the network rejects at 1232 bytes. The version flag (high bit) of the first message byte is now required to be set before treating a transaction as version 1