tp3 (2024-04-25)
The next version of the @solana/web3.js Technology Preview brings a major change to how signed transactions are represented, in response to user feedback.
To install the third Technology Preview:
npm install --save @solana/web3.js@tp3Most notably, all *Transaction*() helpers have been renamed to *TransactionMessage*() to reflect what is actually being built when you build a transaction: the transaction message.
Before
const tx = pipe(
createTransaction({ version: 0 }),
tx => addTransactionFeePayer(payerAddress, tx),
/* ... */
);After
const txMessage = pipe(
createTransactionMessage({ version: 0 }),
m => addTransactionMessageFeePayer(payerAddress, m),
/* ... */
);We've introduced a new type to represent signed and partially signed messages. This type encapsulates the bytes of a transaction message – however they were serialized – and the ordered map of signer addresses to signatures. Reducing a transaction message to just those two things after the first signature is applied will make it harder for a subsequent signer to invalidate the existing signatures by _re_serializing the transaction message in such a way that the bytes or the order of signer addresses changes.
Try a demo of Technology Preview 3 in your browser at CodeSandbox.
Changelog since Technology Preview 2
-
#2434
31916aeThanks @lorisleiva! - RenamedmapCodectotransformCodec -
#2411
2e5af9fThanks @lorisleiva! - RenamedfixCodectofixCodecSize -
#2352
125fc15Thanks @steveluscher! -SubtleCryptoassertion methods that can make their assertions synchronously are now synchronous, for performance. -
#2329
478443fThanks @luu-alex! -createKeyPairFromBytes()now validates that the public key imported is the one that would be derived from the private key imported -
#2383
ce1be3fThanks @lorisleiva! -getScalarEnumCodecis now calledgetEnumCodec -
#2382
7e86583Thanks @lorisleiva! -getDataEnumCodecis now calledgetDiscriminatedUnionCodec -
#2397
a548de2Thanks @lorisleiva! - Added a newaddCodecSizePrefixprimitiveconst codec = addCodecSizePrefix(getBase58Codec(), getU32Codec()); codec.encode("hello world"); // 0x0b00000068656c6c6f20776f726c64 // | └-- Our encoded base-58 string. // └-- Our encoded u32 size prefix.
-
#2419
89f399dThanks @lorisleiva! - Added newaddCodecSentinelprimitiveThe
addCodecSentinelfunction provides a new way of delimiting the size of a codec. It allows us to add a sentinel to the end of the encoded data and to read until that sentinel is found when decoding. It accepts any codec and aUint8Arraysentinel responsible for delimiting the encoded data.const codec = addCodecSentinel(getUtf8Codec(), new Uint8Array([255, 255])); codec.encode("hello"); // 0x68656c6c6fffff // | └-- Our sentinel. // └-- Our encoded string.
-
#2400
ebb03cdThanks @lorisleiva! - Added newcontainsBytesandgetConstantCodechelpersThe
containsByteshelper checks if aUint8Arraycontains anotherUint8Arrayat a given offset.containsBytes(new Uint8Array([1, 2, 3, 4]), new Uint8Array([2, 3]), 1); // true containsBytes(new Uint8Array([1, 2, 3, 4]), new Uint8Array([2, 3]), 2); // false
The
getConstantCodecfunction accepts anyUint8Arrayand returns aCodec<void>. When encoding, it will set the providedUint8Arrayas-is. When decoding, it will assert that the next bytes contain the providedUint8Arrayand move the offset forward.const codec = getConstantCodec(new Uint8Array([1, 2, 3])); codec.encode(undefined); // 0x010203 codec.decode(new Uint8Array([1, 2, 3])); // undefined codec.decode(new Uint8Array([1, 2, 4])); // Throws an error.
-
#2344
deb7b80Thanks @lorisleiva! - ImprovegetTupleCodectype inferences and performanceThe tuple codec now infers its encoded/decoded type from the provided codec array and uses the new
DrainOuterGenerichelper to reduce the number of TypeScript instantiations. -
#2322
6dcf548Thanks @lorisleiva! - UseDrainOuterGenerichelper on codec type mappingsThis significantly reduces the number of TypeScript instantiations on object mappings,
which increases TypeScript performance and prevents "Type instantiation is excessively deep and possibly infinite" errors. -
#2381
49a764cThanks @lorisleiva! - DataEnum codecs can now use numbers or symbols as discriminator valuesconst codec = getDataEnumCodec([ [1, getStructCodec([[["one", u32]]])][ (2, getStructCodec([[["two", u32]]])) ], ]); codec.encode({ __kind: 1, one: 42 }); codec.encode({ __kind: 2, two: 42 });
This means you can also use enum values as discriminators, like so:
enum Event { Click, KeyPress, } const codec = getDataEnumCodec([ [ Event.Click, getStructCodec([ [ ["x", u32], ["y", u32], ], ]), ], [Event.KeyPress, getStructCodec([[["key", u32]]])], ]); codec.encode({ __kind: Event.Click, x: 1, y: 2 }); codec.encode({ __kind: Event.KeyPress, key: 3 });
-
#2430
82cf07fThanks @lorisleiva! - AddeduseValuesAsDiscriminatorsoption togetEnumCodecWhen dealing with numerical enums that have explicit values, you may now use the
useValuesAsDiscriminatorsoption to encode the value of the enum variant instead of its index.enum Numbers { One, Five = 5, Six, Nine = 9, } const codec = getEnumCodec(Numbers, { useValuesAsDiscriminators: true }); codec.encode(Direction.One); // 0x00 codec.encode(Direction.Five); // 0x05 codec.encode(Direction.Six); // 0x06 codec.encode(Direction.Nine); // 0x09
Note that when using the
useValuesAsDiscriminatorsoption on an enum that contains a lexical value, an error will be thrown.enum Lexical { One, Two = "two", } getEnumCodec(Lexical, { useValuesAsDiscriminators: true }); // Throws an error.
-
#2398
bef9604Thanks @lorisleiva! - Added a newgetUnionCodechelper that can be used to encode/decode any TypeScript union.const codec: Codec<number | boolean> = getUnionCodec( [getU16Codec(), getBooleanCodec()], (value) => (typeof value === "number" ? 0 : 1), (bytes, offset) => (bytes.slice(offset).length > 1 ? 0 : 1), ); codec.encode(42); // 0x2a00 codec.encode(true); // 0x01
-
#2401
919c736Thanks @lorisleiva! - Added newgetHiddenPrefixCodecandgetHiddenSuffixCodechelpersThese functions allow us to respectively prepend or append a list of hidden
Codec<void>to a given codec. When encoding, the hidden codecs will be encoded before or after the main codec and the offset will be moved accordingly. When decoding, the hidden codecs will be decoded but only the result of the main codec will be returned. This is particularly helpful when creating data structures that include constant values that should not be included in the final type.const codec: Codec<number> = getHiddenPrefixCodec(getU16Codec(), [ getConstantCodec(new Uint8Array([1, 2, 3])), getConstantCodec(new Uint8Array([4, 5, 6])), ]); codec.encode(42); // 0x0102030405062a00 // | | └-- Our main u16 codec (value = 42). // | └-- Our second hidden prefix codec. // └-- Our first hidden prefix codec. codec.decode(new Uint8Array([1, 2, 3, 4, 5, 6, 42, 0])); // 42
-
#2433
2d48c09Thanks @lorisleiva! - ThegetBooleanCodecfunction now accepts variable-size number codecs -
#2394
288029aThanks @lorisleiva! - Added a newgetLiteralUnionCodecconst codec = getLiteralUnionCodec(["left", "right", "up", "down"]); // ^? FixedSizeCodec<"left" | "right" | "up" | "down"> const bytes = codec.encode("left"); // 0x00 const value = codec.decode(bytes); // 'left'
-
#2410
4ae78f5Thanks @lorisleiva! - Added newgetZeroableNullableCodecandgetZeroableOptionCodecfunctionsThese functions rely on a zero value to represent
Noneornullvalues as opposed to using a boolean prefix.const codec = getZeroableNullableCodec(getU16Codec()); codec.encode(42); // 0x2a00 codec.encode(null); // 0x0000 codec.decode(new Uint8Array([42, 0])); // 42 codec.encode(new Uint8Array([0, 0])); // null
Both functions can also be provided with a custom definition of the zero value using the
zeroValueoption.const codec = getZeroableNullableCodec(getU16Codec(), { zeroValue: new Uint8Array([255, 255]), }); codec.encode(42); // 0x2a00 codec.encode(null); // 0xfffff codec.encode(new Uint8Array([0, 0])); // 0 codec.decode(new Uint8Array([42, 0])); // 42 codec.decode(new Uint8Array([255, 255])); // null
-
#2380
bf029ddThanks @lorisleiva! - DataEnum codecs now support custom discriminator propertiesconst codec = getDataEnumCodec( [ [ "click", getStructCodec([ [ ["x", u32], ["y", u32], ], ]), ], ["keyPress", getStructCodec([[["key", u32]]])], ], { discriminator: "event" }, ); codec.encode({ event: "click", x: 1, y: 2 }); codec.encode({ event: "keyPress", key: 3 });
-
#2414
ff4aff6Thanks @lorisleiva! - Used capitalised variant names forEndianenumThis makes the enum more consistent with other enums in the library.
// Before. Endian.BIG; Endian.LITTLE; // After. Endian.Big; Endian.Little;
-
#2376
9370133Thanks @steveluscher! - Fixed a bug that prevented the production error decoder from decoding negative error codes -
#2358
2d54650Thanks @steveluscher! - The encodedSolanaErrorcontext that is thrown in production is now base64-encoded for compatibility with more terminal shells -
#2502
5ed19c6Thanks @steveluscher! - Added TypeScript types to@solana/fast-stable-stringify -
#2491
2040f96Thanks @lorisleiva! - Remove program types andresolveTransactionErrorhelper -
#2490
1672346Thanks @lorisleiva! - AddisProgramErrorhelper function to@solana/programs -
#2504
18d6b56Thanks @steveluscher! - Replacedfast-stable-stringifywith our fork -
#2415
c801637Thanks @steveluscher! - Improve transaction sending reliability for those who skip preflight (simulation) when callingsendTransaction -
#2553
af9fa3bThanks @buffalojoec! - ChangescreateRecentSignatureConfirmationPromiseFactoryto enforcerpcandrpcSubscriptionsto have matching clusters, changing the function signature to accept an object rather than two parameters. -
#2554
0b02de1Thanks @buffalojoec! - ChangescreateNonceInvalidationPromiseFactoryto enforcerpcandrpcSubscriptionsto have matching clusters, changing the function signature to accept an object rather than two parameters. -
#2550
54d68c4Thanks @mcintyre94! - Refactor transactions, to separate constructing transaction messages from signing/sending compiled transactionsA transaction message contains a transaction version and an array of transaction instructions. It may also have a fee payer and a lifetime. Transaction messages can be built up incrementally, for example by adding instructions or a fee payer.
Transactions represent a compiled transaction message (serialized to an immutable byte array) and a map of signatures for each required signer of the transaction message. These signatures are only valid for the byte array stored in the transaction. Transactions can be signed by updating this map of signatures, and when they have a valid signature for all required signers they can be landed on the network.
Note that this change essentially splits the previous
@solana/transactionsAPI in two, with functionality for creating/modifying transaction messages moved to@solana/transaction-messages. -
#2413
002cc38Thanks @lorisleiva! - RemovedgetStringCodecin favour offixCodecSizeandaddCodecSizePrefixThe
getStringCodecfunction now always returns aVariableSizeCodecthat uses as many bytes as necessary to encode and/or decode strings. In order to fix or prefix the size of agetStringCodec, you may now use thefixCodecSizeorprefixCodecSideaccordingly. Here are some examples:// Before. getStringCodec({ size: "variable" }); // Variable. getStringCodec({ encoding: getUtf8Codec(), size: "variable" }); // Variable (equivalent). getStringCodec({ size: 5 }); // Fixed. getStringCodec({ encoding: getUtf8Codec(), size: 5 }); // Fixed (equivalent). getStringCodec(); // Prefixed. getStringCodec({ encoding: getUtf8Codec(), size: getU32Codec() }); // Prefixed (equivalent). // After. getUtf8Codec(); // Variable. fixCodecSize(getUtf8Codec(), 5); // Fixed. addCodecSizePrefix(getUtf8Codec(), getU32Codec()); // Prefixed.
-
#2412
e3e82d9Thanks @lorisleiva! - Removed the size option ofgetBytesCodecThe
getBytesCodecfunction now always returns aVariableSizeCodecthat uses as many bytes as necessary to encode and/or decode byte arrays. In order to fix or prefix the size of agetBytesCodec, you may now use thefixCodecSizeorprefixCodecSideaccordingly. Here are some examples:// Before. getBytesCodec(); // Variable. getBytesCodec({ size: 5 }); // Fixed. getBytesCodec({ size: getU16Codec() }); // Prefixed. // After. getBytesCodec(); // Variable. fixCodecSize(getBytesCodec(), 5); // Fixed. addCodecSizePrefix(getBytesCodec(), getU16Codec()); // Prefixed.