Repository: solana-labs/solana-program-library
Pull request #6388 (2024-03-11 14:20:34)

js: Update token-group / token-metadata to use more idiomatic encoders
#### Problem

As pointed out in the comments in https://github.com/solana-labs/solana-program-library/pull/6358, the token group and metadata JS libraries do some slightly non-idiomatic things with the new web3.js.

#### Solution

Implement those suggestions:

* update to using just `@solana/codecs`
* change version to just `2.0.0-preview.1`
* eliminate `packInstruction` for more idiomatic encoder creator
* use a real `PublicKey` encoder rather than a buffer

Unfortunately, we can't totally get rid of `Buffer` because it's required in the `data` part of the `TransactionInstruction` in old web3.js, but now its use is more isolated.


Patch:
diff --git a/docs/src/account-compression.md b/docs/src/account-compression.md
index e994945ed..e7e8e67ce 100644
--- a/docs/src/account-compression.md
+++ b/docs/src/account-compression.md
@@ -2,20 +2,20 @@
 title: Account Compression Program
 ---
 
-This on-chain program provides an interface for composing smart-contracts to create and use SPL ConcurrentMerkleTrees. The primary application of using SPL ConcurrentMerkleTrees is to make edits to off-chain data with on-chain verification.
+This on-chain program provides an interface for composing smart contracts to create and use SPL ConcurrentMerkleTrees. The primary application of using SPL ConcurrentMerkleTrees is to make edits to off-chain data with on-chain verification.
 
 ## Motivation
 
--  The high throughput of the Solana blockchain has increased the creation of Non fungible assets i.e NFTs due to their custodial ownership and censorship resistance characteristics. However, the practical use cases of these NFTs are limited by network storage costs when these are created at scale. It's rather inexpensive to mint a single non fungible token, however as you increase the quantity the cost of storing the asset's data on-chain becomes uneconomical.
+-  The high throughput of the Solana blockchain has increased the creation of non-fungible assets i.e. NFTs due to their custodial ownership and censorship-resistance characteristics. However, the practical use cases of these NFTs are limited by network storage costs when these are created at scale. It's rather inexpensive to mint a single non-fungible token; however, as you increase the quantity the cost of storing the asset's data on-chain becomes uneconomical.
 
-- To fix this we must ensure the cost per token is as close to zero as possible. The solution is to store a compressed hash of the asset data on chain while maintaining the actual data off chain in a database. The program provides a way to verify the off chain data on chain and also make concurrent writes to the data. In order to do this we introduced a new data structure called a Concurrent Merkle Tree that avoids proof collision while making concurrent writes.
+- To fix this, we must ensure the cost per token is as close to zero as possible. The solution is to store a compressed hash of the asset data on-chain, while maintaining the actual data off-chain in a database. The program provides a way to verify the off-chain data on-chain and also to make concurrent writes to the data. In order to do this, we introduced a new data structure called a Concurrent Merkle Tree, which avoids proof collision while making concurrent writes.
 
 
 ## Background
 
-The account compression program is currently being used for the [Metaplex Bubblegum Program](https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/)
+The account compression program is currently being used for the [Metaplex Bubblegum Program](https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/).
 
-To solve the problem of the high on-chain storage cost per unit of these assets, we need to store a compressed fingerprint on-chain that can verify the off-chain asset data. To do this we need
+To solve the problem of the high on-chain storage cost per unit of these assets, we need to store a compressed fingerprint on-chain that can verify the off-chain asset data. To do this we need:
   - Concurrent Merkle Trees
     - The concurrent merkle trees allow us to compress all the data into a single root hash stored on-chain while allowing concurrent replacements and appends to the data.
   - Program indexer
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1af299e14..ae7be2ac7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -488,21 +488,12 @@ importers:
 
   token-group/js:
     dependencies:
-      '@solana/codecs-core':
-        specifier: 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
-        version: 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
-      '@solana/codecs-data-structures':
-        specifier: 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
-        version: 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
-      '@solana/codecs-numbers':
-        specifier: 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
-        version: 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
-      '@solana/codecs-strings':
-        specifier: 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
-        version: 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb(fastestsmallesttextencoderdecoder@1.0.22)
+      '@solana/codecs':
+        specifier: 2.0.0-preview.1
+        version: 2.0.0-preview.1(fastestsmallesttextencoderdecoder@1.0.22)
       '@solana/options':
