Repository: solana-labs/solana-program-library
Pull request #6665 (2024-04-30 10:52:22)

token-group-js: Update to tp3
#### Problem

web3.js TP3 is out, but the token-group JS library isn't using it.

#### Solution

Upgrade to the new lib and use it. Let me know if I'm following the right conventions! I looked at example usage from the repo directly for inspiration.


Patch:
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8c1f79c72..e4a81a4d1 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -489,8 +489,8 @@ importers:
   token-group/js:
     dependencies:
       '@solana/codecs':
-        specifier: 2.0.0-preview.2
-        version: 2.0.0-preview.2(fastestsmallesttextencoderdecoder@1.0.22)
+        specifier: 2.0.0-preview.3
+        version: 2.0.0-preview.3(fastestsmallesttextencoderdecoder@1.0.22)
       '@solana/spl-type-length-value':
         specifier: 0.1.0
         version: link:../../libraries/type-length-value/js
@@ -632,8 +632,8 @@ importers:
   token-metadata/js:
     dependencies:
       '@solana/codecs':
-        specifier: 2.0.0-preview.2
-        version: 2.0.0-preview.2(fastestsmallesttextencoderdecoder@1.0.22)
+        specifier: 2.0.0-preview.3
+        version: 2.0.0-preview.3(fastestsmallesttextencoderdecoder@1.0.22)
       '@solana/spl-type-length-value':
         specifier: 0.1.0
         version: link:../../libraries/type-length-value/js
@@ -773,7 +773,7 @@ importers:
         version: link:../../token-group/js
       '@solana/spl-token-metadata':
         specifier: ^0.1.3
-        version: 0.1.4(@solana/web3.js@1.91.7)(fastestsmallesttextencoderdecoder@1.0.22)
+        version: link:../../token-metadata/js
       buffer:
         specifier: ^6.0.3
         version: 6.0.3
@@ -2363,7 +2363,6 @@ packages:
     resolution: {integrity: sha512-xQz6USSBs82lUNoVa/wwnm6wa2y2eWtGwPLUwF/NOGGpR+QH9EODijXvJ8wuC9llyqerqdC+5mrmx9C8VSMNYg==}
     dependencies:
       '@solana/errors': 2.0.0-preview.3
-    dev: true
 
   /@solana/codecs-data-structures@2.0.0-preview.2:
     resolution: {integrity: sha512-Xf5vIfromOZo94Q8HbR04TbgTwzigqrKII0GjYr21K7rb3nba4hUW2ir8kguY7HWFBcjHGlU5x3MevKBOLp3Zg==}
@@ -2372,6 +2371,14 @@ packages:
       '@solana/codecs-numbers': 2.0.0-preview.2
       '@solana/errors': 2.0.0-preview.2
 
+  /@solana/codecs-data-structures@2.0.0-preview.3:
+    resolution: {integrity: sha512-PfXvZCf9qDF+Dv4WG6cb4xZoY9tj117bmZWS17iKimuNSsvuFSHpzERy0mmX2hwYEAM4CnQBd/9dgx+eAeMAsg==}
+    dependencies:
+      '@solana/codecs-core': 2.0.0-preview.3
+      '@solana/codecs-numbers': 2.0.0-preview.3
+      '@solana/errors': 2.0.0-preview.3
+    dev: false
+
   /@solana/codecs-numbers@2.0.0-preview.2:
     resolution: {integrity: sha512-aLZnDTf43z4qOnpTcDsUVy1Ci9im1Md8thWipSWbE+WM9ojZAx528oAql+Cv8M8N+6ALKwgVRhPZkto6E59ARw==}
     dependencies:
@@ -2383,7 +2390,6 @@ packages:
     dependencies:
       '@solana/codecs-core': 2.0.0-preview.3
       '@solana/errors': 2.0.0-preview.3
-    dev: true
 
   /@solana/codecs-strings@2.0.0-preview.2(fastestsmallesttextencoderdecoder@1.0.22):
     resolution: {integrity: sha512-EgBwY+lIaHHgMJIqVOGHfIfpdmmUDNoNO/GAUGeFPf+q0dF+DtwhJPEMShhzh64X2MeCZcmSO6Kinx0Bvmmz2g==}