-        specifier: 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
-        version: 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
+        specifier: 2.0.0-preview.1
+        version: 2.0.0-preview.1
       '@solana/spl-type-length-value':
         specifier: 0.1.0
         version: link:../../libraries/type-length-value/js
@@ -643,21 +634,12 @@ importers:
 
   token-metadata/js:
     dependencies:
-      '@solana/codecs-core':
-        specifier: 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
-        version: 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
-      '@solana/codecs-data-structures':
-        specifier: 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
-        version: 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
-      '@solana/codecs-numbers':
-        specifier: 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
-        version: 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
-      '@solana/codecs-strings':
-        specifier: 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
-        version: 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb(fastestsmallesttextencoderdecoder@1.0.22)
+      '@solana/codecs':
+        specifier: 2.0.0-preview.1
+        version: 2.0.0-preview.1(fastestsmallesttextencoderdecoder@1.0.22)
       '@solana/options':
-        specifier: 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
-        version: 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
+        specifier: 2.0.0-preview.1
+        version: 2.0.0-preview.1
       '@solana/spl-type-length-value':
         specifier: 0.1.0
         version: link:../../libraries/type-length-value/js
@@ -2361,10 +2343,17 @@ packages:
   /@solana/codecs-core@2.0.0-experimental.8618508:
     resolution: {integrity: sha512-JCz7mKjVKtfZxkuDtwMAUgA7YvJcA2BwpZaA1NOLcted4OMC4Prwa3DUe3f3181ixPYaRyptbF0Ikq2MbDkYEA==}
 
+  /@solana/codecs-core@2.0.0-preview.1:
+    resolution: {integrity: sha512-0y3kgFSJAOApNGefEOgLRMiXOHVmcF29Xw3zmR4LDUABvytG3ecKMXGz5oLt3vdaU2CyN3tuUVeGmQjrd0U1kw==}
+    dependencies:
+      '@solana/errors': 2.0.0-preview.1
+    dev: false
+
   /@solana/codecs-core@2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb:
     resolution: {integrity: sha512-g4UutMYkgbhA607ED7KXY/wIrkR1cH5Xx+I4x4gUIWHa3F3h2icnj3KszvCOsnpXl821a7LHdtmQWCrgrQIpsQ==}
     dependencies:
       '@solana/errors': 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
+    dev: true
 
   /@solana/codecs-data-structures@2.0.0-experimental.8618508:
     resolution: {integrity: sha512-sLpjL9sqzaDdkloBPV61Rht1tgaKq98BCtIKRuyscIrmVPu3wu0Bavk2n/QekmUzaTsj7K1pVSniM0YqCdnEBw==}
@@ -2372,12 +2361,12 @@ packages:
       '@solana/codecs-core': 2.0.0-experimental.8618508
       '@solana/codecs-numbers': 2.0.0-experimental.8618508
 
-  /@solana/codecs-data-structures@2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb:
-    resolution: {integrity: sha512-XI8QO48HZh8ah6/t4QRCCOzD5OHe1obNnc9LmEyaJwBe5gmzH4DEUgqAol21UqJtoYvYpK855sP7Kv/2/rGRxA==}
+  /@solana/codecs-data-structures@2.0.0-preview.1:
+    resolution: {integrity: sha512-E5ToPu+WAEZdNdFBqdxB+M25vQWBWimRkvno58qWuKAIaRJUaM3rAhLhH4oeVx8Epgxg9iXzxyba92sds6CURA==}
     dependencies:
-      '@solana/codecs-core': 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
-      '@solana/codecs-numbers': 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
-      '@solana/errors': 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
+      '@solana/codecs-core': 2.0.0-preview.1
+      '@solana/codecs-numbers': 2.0.0-preview.1
+      '@solana/errors': 2.0.0-preview.1
     dev: false
 
   /@solana/codecs-numbers@2.0.0-experimental.8618508:
@@ -2385,11 +2374,19 @@ packages:
     dependencies:
       '@solana/codecs-core': 2.0.0-experimental.8618508
 
+  /@solana/codecs-numbers@2.0.0-preview.1:
+    resolution: {integrity: sha512-NFA8itgcYUY3hkWMBpVRozd2poy1zfOvkIWZKx/D69oIMUtQTBpKrodRVBuhlBkAv12vDNkFljqVySpcMZMl7A==}
+    dependencies:
+      '@solana/codecs-core': 2.0.0-preview.1
+      '@solana/errors': 2.0.0-preview.1
+    dev: false
+
   /@solana/codecs-numbers@2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb:
     resolution: {integrity: sha512-rYpFqYNZqWH45N+bJ40O7v5tejtd6eJLK8Z7NKEK3E46p8MXkK3AenCz5pN/c9bwWzdifCt5OIgtnvEVXbMnsg==}
     dependencies:
       '@solana/codecs-core': 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
       '@solana/errors': 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
+    dev: true
 
   /@solana/codecs-strings@2.0.0-experimental.8618508(fastestsmallesttextencoderdecoder@1.0.22):
     resolution: {integrity: sha512-b2yhinr1+oe+JDmnnsV0641KQqqDG8AQ16Z/x7GVWO+AWHMpRlHWVXOq8U1yhPMA4VXxl7i+D+C6ql0VGFp0GA==}
@@ -2400,6 +2397,17 @@ packages:
       '@solana/codecs-numbers': 2.0.0-experimental.8618508
       fastestsmallesttextencoderdecoder: 1.0.22
 
+  /@solana/codecs-strings@2.0.0-preview.1(fastestsmallesttextencoderdecoder@1.0.22):
+    resolution: {integrity: sha512-kBAxE9ZD5/c8j9CkPxqc55dbo7R50Re3k94SXR+k13DZCCs37Fyn0/mAkw/S95fofbi/zsi4jSfmsT5vCZD75A==}
+    peerDependencies:
+      fastestsmallesttextencoderdecoder: ^1.0.22
+    dependencies:
+      '@solana/codecs-core': 2.0.0-preview.1
+      '@solana/codecs-numbers': 2.0.0-preview.1
+      '@solana/errors': 2.0.0-preview.1
+      fastestsmallesttextencoderdecoder: 1.0.22
+    dev: false
+
   /@solana/codecs-strings@2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb(fastestsmallesttextencoderdecoder@1.0.22):
     resolution: {integrity: sha512-e33PAF9UPqV2xPlIuWeYP7nRhiZuOqF96PJmMQhsk+ck74LtyldDbQ/sMnI9shXcdmjmJivDx/FykhNXI5VVig==}
     peerDependencies:
@@ -2409,6 +2417,27 @@ packages:
       '@solana/codecs-numbers': 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
       '@solana/errors': 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
       fastestsmallesttextencoderdecoder: 1.0.22
+    dev: true
+
+  /@solana/codecs@2.0.0-preview.1(fastestsmallesttextencoderdecoder@1.0.22):
+    resolution: {integrity: sha512-5jZZFZ+/RBbyhItPFv4Htlk+86VXGAY2DobUUXoAZPADnWNR034g5ccX3/sByf6nQ1YKwTALhNHzCZkxrhfr3A==}
+    dependencies:
+      '@solana/codecs-core': 2.0.0-preview.1
+      '@solana/codecs-data-structures': 2.0.0-preview.1
+      '@solana/codecs-numbers': 2.0.0-preview.1
+      '@solana/codecs-strings': 2.0.0-preview.1(fastestsmallesttextencoderdecoder@1.0.22)
+      '@solana/options': 2.0.0-preview.1
+    transitivePeerDependencies:
+      - fastestsmallesttextencoderdecoder
+    dev: false
+
+  /@solana/errors@2.0.0-preview.1:
+    resolution: {integrity: sha512-mnBWfLVwMH4hxwb4sWZ7G7djQCMsyymjysvUPDiF89LueTBm1hfdxUv8Cy1uUCGsJ3jO3scdPwB4noOgr0rG/g==}
+    hasBin: true
+    dependencies:
+      chalk: 5.3.0
+      commander: 12.0.0
+    dev: false
 
   /@solana/errors@2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb:
     resolution: {integrity: sha512-PqBVMviXnHao5dtpDOAp5HgRhIWFkP4Y5DCXMC2PWAS+ogFo4kDwdMTEl5eCBNL/m7irpgrxDfexMdvK8J4DuA==}
@@ -2416,6 +2445,7 @@ packages:
     dependencies:
       chalk: 5.3.0
       commander: 12.0.0