@@ -2404,7 +2410,6 @@ packages:
       '@solana/codecs-numbers': 2.0.0-preview.3
       '@solana/errors': 2.0.0-preview.3
       fastestsmallesttextencoderdecoder: 1.0.22
-    dev: true
 
   /@solana/codecs@2.0.0-preview.2(fastestsmallesttextencoderdecoder@1.0.22):
     resolution: {integrity: sha512-4HHzCD5+pOSmSB71X6w9ptweV48Zj1Vqhe732+pcAQ2cMNnN0gMPMdDq7j3YwaZDZ7yrILVV/3+HTnfT77t2yA==}
@@ -2417,6 +2422,18 @@ packages:
     transitivePeerDependencies:
       - fastestsmallesttextencoderdecoder
 
+  /@solana/codecs@2.0.0-preview.3(fastestsmallesttextencoderdecoder@1.0.22):
+    resolution: {integrity: sha512-uB0GMAY1VrNoJxZ9S4F1RBL57gI+8YwxnV9DD5EP5rU8iD7Wq4wbaB2IPcENyJi7rmzytIjKJg0MI6i2bBr+0w==}
+    dependencies:
+      '@solana/codecs-core': 2.0.0-preview.3
+      '@solana/codecs-data-structures': 2.0.0-preview.3
+      '@solana/codecs-numbers': 2.0.0-preview.3
+      '@solana/codecs-strings': 2.0.0-preview.3(fastestsmallesttextencoderdecoder@1.0.22)
+      '@solana/options': 2.0.0-preview.3(fastestsmallesttextencoderdecoder@1.0.22)
+    transitivePeerDependencies:
+      - fastestsmallesttextencoderdecoder
+    dev: false
+
   /@solana/errors@2.0.0-preview.2:
     resolution: {integrity: sha512-H2DZ1l3iYF5Rp5pPbJpmmtCauWeQXRJapkDg8epQ8BJ7cA2Ut/QEtC3CMmw/iMTcuS6uemFNLcWvlOfoQhvQuA==}
     hasBin: true
@@ -2430,7 +2447,6 @@ packages:
     dependencies:
       chalk: 5.3.0
       commander: 12.0.0
-    dev: true
 
   /@solana/eslint-config-solana@3.0.3(@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.5):
     resolution: {integrity: sha512-yTaeCbOBwjmK4oUkknixOpwOzzAK8+4YWvJEJFNHuueESetieDnAeEHV7rzJllFgHEWa9nXps9Q3aD4/XJp71A==}
@@ -2500,6 +2516,18 @@ packages:
       '@solana/codecs-core': 2.0.0-preview.2
       '@solana/codecs-numbers': 2.0.0-preview.2
 
+  /@solana/options@2.0.0-preview.3(fastestsmallesttextencoderdecoder@1.0.22):
+    resolution: {integrity: sha512-tT5O1CCJVE+rzo4VeeivYLNUL4L/2BjIeiy0MRh04lPxieiR346vUOPC1uCWGD6WqyTOOVUL0tsY4saYLmCTtA==}
+    dependencies:
+      '@solana/codecs-core': 2.0.0-preview.3
+      '@solana/codecs-data-structures': 2.0.0-preview.3
+      '@solana/codecs-numbers': 2.0.0-preview.3
+      '@solana/codecs-strings': 2.0.0-preview.3(fastestsmallesttextencoderdecoder@1.0.22)
+      '@solana/errors': 2.0.0-preview.3
+    transitivePeerDependencies:
+      - fastestsmallesttextencoderdecoder
+    dev: false
+
   /@solana/prettier-config-solana@0.0.5(prettier@3.2.5):
     resolution: {integrity: sha512-igtLH1QaX5xzSLlqteexRIg9X1QKA03xKYQc2qY1TrMDDhxKXoRZOStQPWdita2FVJzxTGz/tdMGC1vS0biRcg==}
     peerDependencies:
@@ -2923,6 +2951,14 @@ packages:
       '@typescript-eslint/visitor-keys': 7.6.0
     dev: true
 
+  /@typescript-eslint/scope-manager@7.7.1:
+    resolution: {integrity: sha512-PytBif2SF+9SpEUKynYn5g1RHFddJUcyynGpztX3l/ik7KmZEv19WCMhUBkHXPU9es/VWGD3/zg3wg90+Dh2rA==}
+    engines: {node: ^18.18.0 || >=20.0.0}
+    dependencies:
+      '@typescript-eslint/types': 7.7.1
+      '@typescript-eslint/visitor-keys': 7.7.1
+    dev: true
+
   /@typescript-eslint/scope-manager@7.8.0:
     resolution: {integrity: sha512-viEmZ1LmwsGcnr85gIq+FCYI7nO90DVbE37/ll51hjv9aG+YZMb4WDE2fyWpUR4O/UrhGRpYXK/XajcGTk2B8g==}
     engines: {node: ^18.18.0 || >=20.0.0}
@@ -2951,6 +2987,26 @@ packages:
       - supports-color
     dev: true
 
+  /@typescript-eslint/type-utils@7.7.1(eslint@8.57.0)(typescript@5.4.5):
+    resolution: {integrity: sha512-ZksJLW3WF7o75zaBPScdW1Gbkwhd/lyeXGf1kQCxJaOeITscoSl0MjynVvCzuV5boUz/3fOI06Lz8La55mu29Q==}
+    engines: {node: ^18.18.0 || >=20.0.0}
+    peerDependencies:
+      eslint: ^8.56.0
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@typescript-eslint/typescript-estree': 7.7.1(typescript@5.4.5)
+      '@typescript-eslint/utils': 7.7.1(eslint@8.57.0)(typescript@5.4.5)
+      debug: 4.3.4(supports-color@8.1.1)
+      eslint: 8.57.0
+      ts-api-utils: 1.3.0(typescript@5.4.5)
+      typescript: 5.4.5
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /@typescript-eslint/type-utils@7.8.0(eslint@8.57.0)(typescript@5.4.5):
     resolution: {integrity: sha512-H70R3AefQDQpz9mGv13Uhi121FNMh+WEaRqcXTX09YEDky21km4dV1ZXJIp8QjXc4ZaVkXVdohvWDzbnbHDS+A==}
     engines: {node: ^18.18.0 || >=20.0.0}
@@ -2986,6 +3042,11 @@ packages:
     engines: {node: ^18.18.0 || >=20.0.0}
     dev: true
 
+  /@typescript-eslint/types@7.7.1:
+    resolution: {integrity: sha512-AmPmnGW1ZLTpWa+/2omPrPfR7BcbUU4oha5VIbSbS1a1Tv966bklvLNXxp3mrbc+P2j4MNOTfDffNsk4o0c6/w==}
+    engines: {node: ^18.18.0 || >=20.0.0}
+    dev: true
+
   /@typescript-eslint/types@7.8.0:
     resolution: {integrity: sha512-wf0peJ+ZGlcH+2ZS23aJbOv+ztjeeP8uQ9GgwMJGVLx/Nj9CJt17GWgWWoSmoRVKAX2X+7fzEnAjxdvK2gqCLw==}
     engines: {node: ^18.18.0 || >=20.0.0}
@@ -3056,6 +3117,28 @@ packages:
       - supports-color
     dev: true
 
+  /@typescript-eslint/typescript-estree@7.7.1(typescript@5.4.5):
+    resolution: {integrity: sha512-CXe0JHCXru8Fa36dteXqmH2YxngKJjkQLjxzoj6LYwzZ7qZvgsLSc+eqItCrqIop8Vl2UKoAi0StVWu97FQZIQ==}
+    engines: {node: ^18.18.0 || >=20.0.0}
+    peerDependencies:
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@typescript-eslint/types': 7.7.1
+      '@typescript-eslint/visitor-keys': 7.7.1
+      debug: 4.3.4(supports-color@8.1.1)
+      globby: 11.1.0
+      is-glob: 4.0.3
+      minimatch: 9.0.4
+      semver: 7.6.0
+      ts-api-utils: 1.3.0(typescript@5.4.5)
+      typescript: 5.4.5
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /@typescript-eslint/typescript-estree@7.8.0(typescript@5.4.5):
     resolution: {integrity: sha512-5pfUCOwK5yjPaJQNy44prjCwtr981dO8Qo9J9PwYXZ0MosgAbfEMB008dJ5sNo3+/BN6ytBPuSvXUg9SAqB0dg==}
     engines: {node: ^18.18.0 || >=20.0.0}