+    dev: true
 
   /@solana/eslint-config-solana@3.0.0(@typescript-eslint/eslint-plugin@6.21.0)(@typescript-eslint/parser@6.21.0)(eslint-plugin-jest@27.9.0)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-simple-import-sort@10.0.0)(eslint-plugin-sort-keys-fix@1.1.2)(eslint-plugin-typescript-sort-keys@3.2.0)(eslint@8.57.0)(typescript@5.4.2):
     resolution: {integrity: sha512-K8qBl5LQRET4H0+JHEfawA5qB9bjkkgOUB0fUHJ6B8bPAvMkzfQ5f5U7udyFeDMH0LsJuL9XRUZuXU4h/l6HNw==}
@@ -2485,11 +2515,11 @@ packages:
       '@solana/codecs-core': 2.0.0-experimental.8618508
       '@solana/codecs-numbers': 2.0.0-experimental.8618508
 
-  /@solana/options@2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb:
-    resolution: {integrity: sha512-1DybB48GGbZgx/1NuAYriHKrMysVjbmqmXRdzjIVVQaWlx5ack2YE6qezurG/QS6A51JVZ7fBJdjLoZpUttM5w==}
+  /@solana/options@2.0.0-preview.1:
+    resolution: {integrity: sha512-+o1ELSfNvGVPLtT9r4ZDri0lVGDbKcQecd3ORN9dYg81hErUW9/K2i0ebpqvv15k66ImLrr4qhkKNUaYE1u+JA==}
     dependencies:
-      '@solana/codecs-core': 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
-      '@solana/codecs-numbers': 2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb
+      '@solana/codecs-core': 2.0.0-preview.1
+      '@solana/codecs-numbers': 2.0.0-preview.1
     dev: false
 
   /@solana/prettier-config-solana@0.0.5(prettier@3.2.5):
diff --git a/token-group/js/package.json b/token-group/js/package.json
index 787ed67e2..9d3841ed1 100644
--- a/token-group/js/package.json
+++ b/token-group/js/package.json
@@ -47,11 +47,8 @@
         "@solana/web3.js": "^1.91.0"
     },
     "dependencies": {
-        "@solana/codecs-core": "2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb",
-        "@solana/codecs-data-structures": "2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb",
-        "@solana/codecs-numbers": "2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb",
-        "@solana/codecs-strings": "2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb",
-        "@solana/options": "2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb",
+        "@solana/codecs": "2.0.0-preview.1",
+        "@solana/options": "2.0.0-preview.1",
         "@solana/spl-type-length-value": "0.1.0"
     },
     "devDependencies": {
diff --git a/token-group/js/src/instruction.ts b/token-group/js/src/instruction.ts
index 7f431b7a4..271eb4b4c 100644
--- a/token-group/js/src/instruction.ts
+++ b/token-group/js/src/instruction.ts
@@ -1,13 +1,18 @@
-import type { Encoder } from '@solana/codecs-core';
+import type { Encoder } from '@solana/codecs';
 import type { PublicKey } from '@solana/web3.js';
-import { getBytesEncoder, getStructEncoder } from '@solana/codecs-data-structures';
-import { getU32Encoder } from '@solana/codecs-numbers';
+import { getBytesEncoder, getStructEncoder, getTupleEncoder, getU32Encoder, mapEncoder } from '@solana/codecs';
 import { splDiscriminate } from '@solana/spl-type-length-value';
-import { TransactionInstruction } from '@solana/web3.js';
+import { SystemProgram, TransactionInstruction } from '@solana/web3.js';
 
-function packInstruction<T extends object>(encoder: Encoder<T>, discriminator: Uint8Array, values: T): Buffer {
-    const data = encoder.encode(values);
-    return Buffer.concat([discriminator, data]);
+function getInstructionEncoder<T extends object>(discriminator: Uint8Array, dataEncoder: Encoder<T>): Encoder<T> {
+    return mapEncoder(getTupleEncoder([getBytesEncoder(), dataEncoder]), (data: T): [Uint8Array, T] => [
+        discriminator,
+        data,
+    ]);
+}
+
+function getPublicKeyEncoder(): Encoder<PublicKey> {
+    return mapEncoder(getBytesEncoder({ size: 32 }), (publicKey: PublicKey) => publicKey.toBytes());
 }
 
 export interface InitializeGroupInstruction {
@@ -22,13 +27,6 @@ export interface InitializeGroupInstruction {
 export function createInitializeGroupInstruction(args: InitializeGroupInstruction): TransactionInstruction {
     const { programId, group, mint, mintAuthority, updateAuthority, maxSize } = args;
 
-    const updateAuthorityBuffer = Buffer.alloc(32);
-    if (updateAuthority) {
-        updateAuthorityBuffer.set(updateAuthority.toBuffer());
-    } else {
-        updateAuthorityBuffer.fill(0);
-    }
-
     return new TransactionInstruction({
         programId,
         keys: [
@@ -36,13 +34,14 @@ export function createInitializeGroupInstruction(args: InitializeGroupInstructio
             { isSigner: false, isWritable: false, pubkey: mint },
             { isSigner: true, isWritable: false, pubkey: mintAuthority },
         ],
-        data: packInstruction(
-            getStructEncoder([
-                ['updateAuthority', getBytesEncoder({ size: 32 })],
-                ['maxSize', getU32Encoder()],
-            ]),
-            splDiscriminate('spl_token_group_interface:initialize_token_group'),
-            { updateAuthority: updateAuthorityBuffer, maxSize }
+        data: Buffer.from(
+            getInstructionEncoder(
+                splDiscriminate('spl_token_group_interface:initialize_token_group'),
+                getStructEncoder([
+                    ['updateAuthority', getPublicKeyEncoder()],
+                    ['maxSize', getU32Encoder()],
+                ])
+            ).encode({ updateAuthority: updateAuthority ?? SystemProgram.programId, maxSize })
         ),
     });
 }
@@ -62,10 +61,11 @@ export function createUpdateGroupMaxSizeInstruction(args: UpdateGroupMaxSize): T
             { isSigner: false, isWritable: true, pubkey: group },
             { isSigner: true, isWritable: false, pubkey: updateAuthority },
         ],
-        data: packInstruction(
-            getStructEncoder([['maxSize', getU32Encoder()]]),
-            splDiscriminate('spl_token_group_interface:update_group_max_size'),
-            { maxSize }
+        data: Buffer.from(
+            getInstructionEncoder(
+                splDiscriminate('spl_token_group_interface:update_group_max_size'),
+                getStructEncoder([['maxSize', getU32Encoder()]])
+            ).encode({ maxSize })
         ),
     });
 }
@@ -80,23 +80,17 @@ export interface UpdateGroupAuthority {
 export function createUpdateGroupAuthorityInstruction(args: UpdateGroupAuthority): TransactionInstruction {
     const { programId, group, currentAuthority, newAuthority } = args;
 
-    const newAuthorityBuffer = Buffer.alloc(32);
-    if (newAuthority) {
-        newAuthorityBuffer.set(newAuthority.toBuffer());
-    } else {
-        newAuthorityBuffer.fill(0);
-    }
-
     return new TransactionInstruction({
         programId,
         keys: [
             { isSigner: false, isWritable: true, pubkey: group },
             { isSigner: true, isWritable: false, pubkey: currentAuthority },
         ],
-        data: packInstruction(
-            getStructEncoder([['newAuthority', getBytesEncoder({ size: 32 })]]),
-            splDiscriminate('spl_token_group_interface:update_authority'),
-            { newAuthority: newAuthorityBuffer }
+        data: Buffer.from(
+            getInstructionEncoder(
+                splDiscriminate('spl_token_group_interface:update_authority'),
+                getStructEncoder([['newAuthority', getPublicKeyEncoder()]])
+            ).encode({ newAuthority: newAuthority ?? SystemProgram.programId })
         ),
     });
 }
@@ -122,6 +116,11 @@ export function createInitializeMemberInstruction(args: InitializeMember): Trans
             { isSigner: false, isWritable: true, pubkey: group },
             { isSigner: true, isWritable: false, pubkey: groupUpdateAuthority },
         ],
-        data: packInstruction(getStructEncoder([]), splDiscriminate('spl_token_group_interface:initialize_member'), {}),
+        data: Buffer.from(
+            getInstructionEncoder(
+                splDiscriminate('spl_token_group_interface:initialize_member'),
+                getStructEncoder([])
+            ).encode({})
+        ),
     });
 }
diff --git a/token-group/js/src/state/tokenGroup.ts b/token-group/js/src/state/tokenGroup.ts
index ed4508431..b03bd70e8 100644
--- a/token-group/js/src/state/tokenGroup.ts
+++ b/token-group/js/src/state/tokenGroup.ts
@@ -1,6 +1,5 @@
 import { PublicKey } from '@solana/web3.js';
-import { getBytesCodec, getStructCodec } from '@solana/codecs-data-structures';
-import { getU32Codec } from '@solana/codecs-numbers';
+import { getBytesCodec, getStructCodec, getU32Codec } from '@solana/codecs';
 
 const tokenGroupCodec = getStructCodec([
     ['updateAuthority', getBytesCodec({ size: 32 })],
diff --git a/token-group/js/src/state/tokenGroupMember.ts b/token-group/js/src/state/tokenGroupMember.ts
index 2bc4d3e2e..cb34ea89e 100644
--- a/token-group/js/src/state/tokenGroupMember.ts
+++ b/token-group/js/src/state/tokenGroupMember.ts
@@ -1,6 +1,5 @@
 import { PublicKey } from '@solana/web3.js';
-import { getBytesCodec, getStructCodec } from '@solana/codecs-data-structures';
-import { getU32Codec } from '@solana/codecs-numbers';
+import { getBytesCodec, getStructCodec, getU32Codec } from '@solana/codecs';
 
 const tokenGroupMemberCodec = getStructCodec([
     ['mint', getBytesCodec({ size: 32 })],
diff --git a/token-metadata/js/package.json b/token-metadata/js/package.json
index 722116cab..a1a391f4a 100644
--- a/token-metadata/js/package.json
+++ b/token-metadata/js/package.json
@@ -47,11 +47,8 @@
         "@solana/web3.js": "^1.91.0"
     },
     "dependencies": {
-        "@solana/codecs-core": "2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb",
-        "@solana/codecs-data-structures": "2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb",
-        "@solana/codecs-numbers": "2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb",
-        "@solana/codecs-strings": "2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb",
-        "@solana/options": "2.0.0-preview.1.20240308041540.58947c719d61c07ce3b1ac04edf51a2b8fa4a3eb",
+        "@solana/codecs": "2.0.0-preview.1",
+        "@solana/options": "2.0.0-preview.1",
         "@solana/spl-type-length-value": "0.1.0"
     },
     "devDependencies": {
diff --git a/token-metadata/js/src/field.ts b/token-metadata/js/src/field.ts
index e6c4aff4b..3e9c7aabe 100644
--- a/token-metadata/js/src/field.ts
+++ b/token-metadata/js/src/field.ts
@@ -1,6 +1,5 @@
-import type { Codec } from '@solana/codecs-core';
-import { getStructCodec, getTupleCodec, getUnitCodec } from '@solana/codecs-data-structures';
-import { getStringCodec } from '@solana/codecs-strings';
+import type { Codec } from '@solana/codecs';
+import { getStringCodec, getStructCodec, getTupleCodec, getUnitCodec } from '@solana/codecs';
 
 export enum Field {
     Name,
diff --git a/token-metadata/js/src/instruction.ts b/token-metadata/js/src/instruction.ts
index 15ab034e3..fcb429873 100644
--- a/token-metadata/js/src/instruction.ts
+++ b/token-metadata/js/src/instruction.ts
@@ -1,18 +1,31 @@
-import type { Encoder } from '@solana/codecs-core';
-import { getBooleanEncoder, getBytesEncoder, getDataEnumCodec, getStructEncoder } from '@solana/codecs-data-structures';
-import { getU64Encoder } from '@solana/codecs-numbers';
-import { getStringEncoder } from '@solana/codecs-strings';
+import type { Encoder } from '@solana/codecs';
+import {
+    getBooleanEncoder,
+    getBytesEncoder,
+    getDataEnumCodec,
+    getStringEncoder,
+    getStructEncoder,
+    getTupleEncoder,
+    getU64Encoder,
+    mapEncoder,
+} from '@solana/codecs';
 import { getOptionEncoder } from '@solana/options';
 import { splDiscriminate } from '@solana/spl-type-length-value';
 import type { PublicKey } from '@solana/web3.js';
-import { TransactionInstruction } from '@solana/web3.js';
+import { SystemProgram, TransactionInstruction } from '@solana/web3.js';
 
 import type { Field } from './field.js';
 import { getFieldCodec, getFieldConfig } from './field.js';
 
-function packInstruction<T extends object>(encoder: Encoder<T>, discriminator: Uint8Array, values: T): Buffer {
-    const data = encoder.encode(values);
-    return Buffer.concat([discriminator, data]);
+function getInstructionEncoder<T extends object>(discriminator: Uint8Array, dataEncoder: Encoder<T>): Encoder<T> {
+    return mapEncoder(getTupleEncoder([getBytesEncoder(), dataEncoder]), (data: T): [Uint8Array, T] => [
+        discriminator,
+        data,
+    ]);
+}
+
+function getPublicKeyEncoder(): Encoder<PublicKey> {
+    return mapEncoder(getBytesEncoder({ size: 32 }), (publicKey: PublicKey) => publicKey.toBytes());
 }
 
 /**
@@ -43,14 +56,15 @@ export function createInitializeInstruction(args: InitializeInstructionArgs): Tr
             { isSigner: false, isWritable: false, pubkey: mint },
             { isSigner: true, isWritable: false, pubkey: mintAuthority },
         ],
-        data: packInstruction(
-            getStructEncoder([
-                ['name', getStringEncoder()],
-                ['symbol', getStringEncoder()],
-                ['uri', getStringEncoder()],
-            ]),
-            splDiscriminate('spl_token_metadata_interface:initialize_account'),
-            { name, symbol, uri }
+        data: Buffer.from(
+            getInstructionEncoder(
+                splDiscriminate('spl_token_metadata_interface:initialize_account'),
+                getStructEncoder([
+                    ['name', getStringEncoder()],
+                    ['symbol', getStringEncoder()],
+                    ['uri', getStringEncoder()],
+                ])
+            ).encode({ name, symbol, uri })
         ),
     });
 }
@@ -75,13 +89,14 @@ export function createUpdateFieldInstruction(args: UpdateFieldInstruction): Tran
             { isSigner: false, isWritable: true, pubkey: metadata },
             { isSigner: true, isWritable: false, pubkey: updateAuthority },
         ],
-        data: packInstruction(
-            getStructEncoder([
-                ['field', getDataEnumCodec(getFieldCodec())],
-                ['value', getStringEncoder()],
-            ]),
-            splDiscriminate('spl_token_metadata_interface:updating_field'),
-            { field: getFieldConfig(field), value }
+        data: Buffer.from(
+            getInstructionEncoder(
+                splDiscriminate('spl_token_metadata_interface:updating_field'),
+                getStructEncoder([
+                    ['field', getDataEnumCodec(getFieldCodec())],
+                    ['value', getStringEncoder()],
+                ])
+            ).encode({ field: getFieldConfig(field), value })
         ),
     });
 }
@@ -102,13 +117,14 @@ export function createRemoveKeyInstruction(args: RemoveKeyInstructionArgs) {
             { isSigner: false, isWritable: true, pubkey: metadata },
             { isSigner: true, isWritable: false, pubkey: updateAuthority },
         ],
-        data: packInstruction(
-            getStructEncoder([
-                ['idempotent', getBooleanEncoder()],
-                ['key', getStringEncoder()],
-            ]),
-            splDiscriminate('spl_token_metadata_interface:remove_key_ix'),
-            { idempotent, key }
+        data: Buffer.from(
+            getInstructionEncoder(
+                splDiscriminate('spl_token_metadata_interface:remove_key_ix'),
+                getStructEncoder([
+                    ['idempotent', getBooleanEncoder()],
+                    ['key', getStringEncoder()],
+                ])
+            ).encode({ idempotent, key })
         ),
     });
 }
@@ -123,23 +139,17 @@ export interface UpdateAuthorityInstructionArgs {
 export function createUpdateAuthorityInstruction(args: UpdateAuthorityInstructionArgs): TransactionInstruction {
     const { programId, metadata, oldAuthority, newAuthority } = args;
 
-    const newAuthorityBuffer = Buffer.alloc(32);
-    if (newAuthority) {
-        newAuthorityBuffer.set(newAuthority.toBuffer());
-    } else {
-        newAuthorityBuffer.fill(0);
-    }
-
     return new TransactionInstruction({
         programId,
         keys: [
             { isSigner: false, isWritable: true, pubkey: metadata },
             { isSigner: true, isWritable: false, pubkey: oldAuthority },
         ],
-        data: packInstruction(
-            getStructEncoder([['newAuthority', getBytesEncoder({ size: 32 })]]),
-            splDiscriminate('spl_token_metadata_interface:update_the_authority'),
-            { newAuthority: newAuthorityBuffer }
+        data: Buffer.from(
+            getInstructionEncoder(
+                splDiscriminate('spl_token_metadata_interface:update_the_authority'),
+                getStructEncoder([['newAuthority', getPublicKeyEncoder()]])
+            ).encode({ newAuthority: newAuthority ?? SystemProgram.programId })
         ),
     });
 }
@@ -156,13 +166,14 @@ export function createEmitInstruction(args: EmitInstructionArgs): TransactionIns
     return new TransactionInstruction({
         programId,
         keys: [{ isSigner: false, isWritable: false, pubkey: metadata }],
-        data: packInstruction(
-            getStructEncoder([
-                ['start', getOptionEncoder(getU64Encoder())],
-                ['end', getOptionEncoder(getU64Encoder())],
-            ]),
-            splDiscriminate('spl_token_metadata_interface:emitter'),
-            { start: start ?? null, end: end ?? null }
+        data: Buffer.from(
+            getInstructionEncoder(
+                splDiscriminate('spl_token_metadata_interface:emitter'),
+                getStructEncoder([
+                    ['start', getOptionEncoder(getU64Encoder())],
+                    ['end', getOptionEncoder(getU64Encoder())],
+                ])
+            ).encode({ start: start ?? null, end: end ?? null })
         ),
     });
 }
diff --git a/token-metadata/js/src/state.ts b/token-metadata/js/src/state.ts
index d26c3b371..5583a6bae 100644
--- a/token-metadata/js/src/state.ts
+++ b/token-metadata/js/src/state.ts
@@ -1,6 +1,5 @@
 import { PublicKey } from '@solana/web3.js';
-import { getArrayCodec, getBytesCodec, getStructCodec, getTupleCodec } from '@solana/codecs-data-structures';
-import { getStringCodec } from '@solana/codecs-strings';
+import { getArrayCodec, getBytesCodec, getStringCodec, getStructCodec, getTupleCodec } from '@solana/codecs';
 
 export const TOKEN_METADATA_DISCRIMINATOR = Buffer.from([112, 132, 90, 90, 11, 88, 157, 87]);
 


Test patch:
diff --git a/token-group/js/test/instruction.test.ts b/token-group/js/test/instruction.test.ts
index b319813b8..0d32f63a7 100644
--- a/token-group/js/test/instruction.test.ts
+++ b/token-group/js/test/instruction.test.ts
@@ -1,8 +1,7 @@
 import { expect } from 'chai';
-import type { Decoder } from '@solana/codecs-core';
-import { getBytesDecoder, getStructDecoder } from '@solana/codecs-data-structures';
+import type { Decoder } from '@solana/codecs';
+import { getBytesDecoder, getStructDecoder, getU32Decoder } from '@solana/codecs';
 import { splDiscriminate } from '@solana/spl-type-length-value';
-import { getU32Decoder } from '@solana/codecs-numbers';
 import { PublicKey, type TransactionInstruction } from '@solana/web3.js';
 
 import {
diff --git a/token-metadata/js/test/instruction.test.ts b/token-metadata/js/test/instruction.test.ts
index 1b0c8c4d8..b9826c06d 100644
--- a/token-metadata/js/test/instruction.test.ts
+++ b/token-metadata/js/test/instruction.test.ts
@@ -9,11 +9,16 @@ import {
     getFieldCodec,
     getFieldConfig,
 } from '../src';
-import { getBooleanDecoder, getBytesDecoder, getDataEnumCodec, getStructDecoder } from '@solana/codecs-data-structures';
-import { getStringDecoder } from '@solana/codecs-strings';
+import {
+    getBooleanDecoder,
+    getBytesDecoder,
+    getDataEnumCodec,
+    getStringDecoder,
+    getU64Decoder,
+    getStructDecoder,
+} from '@solana/codecs';
 import { splDiscriminate } from '@solana/spl-type-length-value';
-import { getU64Decoder } from '@solana/codecs-numbers';
-import type { Decoder } from '@solana/codecs-core';
+import type { Decoder } from '@solana/codecs';
 import type { Option } from '@solana/options';
 import { getOptionDecoder, some } from '@solana/options';
 import { PublicKey, type TransactionInstruction } from '@solana/web3.js';