@@ -3136,6 +3219,25 @@ packages:
       - typescript
     dev: true
 
+  /@typescript-eslint/utils@7.7.1(eslint@8.57.0)(typescript@5.4.5):
+    resolution: {integrity: sha512-QUvBxPEaBXf41ZBbaidKICgVL8Hin0p6prQDu6bbetWo39BKbWJxRsErOzMNT1rXvTll+J7ChrbmMCXM9rsvOQ==}
+    engines: {node: ^18.18.0 || >=20.0.0}
+    peerDependencies:
+      eslint: ^8.56.0
+    dependencies:
+      '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
+      '@types/json-schema': 7.0.15
+      '@types/semver': 7.5.8
+      '@typescript-eslint/scope-manager': 7.7.1
+      '@typescript-eslint/types': 7.7.1
+      '@typescript-eslint/typescript-estree': 7.7.1(typescript@5.4.5)
+      eslint: 8.57.0
+      semver: 7.6.0
+    transitivePeerDependencies:
+      - supports-color
+      - typescript
+    dev: true
+
   /@typescript-eslint/utils@7.8.0(eslint@8.57.0)(typescript@5.4.5):
     resolution: {integrity: sha512-L0yFqOCflVqXxiZyXrDr80lnahQfSOfc9ELAAZ75sqicqp2i36kEZZGuUymHNFoYOqxRT05up760b4iGsl02nQ==}
     engines: {node: ^18.18.0 || >=20.0.0}
@@ -3179,6 +3281,14 @@ packages:
       eslint-visitor-keys: 3.4.3
     dev: true
 
+  /@typescript-eslint/visitor-keys@7.7.1:
+    resolution: {integrity: sha512-gBL3Eq25uADw1LQ9kVpf3hRM+DWzs0uZknHYK3hq4jcTPqVCClHGDnB6UUUV2SFeBeA4KWHWbbLqmbGcZ4FYbw==}
+    engines: {node: ^18.18.0 || >=20.0.0}
+    dependencies:
+      '@typescript-eslint/types': 7.7.1
+      eslint-visitor-keys: 3.4.3
+    dev: true
+
   /@typescript-eslint/visitor-keys@7.8.0:
     resolution: {integrity: sha512-q4/gibTNBQNA0lGyYQCmWRS5D15n8rXh4QjK3KV+MBPlTYHpfBUT3D3PaPR/HeNiI9W6R7FvlkcGhNyAoP+caA==}
     engines: {node: ^18.18.0 || >=20.0.0}
@@ -5694,7 +5804,7 @@ packages:
       eslint: '*'
       typescript: '>=4.7.4'
     dependencies:
-      '@typescript-eslint/type-utils': 7.8.0(eslint@8.57.0)(typescript@5.4.5)
+      '@typescript-eslint/type-utils': 7.7.1(eslint@8.57.0)(typescript@5.4.5)
       eslint: 8.57.0
       ts-api-utils: 1.3.0(typescript@5.4.5)
       ts-declaration-location: 1.0.0(typescript@5.4.5)
diff --git a/token-group/js/package.json b/token-group/js/package.json
index f569d0d1d..bd0233871 100644
--- a/token-group/js/package.json
+++ b/token-group/js/package.json
@@ -47,7 +47,7 @@
         "@solana/web3.js": "^1.91.7"
     },
     "dependencies": {
-        "@solana/codecs": "2.0.0-preview.2",
+        "@solana/codecs": "2.0.0-preview.3",
         "@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 271eb4b4c..ab08a770d 100644
--- a/token-group/js/src/instruction.ts
+++ b/token-group/js/src/instruction.ts
@@ -1,18 +1,25 @@
 import type { Encoder } from '@solana/codecs';
 import type { PublicKey } from '@solana/web3.js';
-import { getBytesEncoder, getStructEncoder, getTupleEncoder, getU32Encoder, mapEncoder } from '@solana/codecs';
+import {
+    fixEncoderSize,
+    getBytesEncoder,
+    getStructEncoder,
+    getTupleEncoder,
+    getU32Encoder,
+    transformEncoder,
+} from '@solana/codecs';
 import { splDiscriminate } from '@solana/spl-type-length-value';
 import { SystemProgram, TransactionInstruction } from '@solana/web3.js';
 
 function getInstructionEncoder<T extends object>(discriminator: Uint8Array, dataEncoder: Encoder<T>): Encoder<T> {
-    return mapEncoder(getTupleEncoder([getBytesEncoder(), dataEncoder]), (data: T): [Uint8Array, T] => [
+    return transformEncoder(getTupleEncoder([getBytesEncoder(), dataEncoder]), (data: T): [Uint8Array, T] => [
         discriminator,
         data,
     ]);
 }
 
 function getPublicKeyEncoder(): Encoder<PublicKey> {
-    return mapEncoder(getBytesEncoder({ size: 32 }), (publicKey: PublicKey) => publicKey.toBytes());
+    return transformEncoder(fixEncoderSize(getBytesEncoder(), 32), (publicKey: PublicKey) => publicKey.toBytes());
 }
 
 export interface InitializeGroupInstruction {
diff --git a/token-group/js/src/state/tokenGroup.ts b/token-group/js/src/state/tokenGroup.ts
index ca42bac2b..ecc512542 100644
--- a/token-group/js/src/state/tokenGroup.ts
+++ b/token-group/js/src/state/tokenGroup.ts
@@ -1,9 +1,10 @@
 import { PublicKey } from '@solana/web3.js';
-import { getBytesCodec, getStructCodec, getU32Codec } from '@solana/codecs';
+import type { ReadonlyUint8Array } from '@solana/codecs';
+import { fixCodecSize, getBytesCodec, getStructCodec, getU32Codec } from '@solana/codecs';
 
 const tokenGroupCodec = getStructCodec([
-    ['updateAuthority', getBytesCodec({ size: 32 })],
-    ['mint', getBytesCodec({ size: 32 })],
+    ['updateAuthority', fixCodecSize(getBytesCodec(), 32)],
+    ['mint', fixCodecSize(getBytesCodec(), 32)],
     ['size', getU32Codec()],
     ['maxSize', getU32Codec()],
 ]);
@@ -22,7 +23,7 @@ export interface TokenGroup {
 }
 
 // Checks if all elements in the array are 0
-function isNonePubkey(buffer: Uint8Array): boolean {
+function isNonePubkey(buffer: ReadonlyUint8Array): boolean {
     for (let i = 0; i < buffer.length; i++) {
         if (buffer[i] !== 0) {
             return false;
@@ -32,7 +33,7 @@ function isNonePubkey(buffer: Uint8Array): boolean {
 }
 
 // Pack TokenGroup into byte slab
-export function packTokenGroup(group: TokenGroup): Uint8Array {
+export function packTokenGroup(group: TokenGroup): ReadonlyUint8Array {
     // If no updateAuthority given, set it to the None/Zero PublicKey for encoding
     const updateAuthority = group.updateAuthority ?? PublicKey.default;
     return tokenGroupCodec.encode({
@@ -44,7 +45,7 @@ export function packTokenGroup(group: TokenGroup): Uint8Array {
 }
 
 // unpack byte slab into TokenGroup
-export function unpackTokenGroup(buffer: Buffer | Uint8Array): TokenGroup {
+export function unpackTokenGroup(buffer: Buffer | Uint8Array | ReadonlyUint8Array): TokenGroup {
     const data = tokenGroupCodec.decode(buffer);
 
     return isNonePubkey(data.updateAuthority)
diff --git a/token-group/js/src/state/tokenGroupMember.ts b/token-group/js/src/state/tokenGroupMember.ts
index e6c07aff6..8d49a0316 100644
--- a/token-group/js/src/state/tokenGroupMember.ts
+++ b/token-group/js/src/state/tokenGroupMember.ts
@@ -1,9 +1,10 @@
 import { PublicKey } from '@solana/web3.js';
-import { getBytesCodec, getStructCodec, getU32Codec } from '@solana/codecs';
+import type { ReadonlyUint8Array } from '@solana/codecs';
+import { fixCodecSize, getBytesCodec, getStructCodec, getU32Codec } from '@solana/codecs';
 
 const tokenGroupMemberCodec = getStructCodec([
-    ['mint', getBytesCodec({ size: 32 })],
-    ['group', getBytesCodec({ size: 32 })],
+    ['mint', fixCodecSize(getBytesCodec(), 32)],
+    ['group', fixCodecSize(getBytesCodec(), 32)],
     ['memberNumber', getU32Codec()],
 ]);
 
@@ -19,7 +20,7 @@ export interface TokenGroupMember {
 }
 
 // Pack TokenGroupMember into byte slab
-export function packTokenGroupMember(member: TokenGroupMember): Uint8Array {
+export function packTokenGroupMember(member: TokenGroupMember): ReadonlyUint8Array {
     return tokenGroupMemberCodec.encode({
         mint: member.mint.toBuffer(),
         group: member.group.toBuffer(),
@@ -28,7 +29,7 @@ export function packTokenGroupMember(member: TokenGroupMember): Uint8Array {
 }
 
 // unpack byte slab into TokenGroupMember
-export function unpackTokenGroupMember(buffer: Buffer | Uint8Array): TokenGroupMember {
+export function unpackTokenGroupMember(buffer: Buffer | Uint8Array | ReadonlyUint8Array): TokenGroupMember {
     const data = tokenGroupMemberCodec.decode(buffer);
     return {
         mint: new PublicKey(data.mint),
diff --git a/token-metadata/js/package.json b/token-metadata/js/package.json
index 91aeee418..cba823d55 100644
--- a/token-metadata/js/package.json
+++ b/token-metadata/js/package.json
@@ -47,7 +47,7 @@
         "@solana/web3.js": "^1.91.7"
     },
     "dependencies": {
-        "@solana/codecs": "2.0.0-preview.2",
+        "@solana/codecs": "2.0.0-preview.3",
         "@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 3e9c7aabe..58db19433 100644
--- a/token-metadata/js/src/field.ts
+++ b/token-metadata/js/src/field.ts
@@ -1,5 +1,12 @@
 import type { Codec } from '@solana/codecs';
-import { getStringCodec, getStructCodec, getTupleCodec, getUnitCodec } from '@solana/codecs';
+import {
+    addCodecSizePrefix,
+    getU32Codec,
+    getUtf8Codec,
+    getStructCodec,
+    getTupleCodec,
+    getUnitCodec,
+} from '@solana/codecs';
 
 export enum Field {
     Name,
@@ -14,7 +21,7 @@ export const getFieldCodec = () =>
         ['Name', getUnitCodec()],
         ['Symbol', getUnitCodec()],
         ['Uri', getUnitCodec()],
-        ['Key', getStructCodec([['value', getTupleCodec([getStringCodec()])]])],
+        ['Key', getStructCodec([['value', getTupleCodec([addCodecSizePrefix(getUtf8Codec(), getU32Codec())])]])],
     ] as const;
 
 export function getFieldConfig(field: Field | string): FieldLayout {
diff --git a/token-metadata/js/src/instruction.ts b/token-metadata/js/src/instruction.ts
index 10493bfb0..b428d4fef 100644
--- a/token-metadata/js/src/instruction.ts
+++ b/token-metadata/js/src/instruction.ts
@@ -1,15 +1,19 @@
 import type { Encoder } from '@solana/codecs';
 import {
+    addEncoderSizePrefix,
+    fixEncoderSize,
     getBooleanEncoder,
     getBytesEncoder,
     getDataEnumCodec,
     getOptionEncoder,
-    getStringEncoder,
+    getUtf8Encoder,
     getStructEncoder,
     getTupleEncoder,
+    getU32Encoder,
     getU64Encoder,
-    mapEncoder,
+    transformEncoder,
 } from '@solana/codecs';
+import type { VariableSizeEncoder } from '@solana/codecs';
 import { splDiscriminate } from '@solana/spl-type-length-value';
 import type { PublicKey } from '@solana/web3.js';
 import { SystemProgram, TransactionInstruction } from '@solana/web3.js';
@@ -18,14 +22,18 @@ import type { Field } from './field.js';
 import { getFieldCodec, getFieldConfig } from './field.js';
 
 function getInstructionEncoder<T extends object>(discriminator: Uint8Array, dataEncoder: Encoder<T>): Encoder<T> {
-    return mapEncoder(getTupleEncoder([getBytesEncoder(), dataEncoder]), (data: T): [Uint8Array, T] => [
+    return transformEncoder(getTupleEncoder([getBytesEncoder(), dataEncoder]), (data: T): [Uint8Array, T] => [
         discriminator,
         data,
     ]);
 }
 
 function getPublicKeyEncoder(): Encoder<PublicKey> {
-    return mapEncoder(getBytesEncoder({ size: 32 }), (publicKey: PublicKey) => publicKey.toBytes());
+    return transformEncoder(fixEncoderSize(getBytesEncoder(), 32), (publicKey: PublicKey) => publicKey.toBytes());
+}
+
+function getStringEncoder(): VariableSizeEncoder<string> {
+    return addEncoderSizePrefix(getUtf8Encoder(), getU32Encoder());
 }
 
 /**
diff --git a/token-metadata/js/src/state.ts b/token-metadata/js/src/state.ts
index 5583a6bae..6f689ebd8 100644
--- a/token-metadata/js/src/state.ts
+++ b/token-metadata/js/src/state.ts
@@ -1,11 +1,25 @@
 import { PublicKey } from '@solana/web3.js';
-import { getArrayCodec, getBytesCodec, getStringCodec, getStructCodec, getTupleCodec } from '@solana/codecs';
+import {
+    addCodecSizePrefix,
+    fixCodecSize,
+    getArrayCodec,
+    getBytesCodec,
+    getUtf8Codec,
+    getU32Codec,
+    getStructCodec,
+    getTupleCodec,
+} from '@solana/codecs';
+import type { ReadonlyUint8Array, VariableSizeCodec } from '@solana/codecs';
 
 export const TOKEN_METADATA_DISCRIMINATOR = Buffer.from([112, 132, 90, 90, 11, 88, 157, 87]);
 
+function getStringCodec(): VariableSizeCodec<string> {
+    return addCodecSizePrefix(getUtf8Codec(), getU32Codec());
+}
+
 const tokenMetadataCodec = getStructCodec([
-    ['updateAuthority', getBytesCodec({ size: 32 })],
-    ['mint', getBytesCodec({ size: 32 })],
+    ['updateAuthority', fixCodecSize(getBytesCodec(), 32)],
+    ['mint', fixCodecSize(getBytesCodec(), 32)],
     ['name', getStringCodec()],
     ['symbol', getStringCodec()],
     ['uri', getStringCodec()],
@@ -24,11 +38,11 @@ export interface TokenMetadata {
     // The URI pointing to richer metadata
     uri: string;
     // Any additional metadata about the token as key-value pairs
-    additionalMetadata: [string, string][];
+    additionalMetadata: (readonly [string, string])[];
 }
 
 // Checks if all elements in the array are 0
-function isNonePubkey(buffer: Uint8Array): boolean {
+function isNonePubkey(buffer: ReadonlyUint8Array): boolean {
     for (let i = 0; i < buffer.length; i++) {
         if (buffer[i] !== 0) {
             return false;
@@ -38,7 +52,7 @@ function isNonePubkey(buffer: Uint8Array): boolean {
 }
 
 // Pack TokenMetadata into byte slab
-export function pack(meta: TokenMetadata): Uint8Array {
+export function pack(meta: TokenMetadata): ReadonlyUint8Array {
     // If no updateAuthority given, set it to the None/Zero PublicKey for encoding
     const updateAuthority = meta.updateAuthority ?? PublicKey.default;
     return tokenMetadataCodec.encode({
@@ -49,7 +63,7 @@ export function pack(meta: TokenMetadata): Uint8Array {
 }
 
 // unpack byte slab into TokenMetadata
-export function unpack(buffer: Buffer | Uint8Array): TokenMetadata {
+export function unpack(buffer: Buffer | Uint8Array | ReadonlyUint8Array): TokenMetadata {
     const data = tokenMetadataCodec.decode(buffer);
 
     return isNonePubkey(data.updateAuthority)


Test patch:
diff --git a/token-group/js/test/instruction.test.ts b/token-group/js/test/instruction.test.ts
index 0d32f63a7..a0454cf4a 100644
--- a/token-group/js/test/instruction.test.ts
+++ b/token-group/js/test/instruction.test.ts
@@ -1,6 +1,6 @@
 import { expect } from 'chai';
 import type { Decoder } from '@solana/codecs';
-import { getBytesDecoder, getStructDecoder, getU32Decoder } from '@solana/codecs';
+import { fixDecoderSize, getBytesDecoder, getStructDecoder, getU32Decoder } from '@solana/codecs';
 import { splDiscriminate } from '@solana/spl-type-length-value';
 import { PublicKey, type TransactionInstruction } from '@solana/web3.js';
 
@@ -42,7 +42,7 @@ describe('Token Group Instructions', () => {
             }),
             splDiscriminate('spl_token_group_interface:initialize_token_group'),
             getStructDecoder([
-                ['updateAuthority', getBytesDecoder({ size: 32 })],
+                ['updateAuthority', fixDecoderSize(getBytesDecoder(), 32)],
                 ['maxSize', getU32Decoder()],
             ]),
             { updateAuthority: Uint8Array.from(updateAuthority.toBuffer()), maxSize }
@@ -72,7 +72,7 @@ describe('Token Group Instructions', () => {
                 newAuthority: PublicKey.default,
             }),
             splDiscriminate('spl_token_group_interface:update_authority'),
-            getStructDecoder([['newAuthority', getBytesDecoder({ size: 32 })]]),
+            getStructDecoder([['newAuthority', fixDecoderSize(getBytesDecoder(), 32)]]),
             { newAuthority: Uint8Array.from(PublicKey.default.toBuffer()) }
         );
     });
diff --git a/token-metadata/js/test/instruction.test.ts b/token-metadata/js/test/instruction.test.ts
index 442bbe4d8..e206393c7 100644
--- a/token-metadata/js/test/instruction.test.ts
+++ b/token-metadata/js/test/instruction.test.ts
@@ -10,17 +10,20 @@ import {
     getFieldConfig,
 } from '../src';
 import {
+    addDecoderSizePrefix,
+    fixDecoderSize,
     getBooleanDecoder,
     getBytesDecoder,
     getDataEnumCodec,
     getOptionDecoder,
-    getStringDecoder,
+    getUtf8Decoder,
+    getU32Decoder,
     getU64Decoder,
     getStructDecoder,
     some,
 } from '@solana/codecs';
 import { splDiscriminate } from '@solana/spl-type-length-value';
-import type { Decoder, Option } from '@solana/codecs';
+import type { Decoder, Option, VariableSizeDecoder } from '@solana/codecs';
 import { PublicKey, type TransactionInstruction } from '@solana/web3.js';
 
 function checkPackUnpack<T extends object>(
@@ -34,6 +37,10 @@ function checkPackUnpack<T extends object>(
     expect(unpacked).to.deep.equal(values);
 }
 
+function getStringDecoder(): VariableSizeDecoder<string> {
+    return addDecoderSizePrefix(getUtf8Decoder(), getU32Decoder());
+}
+
 describe('Token Metadata Instructions', () => {
     const programId = new PublicKey('22222222222222222222222222222222222222222222');
     const metadata = new PublicKey('33333333333333333333333333333333333333333333');
@@ -134,7 +141,7 @@ describe('Token Metadata Instructions', () => {
                 newAuthority,
             }),
             splDiscriminate('spl_token_metadata_interface:update_the_authority'),
-            getStructDecoder([['newAuthority', getBytesDecoder({ size: 32 })]]),
+            getStructDecoder([['newAuthority', fixDecoderSize(getBytesDecoder(), 32)]]),
             { newAuthority: Uint8Array.from(newAuthority.toBuffer()) }
         );
     });