Repository: solana-labs/solana-program-library
Pull request #6153 (2024-01-19 12:15:41)

stake-pool: Ceiling all fee calculations (HAL-01)
#### Problem

During deposit and withdrawal functions, if the SOL or pool token amount is small, it is possible the fee calculations floor to zero due to loss of precision, preventing fee collection.

#### Solution

This scenario is not economically viable, and the cost of fee avoidance would outweigh the benefit of depositing such a small amount to the stake pool, but it's better to fix things up.

The change takes the ceiling of all fee calculations by adding `denominator - 1` to the numerator.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index cc14799c9..5740b79b8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1035,9 +1035,9 @@ dependencies = [
 
 [[package]]
 name = "chrono"
-version = "0.4.31"
+version = "0.4.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
+checksum = "41daef31d7a747c5c847246f36de49ced6f7403b4cdabc807a97b5cc184cda7a"
 dependencies = [
  "android-tzdata",
  "iana-time-zone",
@@ -1045,7 +1045,7 @@ dependencies = [
  "num-traits",
  "serde",
  "wasm-bindgen",
- "windows-targets 0.48.0",
+ "windows-targets 0.52.0",
 ]
 
 [[package]]
@@ -4568,9 +4568,9 @@ dependencies = [
 
 [[package]]
 name = "serde_with"
-version = "3.4.0"
+version = "3.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23"
+checksum = "f58c3a1b3e418f61c25b2aeb43fc6c95eaa252b8cecdda67f401943e9e08d33f"
 dependencies = [
  "base64 0.21.7",
  "chrono",
@@ -4579,7 +4579,7 @@ dependencies = [
  "indexmap 2.0.2",
  "serde",
  "serde_json",
- "serde_with_macros 3.4.0",
+ "serde_with_macros 3.5.0",
  "time",
 ]
 
@@ -4597,9 +4597,9 @@ dependencies = [
 
 [[package]]
 name = "serde_with_macros"
-version = "3.4.0"
+version = "3.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788"
+checksum = "d2068b437a31fc68f25dd7edc296b078f04b45145c199d8eed9866e45f1ff274"
 dependencies = [
  "darling",
  "proc-macro2",
@@ -4801,9 +4801,9 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
 
 [[package]]
 name = "shlex"
-version = "1.1.0"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
 
 [[package]]
 name = "signal-hook-registry"
@@ -6849,7 +6849,7 @@ dependencies = [
 
 [[package]]
 name = "spl-governance"
-version = "3.1.1"
+version = "4.0.0"
 dependencies = [
  "arrayref",
  "assert_matches",
@@ -6874,7 +6874,7 @@ dependencies = [
 
 [[package]]
 name = "spl-governance-addin-api"
-version = "0.1.3"
+version = "0.1.4"
 dependencies = [
  "borsh 0.10.3",
  "solana-program",
@@ -6883,7 +6883,7 @@ dependencies = [
 
 [[package]]
 name = "spl-governance-addin-mock"
-version = "0.1.3"
+version = "0.1.4"
 dependencies = [
  "arrayref",
  "assert_matches",
@@ -6906,7 +6906,7 @@ dependencies = [
 
 [[package]]
 name = "spl-governance-chat"
-version = "0.2.8"
+version = "0.2.9"
 dependencies = [
  "arrayref",
  "assert_matches",
@@ -6931,7 +6931,7 @@ dependencies = [
 
 [[package]]
 name = "spl-governance-test-sdk"
-version = "0.1.3"
+version = "0.1.4"
 dependencies = [
  "arrayref",
  "bincode",
@@ -6950,7 +6950,7 @@ dependencies = [
 
 [[package]]
 name = "spl-governance-tools"
-version = "0.1.3"
+version = "0.1.4"
 dependencies = [
  "arrayref",
  "bincode",
@@ -7177,7 +7177,7 @@ dependencies = [
  "serde",
  "serde_derive",
  "serde_json",
- "serde_with 3.4.0",
+ "serde_with 3.5.0",
  "serial_test",
  "solana-account-decoder",
  "solana-clap-v3-utils",
@@ -7353,7 +7353,7 @@ dependencies = [
  "proptest",
  "serde",
  "serde_json",
- "serde_with 3.4.0",
+ "serde_with 3.5.0",
  "serial_test",
  "solana-program",
  "solana-program-test",
@@ -7366,7 +7366,7 @@ dependencies = [
  "spl-token 4.0.0",
  "spl-token-group-interface",
  "spl-token-metadata-interface 0.2.0",
- "spl-transfer-hook-interface 0.4.1",
+ "spl-transfer-hook-interface 0.5.0",
  "spl-type-length-value 0.3.0",
  "thiserror",
 ]
@@ -7391,7 +7391,7 @@ dependencies = [
  "spl-token-group-interface",
  "spl-token-metadata-interface 0.2.0",
  "spl-transfer-hook-example",
- "spl-transfer-hook-interface 0.4.1",
+ "spl-transfer-hook-interface 0.5.0",
  "test-case",
  "walkdir",
 ]
@@ -7425,6 +7425,7 @@ dependencies = [
  "spl-token 4.0.0",
  "spl-token-2022 1.0.0",
  "spl-token-client",
+ "spl-token-group-interface",
  "spl-token-metadata-interface 0.2.0",
  "strum 0.25.0",
  "strum_macros 0.25.3",
@@ -7453,7 +7454,7 @@ dependencies = [
  "spl-token-2022 1.0.0",
  "spl-token-group-interface",
  "spl-token-metadata-interface 0.2.0",
- "spl-transfer-hook-interface 0.4.1",
+ "spl-transfer-hook-interface 0.5.0",
  "thiserror",
 ]
 
@@ -7665,7 +7666,7 @@ dependencies = [
 
 [[package]]
 name = "spl-transfer-hook-cli"
-version = "0.1.0"
+version = "0.1.1"
 dependencies = [
  "clap 3.2.25",
  "futures-util",
@@ -7682,7 +7683,7 @@ dependencies = [
  "spl-tlv-account-resolution 0.5.1",
  "spl-token-2022 1.0.0",
  "spl-token-client",
- "spl-transfer-hook-interface 0.4.1",
+ "spl-transfer-hook-interface 0.5.0",
  "strum 0.25.0",
  "strum_macros 0.25.3",
  "tokio",
@@ -7690,7 +7691,7 @@ dependencies = [
 
 [[package]]
 name = "spl-transfer-hook-example"
-version = "0.4.0"
+version = "0.5.0"
 dependencies = [
  "arrayref",
  "solana-program",
@@ -7698,7 +7699,7 @@ dependencies = [
  "solana-sdk",
  "spl-tlv-account-resolution 0.5.1",
  "spl-token-2022 1.0.0",
- "spl-transfer-hook-interface 0.4.1",
+ "spl-transfer-hook-interface 0.5.0",
  "spl-type-length-value 0.3.0",
 ]
 
@@ -7720,7 +7721,7 @@ dependencies = [
 
 [[package]]
 name = "spl-transfer-hook-interface"
-version = "0.4.1"
+version = "0.5.0"
 dependencies = [
  "arrayref",
  "bytemuck",
diff --git a/account-compression/sdk/package.json b/account-compression/sdk/package.json
index d7b64106f..461f4f68d 100644
--- a/account-compression/sdk/package.json
+++ b/account-compression/sdk/package.json
@@ -73,8 +73,8 @@
     "@types/chai": "^4.3.0",
     "@types/jest": "^29.5.11",
     "@types/node-fetch": "^2.6.11",
-    "@typescript-eslint/eslint-plugin": "^6.19.0",
-    "@typescript-eslint/parser": "^6.19.0",
+    "@typescript-eslint/eslint-plugin": "^6.19.1",
+    "@typescript-eslint/parser": "^6.19.1",
     "chai": "^4.3.4",
     "eslint": "^8.56.0",
     "eslint-config-prettier": "^9.1.0",
@@ -89,7 +89,7 @@
     "jest": "^29.0.1",
     "jest-config": "^29.0.1",
     "start-server-and-test": "^2.0.3",
-    "ts-jest": "^29.0.0",
+    "ts-jest": "^29.1.2",
     "ts-jest-resolver": "^2.0.1",
     "ts-node": "^10.9.2",
     "typedoc": "^0.25.7",
diff --git a/feature-proposal/cli/Cargo.toml b/feature-proposal/cli/Cargo.toml
index 4e00b55b3..1d356e81a 100644
--- a/feature-proposal/cli/Cargo.toml
+++ b/feature-proposal/cli/Cargo.toml
@@ -8,7 +8,7 @@ license = "Apache-2.0"
 edition = "2021"
 
 [dependencies]
-chrono = "0.4.31"
+chrono = "0.4.32"
 clap = "2.33.3"
 solana-clap-utils = "1.17.13"
 solana-cli-config = "1.17.13"
diff --git a/governance/CHANGELOG.md b/governance/CHANGELOG.md
index a427dec04..f68e2cba3 100644
--- a/governance/CHANGELOG.md
+++ b/governance/CHANGELOG.md
@@ -1,5 +1,9 @@
 # SPL Governance Changelog
 
+## v4.0.0 - WIP
+
+- Mandatory signatories
+
 ## v3.1.1 - 25 Apr 2022
 
 - Weighted multi choice voting
diff --git a/governance/addin-api/Cargo.toml b/governance/addin-api/Cargo.toml
index 0cd3c01e7..6d8ffa964 100644
--- a/governance/addin-api/Cargo.toml
+++ b/governance/addin-api/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "spl-governance-addin-api"
-version = "0.1.3"
+version = "0.1.4"
 description = "Solana Program Library Governance Addin Api"
 authors = ["Solana Labs Maintainers <maintainers@solanalabs.com>"]
 repository = "https://github.com/solana-labs/solana-program-library"
@@ -9,5 +9,5 @@ edition = "2021"
 
 [dependencies]
 borsh = "0.10"
-spl-governance-tools= { version = "0.1.3", path ="../tools"}
+spl-governance-tools = { version = "0.1.4", path = "../tools" }
 solana-program = "1.17.13"
diff --git a/governance/addin-mock/program/Cargo.toml b/governance/addin-mock/program/Cargo.toml
index 67590bee0..af1d4636c 100644
--- a/governance/addin-mock/program/Cargo.toml
+++ b/governance/addin-mock/program/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "spl-governance-addin-mock"
-version = "0.1.3"
+version = "0.1.4"
 description = "Solana Program Library Governance Voter Weight Addin Program"
 authors = ["Solana Labs Maintainers <maintainers@solanalabs.com>"]
 repository = "https://github.com/solana-labs/solana-program-library"
@@ -20,9 +20,11 @@ num-traits = "0.2"
 serde = "1.0.195"
 serde_derive = "1.0.103"
 solana-program = "1.17.13"
-spl-token = { version = "4.0", path = "../../../token/program", features = [ "no-entrypoint" ] }
-spl-governance-addin-api= { version = "0.1.3", path ="../../addin-api"}
-spl-governance-tools= { version = "0.1.3", path ="../../tools"}
+spl-token = { version = "4.0", path = "../../../token/program", features = [
+    "no-entrypoint",
+] }
+spl-governance-addin-api = { version = "0.1.4", path = "../../addin-api" }
+spl-governance-tools = { version = "0.1.4", path = "../../tools" }
 thiserror = "1.0"
 
 
@@ -31,7 +33,7 @@ assert_matches = "1.5.0"
 proptest = "1.4"
 solana-program-test = "1.17.13"
 solana-sdk = "1.17.13"
-spl-governance-test-sdk = { version = "0.1.3", path ="../../test-sdk"}
+spl-governance-test-sdk = { version = "0.1.4", path = "../../test-sdk" }
 
 
 [lib]
diff --git a/governance/chat/program/Cargo.toml b/governance/chat/program/Cargo.toml
index 9aa80ef61..7408915fa 100644
--- a/governance/chat/program/Cargo.toml
+++ b/governance/chat/program/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "spl-governance-chat"
-version = "0.2.8"
+version = "0.2.9"
 description = "Solana Program Library Governance Chat Program"
 authors = ["Solana Labs Maintainers <maintainers@solanalabs.com>"]
 repository = "https://github.com/solana-labs/solana-program-library"
@@ -23,11 +23,11 @@ solana-program = "1.17.13"
 spl-token = { version = "4.0", path = "../../../token/program", features = [
     "no-entrypoint",
 ] }
-spl-governance = { version = "3.1.1", path = "../../program", features = [
+spl-governance = { version = "4.0.0", path = "../../program", features = [
     "no-entrypoint",
 ] }
-spl-governance-tools = { version = "0.1.3", path = "../../tools" }
-spl-governance-addin-api = { version = "0.1.3", path = "../../addin-api" }
+spl-governance-tools = { version = "0.1.4", path = "../../tools" }
+spl-governance-addin-api = { version = "0.1.4", path = "../../addin-api" }
 thiserror = "1.0"
 
 
@@ -36,8 +36,8 @@ assert_matches = "1.5.0"
 proptest = "1.4"
 solana-program-test = "1.17.13"
 solana-sdk = "1.17.13"
-spl-governance-test-sdk = { version = "0.1.3", path = "../../test-sdk" }
-spl-governance-addin-mock = { version = "0.1.3", path = "../../addin-mock/program" }
+spl-governance-test-sdk = { version = "0.1.4", path = "../../test-sdk" }
+spl-governance-addin-mock = { version = "0.1.4", path = "../../addin-mock/program" }
 
 
 [lib]
diff --git a/governance/program/Cargo.toml b/governance/program/Cargo.toml
index ae451e9ff..c5abd60a9 100644
--- a/governance/program/Cargo.toml
+++ b/governance/program/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "spl-governance"
-version = "3.1.1"
+version = "4.0.0"
 description = "Solana Program Library Governance Program"
 authors = ["Solana Labs Maintainers <maintainers@solanalabs.com>"]
 repository = "https://github.com/solana-labs/solana-program-library"
@@ -20,9 +20,11 @@ num-traits = "0.2"
 serde = "1.0.195"
 serde_derive = "1.0.103"
 solana-program = "1.17.13"
-spl-token = { version = "4.0", path = "../../token/program", features = [ "no-entrypoint" ] }
-spl-governance-tools= { version = "0.1.3", path ="../tools"}
-spl-governance-addin-api= { version = "0.1.3", path ="../addin-api"}
+spl-token = { version = "4.0", path = "../../token/program", features = [
+    "no-entrypoint",
+] }
+spl-governance-tools = { version = "0.1.4", path = "../tools" }
+spl-governance-addin-api = { version = "0.1.4", path = "../addin-api" }
 thiserror = "1.0"
 
 [dev-dependencies]
@@ -31,8 +33,8 @@ base64 = "0.21"
 proptest = "1.4"
 solana-program-test = "1.17.13"
 solana-sdk = "1.17.13"
-spl-governance-test-sdk = { version = "0.1.3", path ="../test-sdk"}
-spl-governance-addin-mock = { version = "0.1.3", path ="../addin-mock/program"}
+spl-governance-test-sdk = { version = "0.1.4", path = "../test-sdk" }
+spl-governance-addin-mock = { version = "0.1.4", path = "../addin-mock/program" }
 
 
 [lib]
diff --git a/governance/tools/Cargo.toml b/governance/tools/Cargo.toml
index 0c0c7ec36..9d7393617 100644
--- a/governance/tools/Cargo.toml
+++ b/governance/tools/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "spl-governance-tools"
-version = "0.1.3"
+version = "0.1.4"
 description = "Solana Program Library Governance Tools"
 authors = ["Solana Labs Maintainers <maintainers@solanalabs.com>"]
 repository = "https://github.com/solana-labs/solana-program-library"
@@ -16,5 +16,7 @@ num-traits = "0.2"
 serde = "1.0.195"
 serde_derive = "1.0.103"
 solana-program = "1.17.13"
-spl-token = { version = "4.0", path = "../../token/program", features = [ "no-entrypoint" ] }
+spl-token = { version = "4.0", path = "../../token/program", features = [
+    "no-entrypoint",
+] }
 thiserror = "1.0"
diff --git a/libraries/type-length-value/js/package.json b/libraries/type-length-value/js/package.json
index f9180028b..cd9edd851 100644
--- a/libraries/type-length-value/js/package.json
+++ b/libraries/type-length-value/js/package.json
@@ -47,8 +47,8 @@
         "@types/chai": "^4.3.11",
         "@types/mocha": "^10.0.6",
         "@types/node": "^20.11.5",
-        "@typescript-eslint/eslint-plugin": "^6.19.0",
-        "@typescript-eslint/parser": "^6.19.0",
+        "@typescript-eslint/eslint-plugin": "^6.19.1",
+        "@typescript-eslint/parser": "^6.19.1",
         "chai": "^4.3.10",
         "chai-as-promised": "^7.1.1",
         "eslint": "^8.56.0",
diff --git a/memo/js/package.json b/memo/js/package.json
index 64d1220cc..676dab732 100644
--- a/memo/js/package.json
+++ b/memo/js/package.json
@@ -57,8 +57,8 @@
         "@types/jest": "^29.5.11",
         "@types/node": "^20.11.5",
         "@types/node-fetch": "^2.6.11",
-        "@typescript-eslint/eslint-plugin": "^6.19.0",
-        "@typescript-eslint/parser": "^6.19.0",
+        "@typescript-eslint/eslint-plugin": "^6.19.1",
+        "@typescript-eslint/parser": "^6.19.1",
         "chai": "^4.3.6",
         "eslint": "^8.56.0",
         "eslint-config-prettier": "^9.1.0",
@@ -70,7 +70,7 @@
         "process": "^0.11.10",
         "shx": "^0.3.4",
         "start-server-and-test": "^2.0.3",
-        "ts-jest": "^29.0.0",
+        "ts-jest": "^29.1.2",
         "ts-node": "^10.9.2",
         "typedoc": "^0.25.7",
         "typescript": "^5.3.3"
diff --git a/name-service/js/package.json b/name-service/js/package.json
index 0a899e445..a434d0a47 100644
--- a/name-service/js/package.json
+++ b/name-service/js/package.json
@@ -40,8 +40,8 @@
     "@types/chai-as-promised": "^7.1.5",
     "@types/mocha": "^10.0.6",
     "@types/node": "^20.11.5",
-    "@typescript-eslint/eslint-plugin": "^6.19.0",
-    "@typescript-eslint/parser": "^6.19.0",
+    "@typescript-eslint/eslint-plugin": "^6.19.1",
+    "@typescript-eslint/parser": "^6.19.1",
     "chai": "^4.3.7",
     "chai-as-promised": "^7.1.1",
     "eslint": "^8.56.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e21c04f27..ec5aa5e26 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -6,7 +6,7 @@ importers:
     devDependencies:
       '@solana/eslint-config-solana':
         specifier: ^1.0.2
-        version: 1.0.2(@typescript-eslint/eslint-plugin@6.19.0)(@typescript-eslint/parser@6.19.0)(eslint-plugin-jest@27.6.3)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-simple-import-sort@10.0.0)(eslint-plugin-sort-keys-fix@1.1.2)(eslint@8.56.0)(typescript@5.3.3)
+        version: 1.0.2(@typescript-eslint/eslint-plugin@6.19.1)(@typescript-eslint/parser@6.19.1)(eslint-plugin-jest@27.6.3)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-simple-import-sort@10.0.0)(eslint-plugin-sort-keys-fix@1.1.2)(eslint@8.56.0)(typescript@5.3.3)
       '@solana/prettier-config-solana':
         specifier: ^0.0.4
         version: 0.0.4(prettier@3.2.4)
@@ -49,7 +49,7 @@ importers:
         version: 0.26.0
       '@solana/eslint-config-solana':
         specifier: ^1.0.2
-        version: 1.0.2(@typescript-eslint/eslint-plugin@6.19.0)(@typescript-eslint/parser@6.19.0)(eslint-plugin-jest@27.6.3)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-simple-import-sort@10.0.0)(eslint-plugin-sort-keys-fix@1.1.2)(eslint@8.56.0)(typescript@5.3.3)
+        version: 1.0.2(@typescript-eslint/eslint-plugin@6.19.1)(@typescript-eslint/parser@6.19.1)(eslint-plugin-jest@27.6.3)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-simple-import-sort@10.0.0)(eslint-plugin-sort-keys-fix@1.1.2)(eslint@8.56.0)(typescript@5.3.3)
       '@solana/prettier-config-solana':
         specifier: ^0.0.4
         version: 0.0.4(prettier@3.2.4)
@@ -66,11 +66,11 @@ importers:
         specifier: ^2.6.11
         version: 2.6.11
       '@typescript-eslint/eslint-plugin':
-        specifier: ^6.19.0
-        version: 6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3)
+        specifier: ^6.19.1
+        version: 6.19.1(@typescript-eslint/parser@6.19.1)(eslint@8.56.0)(typescript@5.3.3)
       '@typescript-eslint/parser':
-        specifier: ^6.19.0
-        version: 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+        specifier: ^6.19.1
+        version: 6.19.1(eslint@8.56.0)(typescript@5.3.3)
       chai:
         specifier: ^4.3.4
         version: 4.3.10
@@ -85,10 +85,10 @@ importers:
         version: 1.11.3(eslint@8.56.0)
       eslint-plugin-import:
         specifier: ^2.29.1
-        version: 2.29.1(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)
+        version: 2.29.1(@typescript-eslint/parser@6.19.1)(eslint@8.56.0)
       eslint-plugin-jest:
         specifier: ^27.6.3
-        version: 27.6.3(@typescript-eslint/eslint-plugin@6.19.0)(eslint@8.56.0)(jest@29.7.0)(typescript@5.3.3)
+        version: 27.6.3(@typescript-eslint/eslint-plugin@6.19.1)(eslint@8.56.0)(jest@29.7.0)(typescript@5.3.3)
       eslint-plugin-mocha:
         specifier: ^10.2.0
         version: 10.2.0(eslint@8.56.0)
@@ -114,8 +114,8 @@ importers:
         specifier: ^2.0.3
         version: 2.0.3
       ts-jest:
-        specifier: ^29.0.0
-        version: 29.1.1(@babel/core@7.23.2)(jest@29.7.0)(typescript@5.3.3)
+        specifier: ^29.1.2
+        version: 29.1.2(@babel/core@7.23.2)(jest@29.7.0)(typescript@5.3.3)
       ts-jest-resolver:
         specifier: ^2.0.1
         version: 2.0.1
@@ -145,11 +145,11 @@ importers:
         specifier: ^20.11.5
         version: 20.11.5
       '@typescript-eslint/eslint-plugin':
-        specifier: ^6.19.0
-        version: 6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3)
+        specifier: ^6.19.1
+        version: 6.19.1(@typescript-eslint/parser@6.19.1)(eslint@8.56.0)(typescript@5.3.3)
       '@typescript-eslint/parser':
-        specifier: ^6.19.0
-        version: 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+        specifier: ^6.19.1
+        version: 6.19.1(eslint@8.56.0)(typescript@5.3.3)
       chai:
         specifier: ^4.3.10
         version: 4.3.10
@@ -209,11 +209,11 @@ importers:
         specifier: ^2.6.11
         version: 2.6.11
       '@typescript-eslint/eslint-plugin':
-        specifier: ^6.19.0
-        version: 6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3)
+        specifier: ^6.19.1
+        version: 6.19.1(@typescript-eslint/parser@6.19.1)(eslint@8.56.0)(typescript@5.3.3)
       '@typescript-eslint/parser':
-        specifier: ^6.19.0
-        version: 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+        specifier: ^6.19.1
+        version: 6.19.1(eslint@8.56.0)(typescript@5.3.3)
       chai:
         specifier: ^4.3.6
         version: 4.3.10
@@ -248,8 +248,8 @@ importers:
         specifier: ^2.0.3
         version: 2.0.3
       ts-jest:
-        specifier: ^29.0.0
-        version: 29.1.1(@babel/core@7.23.2)(jest@29.7.0)(typescript@5.3.3)
+        specifier: ^29.1.2
+        version: 29.1.2(@babel/core@7.23.2)(jest@29.7.0)(typescript@5.3.3)
       ts-node:
         specifier: ^10.9.2
         version: 10.9.2(@types/node@20.11.5)(typescript@5.3.3)
@@ -288,11 +288,11 @@ importers:
         specifier: ^20.11.5
         version: 20.11.5
       '@typescript-eslint/eslint-plugin':
-        specifier: ^6.19.0
-        version: 6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3)
+        specifier: ^6.19.1
+        version: 6.19.1(@typescript-eslint/parser@6.19.1)(eslint@8.56.0)(typescript@5.3.3)
       '@typescript-eslint/parser':
-        specifier: ^6.19.0
-        version: 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+        specifier: ^6.19.1
+        version: 6.19.1(eslint@8.56.0)(typescript@5.3.3)
       chai:
         specifier: ^4.3.7
         version: 4.3.10
@@ -313,7 +313,7 @@ importers:
         version: 6.0.0(eslint@8.56.0)(typescript@5.3.3)
       eslint-plugin-import:
         specifier: ^2.29.1
-        version: 2.29.1(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)
+        version: 2.29.1(@typescript-eslint/parser@6.19.1)(eslint@8.56.0)
       mocha:
         specifier: ^10.2.0
         version: 10.2.0
@@ -349,11 +349,11 @@ importers:
         specifier: ^20.11.5
         version: 20.11.5
       '@typescript-eslint/eslint-plugin':
-        specifier: ^6.19.0
-        version: 6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3)
+        specifier: ^6.19.1
+        version: 6.19.1(@typescript-eslint/parser@6.19.1)(eslint@8.56.0)(typescript@5.3.3)
       ava:
-        specifier: ^6.0.1
-        version: 6.0.1(@ava/typescript@4.1.0)
+        specifier: ^6.1.0
+        version: 6.1.0(@ava/typescript@4.1.0)
       eslint:
         specifier: ^8.56.0
         version: 8.56.0
@@ -383,8 +383,8 @@ importers:
         specifier: ^20.11.5
         version: 20.11.5
       '@typescript-eslint/eslint-plugin':
-        specifier: ^6.19.0
-        version: 6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3)
+        specifier: ^6.19.1
+        version: 6.19.1(@typescript-eslint/parser@6.19.1)(eslint@8.56.0)(typescript@5.3.3)
       eslint:
         specifier: ^8.56.0
         version: 8.56.0
@@ -424,25 +424,25 @@ importers:
     devDependencies:
       '@rollup/plugin-alias':
         specifier: ^5.1.0
-        version: 5.1.0(rollup@4.9.5)
+        version: 5.1.0(rollup@4.9.6)
       '@rollup/plugin-commonjs':
         specifier: ^25.0.0
-        version: 25.0.7(rollup@4.9.5)
+        version: 25.0.7(rollup@4.9.6)
       '@rollup/plugin-json':
         specifier: ^6.1.0
-        version: 6.1.0(rollup@4.9.5)
+        version: 6.1.0(rollup@4.9.6)
       '@rollup/plugin-multi-entry':
         specifier: ^6.0.0
-        version: 6.0.1(rollup@4.9.5)
+        version: 6.0.1(rollup@4.9.6)
       '@rollup/plugin-node-resolve':
         specifier: ^15.0.2
-        version: 15.2.3(rollup@4.9.5)
+        version: 15.2.3(rollup@4.9.6)
       '@rollup/plugin-terser':
         specifier: ^0.4.4
-        version: 0.4.4(rollup@4.9.5)
+        version: 0.4.4(rollup@4.9.6)
       '@rollup/plugin-typescript':
         specifier: ^11.1.6
-        version: 11.1.6(rollup@4.9.5)(tslib@2.6.2)(typescript@5.3.3)
+        version: 11.1.6(rollup@4.9.6)(tslib@2.6.2)(typescript@5.3.3)
       '@types/bn.js':
         specifier: ^5.1.0
         version: 5.1.5
@@ -456,11 +456,11 @@ importers:
         specifier: ^2.6.11
         version: 2.6.11
       '@typescript-eslint/eslint-plugin':
-        specifier: ^6.19.0
-        version: 6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3)
+        specifier: ^6.19.1
+        version: 6.19.1(@typescript-eslint/parser@6.19.1)(eslint@8.56.0)(typescript@5.3.3)
       '@typescript-eslint/parser':
-        specifier: ^6.19.0
-        version: 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+        specifier: ^6.19.1
+        version: 6.19.1(eslint@8.56.0)(typescript@5.3.3)
       cross-env:
         specifier: ^7.0.3
         version: 7.0.3
@@ -483,14 +483,14 @@ importers:
         specifier: ^5.0.0
         version: 5.0.5
       rollup:
-        specifier: ^4.9.5
-        version: 4.9.5
+        specifier: ^4.9.6
+        version: 4.9.6
       rollup-plugin-dts:
         specifier: ^6.1.0
-        version: 6.1.0(rollup@4.9.5)(typescript@5.3.3)
+        version: 6.1.0(rollup@4.9.6)(typescript@5.3.3)
       ts-jest:
-        specifier: ^29.0.0
-        version: 29.1.1(@babel/core@7.23.2)(jest@29.7.0)(typescript@5.3.3)
+        specifier: ^29.1.2
+        version: 29.1.2(@babel/core@7.23.2)(jest@29.7.0)(typescript@5.3.3)
       typescript:
         specifier: ^5.3.3
         version: 5.3.3
@@ -526,11 +526,11 @@ importers:
         specifier: ^20.11.5
         version: 20.11.5
       '@typescript-eslint/eslint-plugin':
-        specifier: ^6.19.0
-        version: 6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3)
+        specifier: ^6.19.1
+        version: 6.19.1(@typescript-eslint/parser@6.19.1)(eslint@8.56.0)(typescript@5.3.3)
       '@typescript-eslint/parser':
-        specifier: ^6.19.0
-        version: 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+        specifier: ^6.19.1
+        version: 6.19.1(eslint@8.56.0)(typescript@5.3.3)
       chai:
         specifier: ^4.3.6
         version: 4.3.10
@@ -588,13 +588,13 @@ importers:
     devDependencies:
       '@rollup/plugin-commonjs':
         specifier: ^25.0.0
-        version: 25.0.7(rollup@4.9.5)
+        version: 25.0.7(rollup@4.9.6)
       '@rollup/plugin-node-resolve':
         specifier: ^15.0.2
-        version: 15.2.3(rollup@4.9.5)
+        version: 15.2.3(rollup@4.9.6)
       '@rollup/plugin-typescript':
         specifier: ^11.1.6
-        version: 11.1.6(rollup@4.9.5)(tslib@2.6.2)(typescript@5.3.3)
+        version: 11.1.6(rollup@4.9.6)(tslib@2.6.2)(typescript@5.3.3)
       '@solana/spl-token':
         specifier: 0.3.11
         version: link:../../token/js
@@ -611,11 +611,11 @@ importers:
         specifier: ^20.11.5
         version: 20.11.5
       '@typescript-eslint/eslint-plugin':
-        specifier: ^6.19.0
-        version: 6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3)
+        specifier: ^6.19.1
+        version: 6.19.1(@typescript-eslint/parser@6.19.1)(eslint@8.56.0)(typescript@5.3.3)
       '@typescript-eslint/parser':
-        specifier: ^6.19.0
-        version: 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+        specifier: ^6.19.1
+        version: 6.19.1(eslint@8.56.0)(typescript@5.3.3)
       eslint:
         specifier: ^8.56.0
         version: 8.56.0
@@ -632,8 +632,8 @@ importers:
         specifier: ^3.2.4
         version: 3.2.4
       rollup:
-        specifier: ^4.9.5
-        version: 4.9.5
+        specifier: ^4.9.6
+        version: 4.9.6
       ts-node:
         specifier: ^10.9.2
         version: 10.9.2(@types/node@20.11.5)(typescript@5.3.3)
@@ -681,11 +681,11 @@ importers:
         specifier: ^20.11.5
         version: 20.11.5
       '@typescript-eslint/eslint-plugin':
-        specifier: ^6.19.0
-        version: 6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3)
+        specifier: ^6.19.1
+        version: 6.19.1(@typescript-eslint/parser@6.19.1)(eslint@8.56.0)(typescript@5.3.3)
       '@typescript-eslint/parser':
-        specifier: ^6.19.0
-        version: 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+        specifier: ^6.19.1
+        version: 6.19.1(eslint@8.56.0)(typescript@5.3.3)
       chai:
         specifier: ^4.3.6
         version: 4.3.10
@@ -754,11 +754,11 @@ importers:
         specifier: ^10.0.6
         version: 10.0.6
       '@typescript-eslint/eslint-plugin':
-        specifier: ^6.19.0
-        version: 6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3)
+        specifier: ^6.19.1
+        version: 6.19.1(@typescript-eslint/parser@6.19.1)(eslint@8.56.0)(typescript@5.3.3)
       '@typescript-eslint/parser':
-        specifier: ^6.19.0
-        version: 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+        specifier: ^6.19.1
+        version: 6.19.1(eslint@8.56.0)(typescript@5.3.3)
       eslint:
         specifier: ^8.56.0
         version: 8.56.0
@@ -767,7 +767,7 @@ importers:
         version: 9.1.0(eslint@8.56.0)
       eslint-plugin-import:
         specifier: ^2.29.1
-        version: 2.29.1(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)
+        version: 2.29.1(@typescript-eslint/parser@6.19.1)(eslint@8.56.0)
       eslint-plugin-prettier:
         specifier: ^5.1.3
         version: 5.1.3(@types/eslint@8.56.2)(eslint-config-prettier@9.1.0)(eslint@8.56.0)(prettier@3.2.4)
@@ -830,11 +830,11 @@ importers:
         specifier: ^2.6.11
         version: 2.6.11
       '@typescript-eslint/eslint-plugin':
-        specifier: ^6.19.0
-        version: 6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3)
+        specifier: ^6.19.1
+        version: 6.19.1(@typescript-eslint/parser@6.19.1)(eslint@8.56.0)(typescript@5.3.3)
       '@typescript-eslint/parser':
-        specifier: ^6.19.0
-        version: 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+        specifier: ^6.19.1
+        version: 6.19.1(eslint@8.56.0)(typescript@5.3.3)
       chai:
         specifier: ^4.3.6
         version: 4.3.10
@@ -2030,7 +2030,7 @@ packages:
       - utf-8-validate
     dev: true
 
-  /@rollup/plugin-alias@5.1.0(rollup@4.9.5):
+  /@rollup/plugin-alias@5.1.0(rollup@4.9.6):
     resolution: {integrity: sha512-lpA3RZ9PdIG7qqhEfv79tBffNaoDuukFDrmhLqg9ifv99u/ehn+lOg30x2zmhf8AQqQUZaMk/B9fZraQ6/acDQ==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
@@ -2039,11 +2039,11 @@ packages:
       rollup:
         optional: true
     dependencies:
-      rollup: 4.9.5
+      rollup: 4.9.6
       slash: 4.0.0
     dev: true
 
-  /@rollup/plugin-commonjs@25.0.7(rollup@4.9.5):
+  /@rollup/plugin-commonjs@25.0.7(rollup@4.9.6):
     resolution: {integrity: sha512-nEvcR+LRjEjsaSsc4x3XZfCCvZIaSMenZu/OiwOKGN2UhQpAYI7ru7czFvyWbErlpoGjnSX3D5Ch5FcMA3kRWQ==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
@@ -2052,16 +2052,16 @@ packages:
       rollup:
         optional: true
     dependencies:
-      '@rollup/pluginutils': 5.0.5(rollup@4.9.5)
+      '@rollup/pluginutils': 5.0.5(rollup@4.9.6)
       commondir: 1.0.1
       estree-walker: 2.0.2
       glob: 8.1.0
       is-reference: 1.2.1
       magic-string: 0.30.5
-      rollup: 4.9.5
+      rollup: 4.9.6
     dev: true
 
-  /@rollup/plugin-json@6.1.0(rollup@4.9.5):
+  /@rollup/plugin-json@6.1.0(rollup@4.9.6):
     resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
@@ -2070,11 +2070,11 @@ packages:
       rollup:
         optional: true
     dependencies:
-      '@rollup/pluginutils': 5.1.0(rollup@4.9.5)
-      rollup: 4.9.5
+      '@rollup/pluginutils': 5.1.0(rollup@4.9.6)
+      rollup: 4.9.6
     dev: true
 
-  /@rollup/plugin-multi-entry@6.0.1(rollup@4.9.5):
+  /@rollup/plugin-multi-entry@6.0.1(rollup@4.9.6):
     resolution: {integrity: sha512-AXm6toPyTSfbYZWghQGbom1Uh7dHXlrGa+HoiYNhQtDUE3Q7LqoUYdVQx9E1579QWS1uOiu+cZRSE4okO7ySgw==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
@@ -2083,12 +2083,12 @@ packages:
       rollup:
         optional: true
     dependencies:
-      '@rollup/plugin-virtual': 3.0.2(rollup@4.9.5)
+      '@rollup/plugin-virtual': 3.0.2(rollup@4.9.6)
       matched: 5.0.1
-      rollup: 4.9.5
+      rollup: 4.9.6
     dev: true
 
-  /@rollup/plugin-node-resolve@15.2.3(rollup@4.9.5):
+  /@rollup/plugin-node-resolve@15.2.3(rollup@4.9.6):
     resolution: {integrity: sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
@@ -2097,16 +2097,16 @@ packages:
       rollup:
         optional: true
     dependencies:
-      '@rollup/pluginutils': 5.0.5(rollup@4.9.5)
+      '@rollup/pluginutils': 5.0.5(rollup@4.9.6)
       '@types/resolve': 1.20.2
       deepmerge: 4.3.1
       is-builtin-module: 3.2.1
       is-module: 1.0.0
       resolve: 1.22.8
-      rollup: 4.9.5
+      rollup: 4.9.6
     dev: true
 
-  /@rollup/plugin-terser@0.4.4(rollup@4.9.5):
+  /@rollup/plugin-terser@0.4.4(rollup@4.9.6):
     resolution: {integrity: sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
@@ -2115,13 +2115,13 @@ packages:
       rollup:
         optional: true
     dependencies:
-      rollup: 4.9.5
+      rollup: 4.9.6
       serialize-javascript: 6.0.1
       smob: 1.4.1
       terser: 5.24.0
     dev: true
 
-  /@rollup/plugin-typescript@11.1.6(rollup@4.9.5)(tslib@2.6.2)(typescript@5.3.3):
+  /@rollup/plugin-typescript@11.1.6(rollup@4.9.6)(tslib@2.6.2)(typescript@5.3.3):
     resolution: {integrity: sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
@@ -2134,14 +2134,14 @@ packages:
       tslib:
         optional: true
     dependencies:
-      '@rollup/pluginutils': 5.1.0(rollup@4.9.5)
+      '@rollup/pluginutils': 5.1.0(rollup@4.9.6)
       resolve: 1.22.8
-      rollup: 4.9.5
+      rollup: 4.9.6
       tslib: 2.6.2
       typescript: 5.3.3
     dev: true
 
-  /@rollup/plugin-virtual@3.0.2(rollup@4.9.5):
+  /@rollup/plugin-virtual@3.0.2(rollup@4.9.6):
     resolution: {integrity: sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
@@ -2150,7 +2150,7 @@ packages:
       rollup:
         optional: true
     dependencies:
-      rollup: 4.9.5
+      rollup: 4.9.6
     dev: true
 
   /@rollup/pluginutils@4.2.1:
@@ -2161,7 +2161,7 @@ packages:
       picomatch: 2.3.1
     dev: true
 
-  /@rollup/pluginutils@5.0.5(rollup@4.9.5):
+  /@rollup/pluginutils@5.0.5(rollup@4.9.6):
     resolution: {integrity: sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
@@ -2173,10 +2173,10 @@ packages:
       '@types/estree': 1.0.5
       estree-walker: 2.0.2
       picomatch: 2.3.1
-      rollup: 4.9.5
+      rollup: 4.9.6
     dev: true
 
-  /@rollup/pluginutils@5.1.0(rollup@4.9.5):
+  /@rollup/pluginutils@5.1.0(rollup@4.9.6):
     resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
@@ -2188,107 +2188,107 @@ packages:
       '@types/estree': 1.0.5
       estree-walker: 2.0.2
       picomatch: 2.3.1
-      rollup: 4.9.5
+      rollup: 4.9.6
     dev: true
 
-  /@rollup/rollup-android-arm-eabi@4.9.5:
-    resolution: {integrity: sha512-idWaG8xeSRCfRq9KpRysDHJ/rEHBEXcHuJ82XY0yYFIWnLMjZv9vF/7DOq8djQ2n3Lk6+3qfSH8AqlmHlmi1MA==}
+  /@rollup/rollup-android-arm-eabi@4.9.6:
+    resolution: {integrity: sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==}
     cpu: [arm]
     os: [android]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-android-arm64@4.9.5:
-    resolution: {integrity: sha512-f14d7uhAMtsCGjAYwZGv6TwuS3IFaM4ZnGMUn3aCBgkcHAYErhV1Ad97WzBvS2o0aaDv4mVz+syiN0ElMyfBPg==}
+  /@rollup/rollup-android-arm64@4.9.6:
+    resolution: {integrity: sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw==}
     cpu: [arm64]
     os: [android]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-darwin-arm64@4.9.5:
-    resolution: {integrity: sha512-ndoXeLx455FffL68OIUrVr89Xu1WLzAG4n65R8roDlCoYiQcGGg6MALvs2Ap9zs7AHg8mpHtMpwC8jBBjZrT/w==}
+  /@rollup/rollup-darwin-arm64@4.9.6:
+    resolution: {integrity: sha512-CqNNAyhRkTbo8VVZ5R85X73H3R5NX9ONnKbXuHisGWC0qRbTTxnF1U4V9NafzJbgGM0sHZpdO83pLPzq8uOZFw==}
     cpu: [arm64]
     os: [darwin]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-darwin-x64@4.9.5:
-    resolution: {integrity: sha512-UmElV1OY2m/1KEEqTlIjieKfVwRg0Zwg4PLgNf0s3glAHXBN99KLpw5A5lrSYCa1Kp63czTpVll2MAqbZYIHoA==}
+  /@rollup/rollup-darwin-x64@4.9.6:
+    resolution: {integrity: sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog==}
     cpu: [x64]
     os: [darwin]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-linux-arm-gnueabihf@4.9.5:
-    resolution: {integrity: sha512-Q0LcU61v92tQB6ae+udZvOyZ0wfpGojtAKrrpAaIqmJ7+psq4cMIhT/9lfV6UQIpeItnq/2QDROhNLo00lOD1g==}
+  /@rollup/rollup-linux-arm-gnueabihf@4.9.6:
+    resolution: {integrity: sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ==}
     cpu: [arm]
     os: [linux]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-linux-arm64-gnu@4.9.5:
-    resolution: {integrity: sha512-dkRscpM+RrR2Ee3eOQmRWFjmV/payHEOrjyq1VZegRUa5OrZJ2MAxBNs05bZuY0YCtpqETDy1Ix4i/hRqX98cA==}
+  /@rollup/rollup-linux-arm64-gnu@4.9.6:
+    resolution: {integrity: sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ==}
     cpu: [arm64]
     os: [linux]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-linux-arm64-musl@4.9.5:
-    resolution: {integrity: sha512-QaKFVOzzST2xzY4MAmiDmURagWLFh+zZtttuEnuNn19AiZ0T3fhPyjPPGwLNdiDT82ZE91hnfJsUiDwF9DClIQ==}
+  /@rollup/rollup-linux-arm64-musl@4.9.6:
+    resolution: {integrity: sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ==}
     cpu: [arm64]
     os: [linux]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-linux-riscv64-gnu@4.9.5:
-    resolution: {integrity: sha512-HeGqmRJuyVg6/X6MpE2ur7GbymBPS8Np0S/vQFHDmocfORT+Zt76qu+69NUoxXzGqVP1pzaY6QIi0FJWLC3OPA==}
+  /@rollup/rollup-linux-riscv64-gnu@4.9.6:
+    resolution: {integrity: sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA==}
     cpu: [riscv64]
     os: [linux]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-linux-x64-gnu@4.9.5:
-    resolution: {integrity: sha512-Dq1bqBdLaZ1Gb/l2e5/+o3B18+8TI9ANlA1SkejZqDgdU/jK/ThYaMPMJpVMMXy2uRHvGKbkz9vheVGdq3cJfA==}
+  /@rollup/rollup-linux-x64-gnu@4.9.6:
+    resolution: {integrity: sha512-HUNqM32dGzfBKuaDUBqFB7tP6VMN74eLZ33Q9Y1TBqRDn+qDonkAUyKWwF9BR9unV7QUzffLnz9GrnKvMqC/fw==}
     cpu: [x64]
     os: [linux]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-linux-x64-musl@4.9.5:
-    resolution: {integrity: sha512-ezyFUOwldYpj7AbkwyW9AJ203peub81CaAIVvckdkyH8EvhEIoKzaMFJj0G4qYJ5sw3BpqhFrsCc30t54HV8vg==}
+  /@rollup/rollup-linux-x64-musl@4.9.6:
+    resolution: {integrity: sha512-ch7M+9Tr5R4FK40FHQk8VnML0Szi2KRujUgHXd/HjuH9ifH72GUmw6lStZBo3c3GB82vHa0ZoUfjfcM7JiiMrQ==}
     cpu: [x64]
     os: [linux]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-win32-arm64-msvc@4.9.5:
-    resolution: {integrity: sha512-aHSsMnUw+0UETB0Hlv7B/ZHOGY5bQdwMKJSzGfDfvyhnpmVxLMGnQPGNE9wgqkLUs3+gbG1Qx02S2LLfJ5GaRQ==}
+  /@rollup/rollup-win32-arm64-msvc@4.9.6:
+    resolution: {integrity: sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA==}
     cpu: [arm64]
     os: [win32]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-win32-ia32-msvc@4.9.5:
-    resolution: {integrity: sha512-AiqiLkb9KSf7Lj/o1U3SEP9Zn+5NuVKgFdRIZkvd4N0+bYrTOovVd0+LmYCPQGbocT4kvFyK+LXCDiXPBF3fyA==}
+  /@rollup/rollup-win32-ia32-msvc@4.9.6:
+    resolution: {integrity: sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ==}
     cpu: [ia32]
     os: [win32]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-win32-x64-msvc@4.9.5:
-    resolution: {integrity: sha512-1q+mykKE3Vot1kaFJIDoUFv5TuW+QQVaf2FmTT9krg86pQrGStOSJJ0Zil7CFagyxDuouTepzt5Y5TVzyajOdQ==}
+  /@rollup/rollup-win32-x64-msvc@4.9.6:
+    resolution: {integrity: sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ==}
     cpu: [x64]
     os: [win32]
     requiresBuild: true
@@ -2385,7 +2385,7 @@ packages:
       '@solana/codecs-numbers': 2.0.0-experimental.9741939
       fastestsmallesttextencoderdecoder: 1.0.22
 
-  /@solana/eslint-config-solana@1.0.2(@typescript-eslint/eslint-plugin@6.19.0)(@typescript-eslint/parser@6.19.0)(eslint-plugin-jest@27.6.3)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-simple-import-sort@10.0.0)(eslint-plugin-sort-keys-fix@1.1.2)(eslint@8.56.0)(typescript@5.3.3):
+  /@solana/eslint-config-solana@1.0.2(@typescript-eslint/eslint-plugin@6.19.1)(@typescript-eslint/parser@6.19.1)(eslint-plugin-jest@27.6.3)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-simple-import-sort@10.0.0)(eslint-plugin-sort-keys-fix@1.1.2)(eslint@8.56.0)(typescript@5.3.3):
     resolution: {integrity: sha512-/mBi2xFsX0fq3ukZ3ugA+Zh2+Ct+bh713ZSdOJAv1G7ARzyOEDxmiUz8TLzHu4joOC/3Ok4Yl7cjupOcIRzMHw==}
     peerDependencies:
       '@typescript-eslint/eslint-plugin': ^6.0.0
@@ -2397,10 +2397,10 @@ packages:
       eslint-plugin-sort-keys-fix: ^1.1.2
       typescript: ^5.1.6
     dependencies:
-      '@typescript-eslint/eslint-plugin': 6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3)
-      '@typescript-eslint/parser': 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+      '@typescript-eslint/eslint-plugin': 6.19.1(@typescript-eslint/parser@6.19.1)(eslint@8.56.0)(typescript@5.3.3)
+      '@typescript-eslint/parser': 6.19.1(eslint@8.56.0)(typescript@5.3.3)
       eslint: 8.56.0
-      eslint-plugin-jest: 27.6.3(@typescript-eslint/eslint-plugin@6.19.0)(eslint@8.56.0)(jest@29.7.0)(typescript@5.3.3)
+      eslint-plugin-jest: 27.6.3(@typescript-eslint/eslint-plugin@6.19.1)(eslint@8.56.0)(jest@29.7.0)(typescript@5.3.3)
       eslint-plugin-react-hooks: 4.6.0(eslint@8.56.0)
       eslint-plugin-simple-import-sort: 10.0.0(eslint@8.56.0)
       eslint-plugin-sort-keys-fix: 1.1.2
@@ -2666,8 +2666,8 @@ packages:
       '@types/yargs-parser': 21.0.3
     dev: true
 
-  /@typescript-eslint/eslint-plugin@6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3):
-    resolution: {integrity: sha512-DUCUkQNklCQYnrBSSikjVChdc84/vMPDQSgJTHBZ64G9bA9w0Crc0rd2diujKbTdp6w2J47qkeHQLoi0rpLCdg==}
+  /@typescript-eslint/eslint-plugin@6.19.1(@typescript-eslint/parser@6.19.1)(eslint@8.56.0)(typescript@5.3.3):
+    resolution: {integrity: sha512-roQScUGFruWod9CEyoV5KlCYrubC/fvG8/1zXuT0WTcxX87GnMMmnksMwSg99lo1xiKrBzw2icsJPMAw1OtKxg==}
     engines: {node: ^16.0.0 || >=18.0.0}
     peerDependencies:
       '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha
@@ -2678,11 +2678,11 @@ packages:
         optional: true
     dependencies:
       '@eslint-community/regexpp': 4.10.0
-      '@typescript-eslint/parser': 6.19.0(eslint@8.56.0)(typescript@5.3.3)
-      '@typescript-eslint/scope-manager': 6.19.0
-      '@typescript-eslint/type-utils': 6.19.0(eslint@8.56.0)(typescript@5.3.3)
-      '@typescript-eslint/utils': 6.19.0(eslint@8.56.0)(typescript@5.3.3)
-      '@typescript-eslint/visitor-keys': 6.19.0
+      '@typescript-eslint/parser': 6.19.1(eslint@8.56.0)(typescript@5.3.3)
+      '@typescript-eslint/scope-manager': 6.19.1
+      '@typescript-eslint/type-utils': 6.19.1(eslint@8.56.0)(typescript@5.3.3)
+      '@typescript-eslint/utils': 6.19.1(eslint@8.56.0)(typescript@5.3.3)
+      '@typescript-eslint/visitor-keys': 6.19.1
       debug: 4.3.4(supports-color@8.1.1)
       eslint: 8.56.0
       graphemer: 1.4.0
@@ -2695,8 +2695,8 @@ packages:
       - supports-color
     dev: true
 
-  /@typescript-eslint/parser@6.19.0(eslint@8.56.0)(typescript@5.3.3):
-    resolution: {integrity: sha512-1DyBLG5SH7PYCd00QlroiW60YJ4rWMuUGa/JBV0iZuqi4l4IK3twKPq5ZkEebmGqRjXWVgsUzfd3+nZveewgow==}
+  /@typescript-eslint/parser@6.19.1(eslint@8.56.0)(typescript@5.3.3):
+    resolution: {integrity: sha512-WEfX22ziAh6pRE9jnbkkLGp/4RhTpffr2ZK5bJ18M8mIfA8A+k97U9ZyaXCEJRlmMHh7R9MJZWXp/r73DzINVQ==}
     engines: {node: ^16.0.0 || >=18.0.0}
     peerDependencies:
       eslint: ^7.0.0 || ^8.0.0
@@ -2705,10 +2705,10 @@ packages:
       typescript:
         optional: true
     dependencies:
-      '@typescript-eslint/scope-manager': 6.19.0
-      '@typescript-eslint/types': 6.19.0
-      '@typescript-eslint/typescript-estree': 6.19.0(typescript@5.3.3)
-      '@typescript-eslint/visitor-keys': 6.19.0
+      '@typescript-eslint/scope-manager': 6.19.1
+      '@typescript-eslint/types': 6.19.1
+      '@typescript-eslint/typescript-estree': 6.19.1(typescript@5.3.3)
+      '@typescript-eslint/visitor-keys': 6.19.1
       debug: 4.3.4(supports-color@8.1.1)
       eslint: 8.56.0
       typescript: 5.3.3
@@ -2732,16 +2732,16 @@ packages:
       '@typescript-eslint/visitor-keys': 6.10.0
     dev: true
 
-  /@typescript-eslint/scope-manager@6.19.0:
-    resolution: {integrity: sha512-dO1XMhV2ehBI6QN8Ufi7I10wmUovmLU0Oru3n5LVlM2JuzB4M+dVphCPLkVpKvGij2j/pHBWuJ9piuXx+BhzxQ==}
+  /@typescript-eslint/scope-manager@6.19.1:
+    resolution: {integrity: sha512-4CdXYjKf6/6aKNMSly/BP4iCSOpvMmqtDzRtqFyyAae3z5kkqEjKndR5vDHL8rSuMIIWP8u4Mw4VxLyxZW6D5w==}
     engines: {node: ^16.0.0 || >=18.0.0}
     dependencies:
-      '@typescript-eslint/types': 6.19.0
-      '@typescript-eslint/visitor-keys': 6.19.0
+      '@typescript-eslint/types': 6.19.1
+      '@typescript-eslint/visitor-keys': 6.19.1
     dev: true
 
-  /@typescript-eslint/type-utils@6.19.0(eslint@8.56.0)(typescript@5.3.3):
-    resolution: {integrity: sha512-mcvS6WSWbjiSxKCwBcXtOM5pRkPQ6kcDds/juxcy/727IQr3xMEcwr/YLHW2A2+Fp5ql6khjbKBzOyjuPqGi/w==}
+  /@typescript-eslint/type-utils@6.19.1(eslint@8.56.0)(typescript@5.3.3):
+    resolution: {integrity: sha512-0vdyld3ecfxJuddDjACUvlAeYNrHP/pDeQk2pWBR2ESeEzQhg52DF53AbI9QCBkYE23lgkhLCZNkHn2hEXXYIg==}
     engines: {node: ^16.0.0 || >=18.0.0}
     peerDependencies:
       eslint: ^7.0.0 || ^8.0.0
@@ -2750,8 +2750,8 @@ packages:
       typescript:
         optional: true
     dependencies:
-      '@typescript-eslint/typescript-estree': 6.19.0(typescript@5.3.3)
-      '@typescript-eslint/utils': 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+      '@typescript-eslint/typescript-estree': 6.19.1(typescript@5.3.3)
+      '@typescript-eslint/utils': 6.19.1(eslint@8.56.0)(typescript@5.3.3)
       debug: 4.3.4(supports-color@8.1.1)
       eslint: 8.56.0
       ts-api-utils: 1.0.3(typescript@5.3.3)
@@ -2770,8 +2770,8 @@ packages:
     engines: {node: ^16.0.0 || >=18.0.0}
     dev: true
 
-  /@typescript-eslint/types@6.19.0:
-    resolution: {integrity: sha512-lFviGV/vYhOy3m8BJ/nAKoAyNhInTdXpftonhWle66XHAtT1ouBlkjL496b5H5hb8dWXHwtypTqgtb/DEa+j5A==}
+  /@typescript-eslint/types@6.19.1:
+    resolution: {integrity: sha512-6+bk6FEtBhvfYvpHsDgAL3uo4BfvnTnoge5LrrCj2eJN8g3IJdLTD4B/jK3Q6vo4Ql/Hoip9I8aB6fF+6RfDqg==}
     engines: {node: ^16.0.0 || >=18.0.0}
     dev: true
 
@@ -2817,8 +2817,8 @@ packages:
       - supports-color
     dev: true
 
-  /@typescript-eslint/typescript-estree@6.19.0(typescript@5.3.3):
-    resolution: {integrity: sha512-o/zefXIbbLBZ8YJ51NlkSAt2BamrK6XOmuxSR3hynMIzzyMY33KuJ9vuMdFSXW+H0tVvdF9qBPTHA91HDb4BIQ==}
+  /@typescript-eslint/typescript-estree@6.19.1(typescript@5.3.3):
+    resolution: {integrity: sha512-aFdAxuhzBFRWhy+H20nYu19+Km+gFfwNO4TEqyszkMcgBDYQjmPJ61erHxuT2ESJXhlhrO7I5EFIlZ+qGR8oVA==}
     engines: {node: ^16.0.0 || >=18.0.0}
     peerDependencies:
       typescript: '*'
@@ -2826,8 +2826,8 @@ packages:
       typescript:
         optional: true
     dependencies:
-      '@typescript-eslint/types': 6.19.0
-      '@typescript-eslint/visitor-keys': 6.19.0
+      '@typescript-eslint/types': 6.19.1
+      '@typescript-eslint/visitor-keys': 6.19.1
       debug: 4.3.4(supports-color@8.1.1)
       globby: 11.1.0
       is-glob: 4.0.3
@@ -2878,8 +2878,8 @@ packages:
       - typescript
     dev: true
 
-  /@typescript-eslint/utils@6.19.0(eslint@8.56.0)(typescript@5.3.3):
-    resolution: {integrity: sha512-QR41YXySiuN++/dC9UArYOg4X86OAYP83OWTewpVx5ct1IZhjjgTLocj7QNxGhWoTqknsgpl7L+hGygCO+sdYw==}
+  /@typescript-eslint/utils@6.19.1(eslint@8.56.0)(typescript@5.3.3):
+    resolution: {integrity: sha512-JvjfEZuP5WoMqwh9SPAPDSHSg9FBHHGhjPugSRxu5jMfjvBpq5/sGTD+9M9aQ5sh6iJ8AY/Kk/oUYVEMAPwi7w==}
     engines: {node: ^16.0.0 || >=18.0.0}
     peerDependencies:
       eslint: ^7.0.0 || ^8.0.0
@@ -2887,9 +2887,9 @@ packages:
       '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0)
       '@types/json-schema': 7.0.15
       '@types/semver': 7.5.5
-      '@typescript-eslint/scope-manager': 6.19.0
-      '@typescript-eslint/types': 6.19.0
-      '@typescript-eslint/typescript-estree': 6.19.0(typescript@5.3.3)
+      '@typescript-eslint/scope-manager': 6.19.1
+      '@typescript-eslint/types': 6.19.1
+      '@typescript-eslint/typescript-estree': 6.19.1(typescript@5.3.3)
       eslint: 8.56.0
       semver: 7.5.4
     transitivePeerDependencies:
@@ -2913,11 +2913,11 @@ packages:
       eslint-visitor-keys: 3.4.3
     dev: true
 
-  /@typescript-eslint/visitor-keys@6.19.0:
-    resolution: {integrity: sha512-hZaUCORLgubBvtGpp1JEFEazcuEdfxta9j4iUwdSAr7mEsYYAp3EAUyCZk3VEEqGj6W+AV4uWyrDGtrlawAsgQ==}
+  /@typescript-eslint/visitor-keys@6.19.1:
+    resolution: {integrity: sha512-gkdtIO+xSO/SmI0W68DBg4u1KElmIUo3vXzgHyGPs6cxgB0sa3TlptRAAE0hUY1hM6FcDKEv7aIwiTGm76cXfQ==}
     engines: {node: ^16.0.0 || >=18.0.0}
     dependencies:
-      '@typescript-eslint/types': 6.19.0
+      '@typescript-eslint/types': 6.19.1
       eslint-visitor-keys: 3.4.3
     dev: true
 
@@ -3229,8 +3229,8 @@ packages:
     resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
     dev: true
 
-  /ava@6.0.1(@ava/typescript@4.1.0):
-    resolution: {integrity: sha512-9zR0wOwlcJdOWwHOKnpi0GrPRLTlxDFapGalP4rGD0oQRKxDVoucBBWvxVQ/2cPv10Hx1PkDXLJH5iUzhPn0/g==}
+  /ava@6.1.0(@ava/typescript@4.1.0):
+    resolution: {integrity: sha512-0fOQIWMHT9DYelzTYc3TI/4fmmmJ5LdvRWd6F43CppYfg84VrM5EO+hmlv2cPujMJAWuVesUWVloUu9PIwrgTA==}
     engines: {node: ^18.18 || ^20.8 || ^21}
     hasBin: true
     peerDependencies:
@@ -4239,7 +4239,7 @@ packages:
       - supports-color
     dev: true
 
-  /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.19.0)(eslint-import-resolver-node@0.3.9)(eslint@8.56.0):
+  /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.19.1)(eslint-import-resolver-node@0.3.9)(eslint@8.56.0):
     resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==}
     engines: {node: '>=4'}
     peerDependencies:
@@ -4260,7 +4260,7 @@ packages:
       eslint-import-resolver-webpack:
         optional: true
     dependencies:
-      '@typescript-eslint/parser': 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+      '@typescript-eslint/parser': 6.19.1(eslint@8.56.0)(typescript@5.3.3)
       debug: 3.2.7
       eslint: 8.56.0
       eslint-import-resolver-node: 0.3.9
@@ -4301,7 +4301,7 @@ packages:
       - supports-color
     dev: true
 
-  /eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.19.0)(eslint@8.56.0):
+  /eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.19.1)(eslint@8.56.0):
     resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==}
     engines: {node: '>=4'}
     peerDependencies:
@@ -4311,7 +4311,7 @@ packages:
       '@typescript-eslint/parser':
         optional: true
     dependencies:
-      '@typescript-eslint/parser': 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+      '@typescript-eslint/parser': 6.19.1(eslint@8.56.0)(typescript@5.3.3)
       array-includes: 3.1.7
       array.prototype.findlastindex: 1.2.3
       array.prototype.flat: 1.3.2
@@ -4320,7 +4320,7 @@ packages:
       doctrine: 2.1.0
       eslint: 8.56.0
       eslint-import-resolver-node: 0.3.9
-      eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.19.0)(eslint-import-resolver-node@0.3.9)(eslint@8.56.0)
+      eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.19.1)(eslint-import-resolver-node@0.3.9)(eslint@8.56.0)
       hasown: 2.0.0
       is-core-module: 2.13.1
       is-glob: 4.0.3
@@ -4336,7 +4336,7 @@ packages:
       - supports-color
     dev: true
 
-  /eslint-plugin-jest@27.6.3(@typescript-eslint/eslint-plugin@6.19.0)(eslint@8.56.0)(jest@29.7.0)(typescript@5.3.3):
+  /eslint-plugin-jest@27.6.3(@typescript-eslint/eslint-plugin@6.19.1)(eslint@8.56.0)(jest@29.7.0)(typescript@5.3.3):
     resolution: {integrity: sha512-+YsJFVH6R+tOiO3gCJon5oqn4KWc+mDq2leudk8mrp8RFubLOo9CVyi3cib4L7XMpxExmkmBZQTPDYVBzgpgOA==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     peerDependencies:
@@ -4349,7 +4349,7 @@ packages:
       jest:
         optional: true
     dependencies:
-      '@typescript-eslint/eslint-plugin': 6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3)
+      '@typescript-eslint/eslint-plugin': 6.19.1(@typescript-eslint/parser@6.19.1)(eslint@8.56.0)(typescript@5.3.3)
       '@typescript-eslint/utils': 5.62.0(eslint@8.56.0)(typescript@5.3.3)
       eslint: 8.56.0
       jest: 29.7.0(@types/node@20.11.5)(ts-node@10.9.2)
@@ -5337,7 +5337,7 @@ packages:
       eslint: '*'
       typescript: '>=4.7.4'
     dependencies:
-      '@typescript-eslint/type-utils': 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+      '@typescript-eslint/type-utils': 6.19.1(eslint@8.56.0)(typescript@5.3.3)
       eslint: 8.56.0
       ts-api-utils: 1.0.3(typescript@5.3.3)
       typescript: 5.3.3
@@ -6946,7 +6946,7 @@ packages:
       glob: 10.3.10
     dev: true
 
-  /rollup-plugin-dts@6.1.0(rollup@4.9.5)(typescript@5.3.3):
+  /rollup-plugin-dts@6.1.0(rollup@4.9.6)(typescript@5.3.3):
     resolution: {integrity: sha512-ijSCPICkRMDKDLBK9torss07+8dl9UpY9z1N/zTeA1cIqdzMlpkV3MOOC7zukyvQfDyxa1s3Dl2+DeiP/G6DOw==}
     engines: {node: '>=16'}
     peerDependencies:
@@ -6954,32 +6954,32 @@ packages:
       typescript: ^4.5 || ^5.0
     dependencies:
       magic-string: 0.30.5
-      rollup: 4.9.5
+      rollup: 4.9.6
       typescript: 5.3.3
     optionalDependencies:
       '@babel/code-frame': 7.22.13
     dev: true
 
-  /rollup@4.9.5:
-    resolution: {integrity: sha512-E4vQW0H/mbNMw2yLSqJyjtkHY9dslf/p0zuT1xehNRqUTBOFMqEjguDvqhXr7N7r/4ttb2jr4T41d3dncmIgbQ==}
+  /rollup@4.9.6:
+    resolution: {integrity: sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==}
     engines: {node: '>=18.0.0', npm: '>=8.0.0'}
     hasBin: true
     dependencies:
       '@types/estree': 1.0.5
     optionalDependencies:
-      '@rollup/rollup-android-arm-eabi': 4.9.5
-      '@rollup/rollup-android-arm64': 4.9.5
-      '@rollup/rollup-darwin-arm64': 4.9.5
-      '@rollup/rollup-darwin-x64': 4.9.5
-      '@rollup/rollup-linux-arm-gnueabihf': 4.9.5
-      '@rollup/rollup-linux-arm64-gnu': 4.9.5
-      '@rollup/rollup-linux-arm64-musl': 4.9.5
-      '@rollup/rollup-linux-riscv64-gnu': 4.9.5
-      '@rollup/rollup-linux-x64-gnu': 4.9.5
-      '@rollup/rollup-linux-x64-musl': 4.9.5
-      '@rollup/rollup-win32-arm64-msvc': 4.9.5
-      '@rollup/rollup-win32-ia32-msvc': 4.9.5
-      '@rollup/rollup-win32-x64-msvc': 4.9.5
+      '@rollup/rollup-android-arm-eabi': 4.9.6
+      '@rollup/rollup-android-arm64': 4.9.6
+      '@rollup/rollup-darwin-arm64': 4.9.6
+      '@rollup/rollup-darwin-x64': 4.9.6
+      '@rollup/rollup-linux-arm-gnueabihf': 4.9.6
+      '@rollup/rollup-linux-arm64-gnu': 4.9.6
+      '@rollup/rollup-linux-arm64-musl': 4.9.6
+      '@rollup/rollup-linux-riscv64-gnu': 4.9.6
+      '@rollup/rollup-linux-x64-gnu': 4.9.6
+      '@rollup/rollup-linux-x64-musl': 4.9.6
+      '@rollup/rollup-win32-arm64-msvc': 4.9.6
+      '@rollup/rollup-win32-ia32-msvc': 4.9.6
+      '@rollup/rollup-win32-x64-msvc': 4.9.6
       fsevents: 2.3.3
     dev: true
 
@@ -7582,9 +7582,9 @@ packages:
       jest-resolve: 29.7.0
     dev: true
 
-  /ts-jest@29.1.1(@babel/core@7.23.2)(jest@29.7.0)(typescript@5.3.3):
-    resolution: {integrity: sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==}
-    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+  /ts-jest@29.1.2(@babel/core@7.23.2)(jest@29.7.0)(typescript@5.3.3):
+    resolution: {integrity: sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==}
+    engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0}
     hasBin: true
     peerDependencies:
       '@babel/core': '>=7.0.0-beta.0 <8'
diff --git a/single-pool/cli/Cargo.toml b/single-pool/cli/Cargo.toml
index d3b0fe854..7a84be6e3 100644
--- a/single-pool/cli/Cargo.toml
+++ b/single-pool/cli/Cargo.toml
@@ -16,7 +16,7 @@ bincode = "1.3.1"
 serde = "1.0.195"
 serde_derive = "1.0.103"
 serde_json = "1.0.111"
-serde_with = "3.4.0"
+serde_with = "3.5.0"
 solana-account-decoder = "=1.17.13"
 solana-clap-v3-utils = "=1.17.13"
 solana-cli-config = "=1.17.13"
diff --git a/single-pool/js/packages/classic/package.json b/single-pool/js/packages/classic/package.json
index 6581898c5..09903cf11 100644
--- a/single-pool/js/packages/classic/package.json
+++ b/single-pool/js/packages/classic/package.json
@@ -20,8 +20,8 @@
   "devDependencies": {
     "@types/node": "^20.11.5",
     "@ava/typescript": "^4.1.0",
-    "@typescript-eslint/eslint-plugin": "^6.19.0",
-    "ava": "^6.0.1",
+    "@typescript-eslint/eslint-plugin": "^6.19.1",
+    "ava": "^6.1.0",
     "eslint": "^8.56.0",
     "eslint-config-prettier": "^9.1.0",
     "eslint-plugin-prettier": "^5.1.3",
diff --git a/single-pool/js/packages/modern/package.json b/single-pool/js/packages/modern/package.json
index 91a30e417..035bfefc3 100644
--- a/single-pool/js/packages/modern/package.json
+++ b/single-pool/js/packages/modern/package.json
@@ -17,7 +17,7 @@
   },
   "devDependencies": {
     "@types/node": "^20.11.5",
-    "@typescript-eslint/eslint-plugin": "^6.19.0",
+    "@typescript-eslint/eslint-plugin": "^6.19.1",
     "eslint": "^8.56.0",
     "eslint-config-prettier": "^9.1.0",
     "eslint-plugin-prettier": "^5.1.3",
diff --git a/stake-pool/cli/src/main.rs b/stake-pool/cli/src/main.rs
index 60ab61c6b..bb20ffde8 100644
--- a/stake-pool/cli/src/main.rs
+++ b/stake-pool/cli/src/main.rs
@@ -430,7 +430,7 @@ fn command_vsa_add(
     }
 
     if !config.no_update {
-        command_update(config, stake_pool_address, false, false)?;
+        command_update(config, stake_pool_address, false, false, false)?;
     }
 
     // iterate until a free account is found
@@ -488,7 +488,7 @@ fn command_vsa_remove(
     vote_account: &Pubkey,
 ) -> CommandResult {
     if !config.no_update {
-        command_update(config, stake_pool_address, false, false)?;
+        command_update(config, stake_pool_address, false, false, false)?;
     }
 
     let stake_pool = get_stake_pool(&config.rpc_client, stake_pool_address)?;
@@ -535,7 +535,7 @@ fn command_increase_validator_stake(
 ) -> CommandResult {
     let lamports = native_token::sol_to_lamports(amount);
     if !config.no_update {
-        command_update(config, stake_pool_address, false, false)?;
+        command_update(config, stake_pool_address, false, false, false)?;
     }
 
     let stake_pool = get_stake_pool(&config.rpc_client, stake_pool_address)?;
@@ -574,7 +574,7 @@ fn command_decrease_validator_stake(
 ) -> CommandResult {
     let lamports = native_token::sol_to_lamports(amount);
     if !config.no_update {
-        command_update(config, stake_pool_address, false, false)?;
+        command_update(config, stake_pool_address, false, false, false)?;
     }
 
     let stake_pool = get_stake_pool(&config.rpc_client, stake_pool_address)?;
@@ -671,7 +671,7 @@ fn command_deposit_stake(
     referrer_token_account: &Option<Pubkey>,
 ) -> CommandResult {
     if !config.no_update {
-        command_update(config, stake_pool_address, false, false)?;
+        command_update(config, stake_pool_address, false, false, false)?;
     }
 
     let stake_pool = get_stake_pool(&config.rpc_client, stake_pool_address)?;
@@ -802,7 +802,7 @@ fn command_deposit_all_stake(
     referrer_token_account: &Option<Pubkey>,
 ) -> CommandResult {
     if !config.no_update {
-        command_update(config, stake_pool_address, false, false)?;
+        command_update(config, stake_pool_address, false, false, false)?;
     }
 
     let stake_addresses = get_all_stake(&config.rpc_client, stake_authority)?;
@@ -945,7 +945,7 @@ fn command_deposit_sol(
     amount: f64,
 ) -> CommandResult {
     if !config.no_update {
-        command_update(config, stake_pool_address, false, false)?;
+        command_update(config, stake_pool_address, false, false, false)?;
     }
 
     let amount = native_token::sol_to_lamports(amount);
@@ -1139,6 +1139,7 @@ fn command_update(
     stake_pool_address: &Pubkey,
     force: bool,
     no_merge: bool,
+    stale_only: bool,
 ) -> CommandResult {
     if config.no_update {
         println!("Update requested, but --no-update flag specified, so doing nothing");
@@ -1158,14 +1159,24 @@ fn command_update(
 
     let validator_list = get_validator_list(&config.rpc_client, &stake_pool.validator_list)?;
 
-    let (mut update_list_instructions, final_instructions) =
+    let (mut update_list_instructions, final_instructions) = if stale_only {
+        spl_stake_pool::instruction::update_stale_stake_pool(
+            &spl_stake_pool::id(),
+            &stake_pool,
+            &validator_list,
+            stake_pool_address,
+            no_merge,
+            epoch_info.epoch,
+        )
+    } else {
         spl_stake_pool::instruction::update_stake_pool(
             &spl_stake_pool::id(),
             &stake_pool,
             &validator_list,
             stake_pool_address,
             no_merge,
-        );
+        )
+    };
 
     let update_list_instructions_len = update_list_instructions.len();
     if update_list_instructions_len > 0 {
@@ -1360,7 +1371,7 @@ fn command_withdraw_stake(
     pool_amount: f64,
 ) -> CommandResult {
     if !config.no_update {
-        command_update(config, stake_pool_address, false, false)?;
+        command_update(config, stake_pool_address, false, false, false)?;
     }
 
     let stake_pool = get_stake_pool(&config.rpc_client, stake_pool_address)?;
@@ -1631,7 +1642,7 @@ fn command_withdraw_sol(
     pool_amount: f64,
 ) -> CommandResult {
     if !config.no_update {
-        command_update(config, stake_pool_address, false, false)?;
+        command_update(config, stake_pool_address, false, false, false)?;
     }
 
     let stake_pool = get_stake_pool(&config.rpc_client, stake_pool_address)?;
@@ -1748,7 +1759,7 @@ fn command_set_manager(
     new_fee_receiver: &Option<Pubkey>,
 ) -> CommandResult {
     if !config.no_update {
-        command_update(config, stake_pool_address, false, false)?;
+        command_update(config, stake_pool_address, false, false, false)?;
     }
     let stake_pool = get_stake_pool(&config.rpc_client, stake_pool_address)?;
 
@@ -1800,7 +1811,7 @@ fn command_set_staker(
     new_staker: &Pubkey,
 ) -> CommandResult {
     if !config.no_update {
-        command_update(config, stake_pool_address, false, false)?;
+        command_update(config, stake_pool_address, false, false, false)?;
     }
     let mut signers = vec![config.fee_payer.as_ref(), config.manager.as_ref()];
     unique_signers!(signers);
@@ -1825,7 +1836,7 @@ fn command_set_funding_authority(
     funding_type: FundingType,
 ) -> CommandResult {
     if !config.no_update {
-        command_update(config, stake_pool_address, false, false)?;
+        command_update(config, stake_pool_address, false, false, false)?;
     }
     let mut signers = vec![config.fee_payer.as_ref(), config.manager.as_ref()];
     unique_signers!(signers);
@@ -1850,7 +1861,7 @@ fn command_set_fee(
     new_fee: FeeType,
 ) -> CommandResult {
     if !config.no_update {
-        command_update(config, stake_pool_address, false, false)?;
+        command_update(config, stake_pool_address, false, false, false)?;
     }
     let mut signers = vec![config.fee_payer.as_ref(), config.manager.as_ref()];
     unique_signers!(signers);
@@ -2418,7 +2429,7 @@ fn main() {
                 Arg::with_name("force")
                     .long("force")
                     .takes_value(false)
-                    .help("Update all balances, even if it has already been performed this epoch."),
+                    .help("Update balances, even if it has already been performed this epoch."),
             )
             .arg(
                 Arg::with_name("no_merge")
@@ -2426,6 +2437,12 @@ fn main() {
                     .takes_value(false)
                     .help("Do not automatically merge transient stakes. Useful if the stake pool is in an expected state, but the balances still need to be updated."),
             )
+            .arg(
+                Arg::with_name("stale_only")
+                    .long("stale-only")
+                    .takes_value(false)
+                    .help("If set, only updates validator list balances that have not been updated for this epoch. Otherwise, updates all validator balances on the validator list."),
+            )
         )
         .subcommand(SubCommand::with_name("withdraw-stake")
             .about("Withdraw active stake from the stake pool in exchange for pool tokens")
@@ -2903,7 +2920,8 @@ fn main() {
             let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap();
             let no_merge = arg_matches.is_present("no_merge");
             let force = arg_matches.is_present("force");
-            command_update(&config, &stake_pool_address, force, no_merge)
+            let stale_only = arg_matches.is_present("stale_only");
+            command_update(&config, &stake_pool_address, force, no_merge, stale_only)
         }
         ("withdraw-stake", Some(arg_matches)) => {
             let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap();
diff --git a/stake-pool/js/package.json b/stake-pool/js/package.json
index 68a650fa8..8c0bbdee0 100644
--- a/stake-pool/js/package.json
+++ b/stake-pool/js/package.json
@@ -63,8 +63,8 @@
     "@types/jest": "^29.5.11",
     "@types/node": "^20.11.5",
     "@types/node-fetch": "^2.6.11",
-    "@typescript-eslint/eslint-plugin": "^6.19.0",
-    "@typescript-eslint/parser": "^6.19.0",
+    "@typescript-eslint/eslint-plugin": "^6.19.1",
+    "@typescript-eslint/parser": "^6.19.1",
     "cross-env": "^7.0.3",
     "eslint": "^8.56.0",
     "eslint-config-prettier": "^9.1.0",
@@ -72,9 +72,9 @@
     "jest": "^29.0.0",
     "prettier": "^3.2.4",
     "rimraf": "^5.0.0",
-    "rollup": "^4.9.5",
+    "rollup": "^4.9.6",
     "rollup-plugin-dts": "^6.1.0",
-    "ts-jest": "^29.0.0",
+    "ts-jest": "^29.1.2",
     "typescript": "^5.3.3"
   },
   "jest": {
diff --git a/stake-pool/program/src/instruction.rs b/stake-pool/program/src/instruction.rs
index 68b094699..4b8afd8e1 100644
--- a/stake-pool/program/src/instruction.rs
+++ b/stake-pool/program/src/instruction.rs
@@ -16,7 +16,9 @@ use {
         instruction::{AccountMeta, Instruction},
         program_error::ProgramError,
         pubkey::Pubkey,
-        stake, system_program, sysvar,
+        stake,
+        stake_history::Epoch,
+        system_program, sysvar,
     },
     std::num::NonZeroU32,
 };
@@ -1496,6 +1498,47 @@ pub fn update_validator_list_balance_chunk(
     })
 }
 
+/// Creates `UpdateValidatorListBalance` instruction (update validator stake
+/// account balances)
+///
+/// Returns `None` if all validators in the given chunk has already been updated
+/// for this epoch, returns the required instruction otherwise.
+pub fn update_stale_validator_list_balance_chunk(
+    program_id: &Pubkey,
+    stake_pool: &Pubkey,
+    stake_pool_withdraw_authority: &Pubkey,
+    validator_list_address: &Pubkey,
+    reserve_stake: &Pubkey,
+    validator_list: &ValidatorList,
+    len: usize,
+    start_index: usize,
+    no_merge: bool,
+    current_epoch: Epoch,
+) -> Result<Option<Instruction>, ProgramError> {
+    let validator_list_subslice = validator_list
+        .validators
+        .get(start_index..start_index.saturating_add(len))
+        .ok_or(ProgramError::InvalidInstructionData)?;
+    if validator_list_subslice.iter().all(|info| {
+        let last_update_epoch: u64 = info.last_update_epoch.into();
+        last_update_epoch >= current_epoch
+    }) {
+        return Ok(None);
+    }
+    update_validator_list_balance_chunk(
+        program_id,
+        stake_pool,
+        stake_pool_withdraw_authority,
+        validator_list_address,
+        reserve_stake,
+        validator_list,
+        len,
+        start_index,
+        no_merge,
+    )
+    .map(Some)
+}
+
 /// Creates `UpdateStakePoolBalance` instruction (pool balance from the stake
 /// account list balances)
 pub fn update_stake_pool_balance(
@@ -1599,6 +1642,65 @@ pub fn update_stake_pool(
     (update_list_instructions, final_instructions)
 }
 
+/// Creates the `UpdateValidatorListBalance` instructions only for validators on
+/// `validator_list` that have not been updated for this epoch, and the
+/// `UpdateStakePoolBalance` instruction for fully updating the stake pool.
+///
+/// Basically same as [`update_stake_pool`], but skips validators that are
+/// already updated for this epoch
+pub fn update_stale_stake_pool(
+    program_id: &Pubkey,
+    stake_pool: &StakePool,
+    validator_list: &ValidatorList,
+    stake_pool_address: &Pubkey,
+    no_merge: bool,
+    current_epoch: Epoch,
+) -> (Vec<Instruction>, Vec<Instruction>) {
+    let (withdraw_authority, _) =
+        find_withdraw_authority_program_address(program_id, stake_pool_address);
+
+    let update_list_instructions = validator_list
+        .validators
+        .chunks(MAX_VALIDATORS_TO_UPDATE)
+        .enumerate()
+        .filter_map(|(i, chunk)| {
+            // unwrap-safety: chunk len and offset are derived
+            update_stale_validator_list_balance_chunk(
+                program_id,
+                stake_pool_address,
+                &withdraw_authority,
+                &stake_pool.validator_list,
+                &stake_pool.reserve_stake,
+                validator_list,
+                chunk.len(),
+                i.saturating_mul(MAX_VALIDATORS_TO_UPDATE),
+                no_merge,
+                current_epoch,
+            )
+            .unwrap()
+        })
+        .collect();
+
+    let final_instructions = vec![
+        update_stake_pool_balance(
+            program_id,
+            stake_pool_address,
+            &withdraw_authority,
+            &stake_pool.validator_list,
+            &stake_pool.reserve_stake,
+            &stake_pool.manager_fee_account,
+            &stake_pool.pool_mint,
+            &stake_pool.token_program_id,
+        ),
+        cleanup_removed_validator_entries(
+            program_id,
+            stake_pool_address,
+            &stake_pool.validator_list,
+        ),
+    ];
+    (update_list_instructions, final_instructions)
+}
+
 fn deposit_stake_internal(
     program_id: &Pubkey,
     stake_pool: &Pubkey,
diff --git a/stake-pool/program/src/state.rs b/stake-pool/program/src/state.rs
index 31c2ef946..b453b1b5a 100644
--- a/stake-pool/program/src/state.rs
+++ b/stake-pool/program/src/state.rs
@@ -942,9 +942,13 @@ impl Fee {
         if self.denominator == 0 {
             return Some(0);
         }
-        (amt as u128)
-            .checked_mul(self.numerator as u128)?
-            .checked_div(self.denominator as u128)
+        let numerator = (amt as u128).checked_mul(self.numerator as u128)?;
+        // ceiling the calculation by adding (denominator - 1) to the numerator
+        let denominator = self.denominator as u128;
+        numerator
+            .checked_add(denominator)?
+            .checked_sub(1)?
+            .checked_div(denominator)
     }
 
     /// Withdrawal fees have some additional restrictions,
diff --git a/token-group/js/package.json b/token-group/js/package.json
index 2001c57ea..81a0b99c5 100644
--- a/token-group/js/package.json
+++ b/token-group/js/package.json
@@ -58,8 +58,8 @@
         "@types/chai": "^4.3.11",
         "@types/mocha": "^10.0.6",
         "@types/node": "^20.11.5",
-        "@typescript-eslint/eslint-plugin": "^6.19.0",
-        "@typescript-eslint/parser": "^6.19.0",
+        "@typescript-eslint/eslint-plugin": "^6.19.1",
+        "@typescript-eslint/parser": "^6.19.1",
         "chai": "^4.3.6",
         "eslint": "^8.55.0",
         "eslint-config-prettier": "^9.1.0",
diff --git a/token-lending/js/package.json b/token-lending/js/package.json
index 551a5f3ac..7bc6fffa0 100644
--- a/token-lending/js/package.json
+++ b/token-lending/js/package.json
@@ -49,14 +49,14 @@
         "@types/eslint": "^8.56.2",
         "@types/eslint-plugin-prettier": "^3.1.0",
         "@types/node": "^20.11.5",
-        "@typescript-eslint/eslint-plugin": "^6.19.0",
-        "@typescript-eslint/parser": "^6.19.0",
+        "@typescript-eslint/eslint-plugin": "^6.19.1",
+        "@typescript-eslint/parser": "^6.19.1",
         "eslint": "^8.56.0",
         "eslint-config-prettier": "^9.1.0",
         "eslint-plugin-prettier": "^5.1.3",
         "gh-pages": "^6.1.1",
         "prettier": "^3.2.4",
-        "rollup": "^4.9.5",
+        "rollup": "^4.9.6",
         "ts-node": "^10.9.2",
         "tslib": "^2.3.0",
         "typedoc": "^0.25.7",
diff --git a/token-metadata/js/package.json b/token-metadata/js/package.json
index 58ac6c794..0308891fe 100644
--- a/token-metadata/js/package.json
+++ b/token-metadata/js/package.json
@@ -59,8 +59,8 @@
         "@types/chai": "^4.3.11",
         "@types/mocha": "^10.0.6",
         "@types/node": "^20.11.5",
-        "@typescript-eslint/eslint-plugin": "^6.19.0",
-        "@typescript-eslint/parser": "^6.19.0",
+        "@typescript-eslint/eslint-plugin": "^6.19.1",
+        "@typescript-eslint/parser": "^6.19.1",
         "chai": "^4.3.6",
         "eslint": "^8.56.0",
         "eslint-config-prettier": "^9.1.0",
diff --git a/token-swap/js/package.json b/token-swap/js/package.json
index 27e1b65e3..0e1278f2a 100644
--- a/token-swap/js/package.json
+++ b/token-swap/js/package.json
@@ -58,8 +58,8 @@
     "@types/chai-as-promised": "^7.1.4",
     "@types/chai": "^4.3.11",
     "@types/mocha": "^10.0.6",
-    "@typescript-eslint/eslint-plugin": "^6.19.0",
-    "@typescript-eslint/parser": "^6.19.0",
+    "@typescript-eslint/eslint-plugin": "^6.19.1",
+    "@typescript-eslint/parser": "^6.19.1",
     "eslint": "^8.56.0",
     "eslint-plugin-import": "^2.29.1",
     "eslint-config-prettier": "^9.1.0",
diff --git a/token/cli/src/clap_app.rs b/token/cli/src/clap_app.rs
index 57f9fff98..7def6bcbe 100644
--- a/token/cli/src/clap_app.rs
+++ b/token/cli/src/clap_app.rs
@@ -108,6 +108,8 @@ pub enum CommandName {
     InitializeMetadata,
     UpdateMetadata,
     InitializeGroup,
+    UpdateGroupMaxSize,
+    InitializeMember,
     UpdateConfidentialTransferSettings,
     ConfigureConfidentialTransferAccount,
     EnableConfidentialCredits,
@@ -189,6 +191,7 @@ pub enum CliAuthorityType {
     Metadata,
     GroupPointer,
     GroupMemberPointer,
+    Group,
 }
 impl TryFrom<CliAuthorityType> for AuthorityType {
     type Error = Error;
@@ -216,6 +219,9 @@ impl TryFrom<CliAuthorityType> for AuthorityType {
             }
             CliAuthorityType::GroupPointer => Ok(AuthorityType::GroupPointer),
             CliAuthorityType::GroupMemberPointer => Ok(AuthorityType::GroupMemberPointer),
+            CliAuthorityType::Group => {
+                Err("Group update authority does not map to a token authority type".into())
+            }
         }
     }
 }
@@ -1007,6 +1013,86 @@ pub fn app<'a, 'b>(
                         ),
                 )
         )
+        .subcommand(
+            SubCommand::with_name(CommandName::UpdateGroupMaxSize.into())
+                .about("Updates the maximum number of members for a group.")
+                .arg(
+                    Arg::with_name("token")
+                        .validator(is_valid_pubkey)
+                        .value_name("TOKEN_MINT_ADDRESS")
+                        .takes_value(true)
+                        .required(true)
+                        .index(1)
+                        .help("The token address of the group account."),
+                )
+                .arg(
+                        Arg::with_name("new_max_size")
+                        .validator(is_amount)
+                        .value_name("NEW_MAX_SIZE")
+                        .takes_value(true)
+                        .required(true)
+                        .index(2)
+                        .help("The number of members in the group."),
+                    )
+                .arg(
+                    Arg::with_name("update_authority")
+                        .long("update-authority")
+                        .value_name("SIGNER")
+                        .validator(is_valid_signer)
+                        .takes_value(true)
+                        .help(
+                            "Specify the update authority address. \
+                             Defaults to the client keypair address."
+                        ),
+                )
+        )
+        .subcommand(
+            SubCommand::with_name(CommandName::InitializeMember.into())
+                .about("Initialize group member extension on a token mint")
+                .arg(
+                    Arg::with_name("token")
+                        .validator(is_valid_pubkey)
+                        .value_name("TOKEN_MINT_ADDRESS")
+                        .takes_value(true)
+                        .required(true)
+                        .index(1)
+                        .help("The token address of the member account."),
+                )
+                .arg(
+                    Arg::with_name("group_token")
+                        .validator(is_valid_pubkey)
+                        .value_name("TOKEN_MINT_ADDRESS")
+                        .takes_value(true)
+                        .required(true)
+                        .index(2)
+                        .help("The token address of the group account that the token will join."),
+                )
+                .arg(
+                    Arg::with_name("mint_authority")
+                        .long("mint-authority")
+                        .alias("owner")
+                        .value_name("KEYPAIR")
+                        .validator(is_valid_signer)
+                        .takes_value(true)
+                        .help(
+                            "Specify the mint authority keypair. \
+                             This may be a keypair file or the ASK keyword. \
+                             Defaults to the client keypair."
+                        ),
+                )
+                .arg(
+                    Arg::with_name("group_update_authority")
+                        .long("group-update-authority")
+                        .value_name("KEYPAIR")
+                        .validator(is_valid_signer)
+                        .takes_value(true)
+                        .help(
+                            "Specify the update authority keypair. \
+                             This may be a keypair file or the ASK keyword. \
+                             Defaults to the client keypair address."
+                        ),
+                )
+        )
         .subcommand(
             SubCommand::with_name(CommandName::CreateAccount.into())
                 .about("Create a new token account")
diff --git a/token/cli/src/command.rs b/token/cli/src/command.rs
index db72e329c..8862e6a0f 100644
--- a/token/cli/src/command.rs
+++ b/token/cli/src/command.rs
@@ -70,6 +70,7 @@ use {
         client::{ProgramRpcClientSendTransaction, RpcClientResponse},
         token::{ExtensionInitializationParams, Token},
     },
+    spl_token_group_interface::state::TokenGroup,
     spl_token_metadata_interface::state::{Field, TokenMetadata},
     std::{collections::HashMap, fmt::Display, process::exit, rc::Rc, str::FromStr, sync::Arc},
 };
@@ -627,6 +628,63 @@ async fn command_initialize_group(
         }
     })
 }
+
+#[allow(clippy::too_many_arguments)]
+async fn command_update_group_max_size(
+    config: &Config<'_>,
+    token_pubkey: Pubkey,
+    update_authority: Pubkey,
+    new_max_size: u32,
+    bulk_signers: Vec<Arc<dyn Signer>>,
+) -> CommandResult {
+    let token = token_client_from_config(config, &token_pubkey, None)?;
+
+    let res = token
+        .token_group_update_max_size(&update_authority, new_max_size, &bulk_signers)
+        .await?;
+
+    let tx_return = finish_tx(config, &res, false).await?;
+    Ok(match tx_return {
+        TransactionReturnData::CliSignature(signature) => {
+            config.output_format.formatted_string(&signature)
+        }
+        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
+            config.output_format.formatted_string(&sign_only_data)
+        }
+    })
+}
+
+async fn command_initialize_member(
+    config: &Config<'_>,
+    member_token_pubkey: Pubkey,
+    mint_authority: Pubkey,
+    group_token_pubkey: Pubkey,
+    group_update_authority: Pubkey,
+    bulk_signers: Vec<Arc<dyn Signer>>,
+) -> CommandResult {
+    let token = token_client_from_config(config, &member_token_pubkey, None)?;
+
+    let res = token
+        .token_group_initialize_member_with_rent_transfer(
+            &config.fee_payer()?.pubkey(),
+            &mint_authority,
+            &group_token_pubkey,
+            &group_update_authority,
+            &bulk_signers,
+        )
+        .await?;
+
+    let tx_return = finish_tx(config, &res, false).await?;
+    Ok(match tx_return {
+        TransactionReturnData::CliSignature(signature) => {
+            config.output_format.formatted_string(&signature)
+        }
+        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
+            config.output_format.formatted_string(&sign_only_data)
+        }
+    })
+}
+
 async fn command_set_transfer_fee(
     config: &Config<'_>,
     token_pubkey: Pubkey,
@@ -959,6 +1017,13 @@ async fn command_authorize(
                         ))
                     }
                 }
+                CliAuthorityType::Group => {
+                    if let Ok(extension) = mint.get_extension::<TokenGroup>() {
+                        Ok(Option::<Pubkey>::from(extension.update_authority))
+                    } else {
+                        Err(format!("Mint `{}` does not support token groups", account))
+                    }
+                }
             }?;
 
             Ok((account, previous_authority))
@@ -999,6 +1064,7 @@ async fn command_authorize(
                 | CliAuthorityType::MetadataPointer
                 | CliAuthorityType::Metadata
                 | CliAuthorityType::GroupPointer
+                | CliAuthorityType::Group
                 | CliAuthorityType::GroupMemberPointer => Err(format!(
                     "Authority type `{auth_str}` not supported for SPL Token accounts",
                 )),
@@ -1050,20 +1116,28 @@ async fn command_authorize(
         ),
     );
 
-    let res = if let CliAuthorityType::Metadata = authority_type {
-        token
-            .token_metadata_update_authority(&authority, new_authority, &bulk_signers)
-            .await?
-    } else {
-        token
-            .set_authority(
-                &account,
-                &authority,
-                new_authority.as_ref(),
-                authority_type.try_into()?,
-                &bulk_signers,
-            )
-            .await?
+    let res = match authority_type {
+        CliAuthorityType::Metadata => {
+            token
+                .token_metadata_update_authority(&authority, new_authority, &bulk_signers)
+                .await?
+        }
+        CliAuthorityType::Group => {
+            token
+                .token_group_update_authority(&authority, new_authority, &bulk_signers)
+                .await?
+        }
+        _ => {
+            token
+                .set_authority(
+                    &account,
+                    &authority,
+                    new_authority.as_ref(),
+                    authority_type.try_into()?,
+                    &bulk_signers,
+                )
+                .await?
+        }
     };
 
     let tx_return = finish_tx(config, &res, false).await?;
@@ -3507,6 +3581,52 @@ pub async fn process_command<'a>(
             )
             .await
         }
+        (CommandName::UpdateGroupMaxSize, arg_matches) => {
+            let token_pubkey = pubkey_of_signer(arg_matches, "token", &mut wallet_manager)
+                .unwrap()
+                .unwrap();
+            let new_max_size = value_t_or_exit!(arg_matches, "new_max_size", u32);
+            let (update_authority_signer, update_authority) =
+                config.signer_or_default(arg_matches, "update_authority", &mut wallet_manager);
+            let bulk_signers = vec![update_authority_signer];
+
+            command_update_group_max_size(
+                config,
+                token_pubkey,
+                update_authority,
+                new_max_size,
+                bulk_signers,
+            )
+            .await
+        }
+        (CommandName::InitializeMember, arg_matches) => {
+            let member_token_pubkey = pubkey_of_signer(arg_matches, "token", &mut wallet_manager)
+                .unwrap()
+                .unwrap();
+            let group_token_pubkey =
+                pubkey_of_signer(arg_matches, "group_token", &mut wallet_manager)
+                    .unwrap()
+                    .unwrap();
+            let (mint_authority_signer, mint_authority) =
+                config.signer_or_default(arg_matches, "mint_authority", &mut wallet_manager);
+            let (group_update_authority_signer, group_update_authority) = config.signer_or_default(
+                arg_matches,
+                "group_update_authority",
+                &mut wallet_manager,
+            );
+            let mut bulk_signers = vec![mint_authority_signer];
+            push_signer_with_dedup(group_update_authority_signer, &mut bulk_signers);
+
+            command_initialize_member(
+                config,
+                member_token_pubkey,
+                mint_authority,
+                group_token_pubkey,
+                group_update_authority,
+                bulk_signers,
+            )
+            .await
+        }
         (CommandName::CreateAccount, arg_matches) => {
             let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager)
                 .unwrap()
diff --git a/token/client/Cargo.toml b/token/client/Cargo.toml
index 7b491f63a..330c59c84 100644
--- a/token/client/Cargo.toml
+++ b/token/client/Cargo.toml
@@ -26,7 +26,7 @@ spl-token = { version = "4.0", path="../program", features = [ "no-entrypoint" ]
 spl-token-2022 = { version = "1.0", path="../program-2022" }
 spl-token-group-interface = { version = "0.1", path="../../token-group/interface" }
 spl-token-metadata-interface = { version = "0.2", path="../../token-metadata/interface" }
-spl-transfer-hook-interface = { version = "0.4", path="../transfer-hook/interface" }
+spl-transfer-hook-interface = { version = "0.5", path="../transfer-hook/interface" }
 thiserror = "1.0"
 
 [features]
diff --git a/token/js/package.json b/token/js/package.json
index ae308512b..f37149bd9 100644
--- a/token/js/package.json
+++ b/token/js/package.json
@@ -68,8 +68,8 @@
         "@types/mocha": "^10.0.6",
         "@types/node": "^20.11.5",
         "@types/node-fetch": "^2.6.11",
-        "@typescript-eslint/eslint-plugin": "^6.19.0",
-        "@typescript-eslint/parser": "^6.19.0",
+        "@typescript-eslint/eslint-plugin": "^6.19.1",
+        "@typescript-eslint/parser": "^6.19.1",
         "chai": "^4.3.6",
         "chai-as-promised": "^7.1.1",
         "eslint": "^8.56.0",
diff --git a/token/js/src/extensions/transferFee/instructions.ts b/token/js/src/extensions/transferFee/instructions.ts
index 3562debe1..b2db4b230 100644
--- a/token/js/src/extensions/transferFee/instructions.ts
+++ b/token/js/src/extensions/transferFee/instructions.ts
@@ -1,7 +1,7 @@
 import { struct, u16, u8 } from '@solana/buffer-layout';
-import { publicKey, u64 } from '@solana/buffer-layout-utils';
-import type { AccountMeta, Signer } from '@solana/web3.js';
-import { PublicKey, TransactionInstruction } from '@solana/web3.js';
+import { u64 } from '@solana/buffer-layout-utils';
+import type { AccountMeta, Signer, PublicKey } from '@solana/web3.js';
+import { TransactionInstruction } from '@solana/web3.js';
 import { programSupportsExtensions, TOKEN_2022_PROGRAM_ID } from '../../constants.js';
 import {
     TokenInvalidInstructionDataError,
@@ -12,6 +12,7 @@ import {
 } from '../../errors.js';
 import { addSigners } from '../../instructions/internal.js';
 import { TokenInstruction } from '../../instructions/types.js';
+import { COptionPublicKeyLayout } from '../../serialization.js';
 
 export enum TransferFeeInstruction {
     InitializeTransferFeeConfig = 0,
@@ -28,10 +29,8 @@ export enum TransferFeeInstruction {
 export interface InitializeTransferFeeConfigInstructionData {
     instruction: TokenInstruction.TransferFeeExtension;
     transferFeeInstruction: TransferFeeInstruction.InitializeTransferFeeConfig;
-    transferFeeConfigAuthorityOption: 1 | 0;
-    transferFeeConfigAuthority: PublicKey;
-    withdrawWithheldAuthorityOption: 1 | 0;
-    withdrawWithheldAuthority: PublicKey;
+    transferFeeConfigAuthority: PublicKey | null;
+    withdrawWithheldAuthority: PublicKey | null;
     transferFeeBasisPoints: number;
     maximumFee: bigint;
 }
@@ -40,10 +39,8 @@ export interface InitializeTransferFeeConfigInstructionData {
 export const initializeTransferFeeConfigInstructionData = struct<InitializeTransferFeeConfigInstructionData>([
     u8('instruction'),
     u8('transferFeeInstruction'),
-    u8('transferFeeConfigAuthorityOption'),
-    publicKey('transferFeeConfigAuthority'),
-    u8('withdrawWithheldAuthorityOption'),
-    publicKey('withdrawWithheldAuthority'),
+    new COptionPublicKeyLayout('transferFeeConfigAuthority'),
+    new COptionPublicKeyLayout('withdrawWithheldAuthority'),
     u16('transferFeeBasisPoints'),
     u64('maximumFee'),
 ]);
@@ -78,10 +75,8 @@ export function createInitializeTransferFeeConfigInstruction(
         {
             instruction: TokenInstruction.TransferFeeExtension,
             transferFeeInstruction: TransferFeeInstruction.InitializeTransferFeeConfig,
-            transferFeeConfigAuthorityOption: transferFeeConfigAuthority ? 1 : 0,
-            transferFeeConfigAuthority: transferFeeConfigAuthority || new PublicKey(0),
-            withdrawWithheldAuthorityOption: withdrawWithheldAuthority ? 1 : 0,
-            withdrawWithheldAuthority: withdrawWithheldAuthority || new PublicKey(0),
+            transferFeeConfigAuthority: transferFeeConfigAuthority,
+            withdrawWithheldAuthority: withdrawWithheldAuthority,
             transferFeeBasisPoints: transferFeeBasisPoints,
             maximumFee: maximumFee,
         },
@@ -174,9 +169,7 @@ export function decodeInitializeTransferFeeConfigInstructionUnchecked({
     const {
         instruction,
         transferFeeInstruction,
-        transferFeeConfigAuthorityOption,
         transferFeeConfigAuthority,
-        withdrawWithheldAuthorityOption,
         withdrawWithheldAuthority,
         transferFeeBasisPoints,
         maximumFee,
@@ -190,8 +183,8 @@ export function decodeInitializeTransferFeeConfigInstructionUnchecked({
         data: {
             instruction,
             transferFeeInstruction,
-            transferFeeConfigAuthority: transferFeeConfigAuthorityOption ? transferFeeConfigAuthority : null,
-            withdrawWithheldAuthority: withdrawWithheldAuthorityOption ? withdrawWithheldAuthority : null,
+            transferFeeConfigAuthority,
+            withdrawWithheldAuthority,
             transferFeeBasisPoints,
             maximumFee,
         },
diff --git a/token/js/src/serialization.ts b/token/js/src/serialization.ts
new file mode 100644
index 000000000..57d426baa
--- /dev/null
+++ b/token/js/src/serialization.ts
@@ -0,0 +1,39 @@
+import { Layout } from '@solana/buffer-layout';
+import { publicKey } from '@solana/buffer-layout-utils';
+import type { PublicKey } from '@solana/web3.js';
+
+export class COptionPublicKeyLayout extends Layout<PublicKey | null> {
+    private publicKeyLayout: Layout<PublicKey>;
+
+    constructor(property?: string | undefined) {
+        super(-1, property);
+        this.publicKeyLayout = publicKey();
+    }
+
+    decode(buffer: Uint8Array, offset: number = 0): PublicKey | null {
+        const option = buffer[offset];
+        if (option === 0) {
+            return null;
+        }
+        return this.publicKeyLayout.decode(buffer, offset + 1);
+    }
+
+    encode(src: PublicKey | null, buffer: Uint8Array, offset: number = 0): number {
+        if (src === null) {
+            buffer[offset] = 0;
+            return 1;
+        } else {
+            buffer[offset] = 1;
+            this.publicKeyLayout.encode(src, buffer, offset + 1);
+            return 33;
+        }
+    }
+
+    getSpan(buffer?: Uint8Array, offset: number = 0): number {
+        if (buffer) {
+            const option = buffer[offset];
+            return option === 0 ? 1 : 1 + this.publicKeyLayout.span;
+        }
+        return 1 + this.publicKeyLayout.span;
+    }
+}
diff --git a/token/program-2022/Cargo.toml b/token/program-2022/Cargo.toml
index e55a16a8c..58143c353 100644
--- a/token/program-2022/Cargo.toml
+++ b/token/program-2022/Cargo.toml
@@ -33,12 +33,12 @@ spl-memo = { version = "4.0.0", path = "../../memo/program", features = [ "no-en
 spl-token = { version = "4.0",  path = "../program", features = ["no-entrypoint"] }
 spl-token-group-interface = { version = "0.1.0", path = "../../token-group/interface" }
 spl-token-metadata-interface = { version = "0.2.0", path = "../../token-metadata/interface" }
-spl-transfer-hook-interface = { version = "0.4.0", path = "../transfer-hook/interface" }
+spl-transfer-hook-interface = { version = "0.5.0", path = "../transfer-hook/interface" }
 spl-type-length-value = { version = "0.3.0", path = "../../libraries/type-length-value" }
 spl-pod = { version = "0.1.0", path = "../../libraries/pod" }
 thiserror = "1.0"
 serde = { version = "1.0.195", optional = true }
-serde_with = { version = "3.4.0", optional = true }
+serde_with = { version = "3.5.0", optional = true }
 base64 = { version = "0.21.7", optional = true }
 
 [dev-dependencies]
diff --git a/token/transfer-hook/cli/Cargo.toml b/token/transfer-hook/cli/Cargo.toml
index 2db588c5a..ed76df043 100644
--- a/token/transfer-hook/cli/Cargo.toml
+++ b/token/transfer-hook/cli/Cargo.toml
@@ -6,7 +6,7 @@ homepage = "https://spl.solana.com/token"
 license = "Apache-2.0"
 name = "spl-transfer-hook-cli"
 repository = "https://github.com/solana-labs/solana-program-library"
-version = "0.1.0"
+version = "0.1.1"
 
 [dependencies]
 clap = { version = "3", features = ["cargo"] }
@@ -17,7 +17,7 @@ solana-client = "=1.17.13"
 solana-logger = "=1.17.13"
 solana-remote-wallet = "=1.17.13"
 solana-sdk = "=1.17.13"
-spl-transfer-hook-interface = { version = "0.4", path = "../interface" }
+spl-transfer-hook-interface = { version = "0.5", path = "../interface" }
 spl-tlv-account-resolution = { version = "0.5.1" , path = "../../../libraries/tlv-account-resolution", features = ["serde-traits"] }
 strum = "0.25"
 strum_macros = "0.25"
diff --git a/token/transfer-hook/example/Cargo.toml b/token/transfer-hook/example/Cargo.toml
index 5c450600b..84d3ff9e1 100644
--- a/token/transfer-hook/example/Cargo.toml
+++ b/token/transfer-hook/example/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "spl-transfer-hook-example"
-version = "0.4.0"
+version = "0.5.0"
 description = "Solana Program Library Transfer Hook Example Program"
 authors = ["Solana Labs Maintainers <maintainers@solanalabs.com>"]
 repository = "https://github.com/solana-labs/solana-program-library"
@@ -16,7 +16,7 @@ arrayref = "0.3.7"
 solana-program = "1.17.13"
 spl-tlv-account-resolution = { version = "0.5" , path = "../../../libraries/tlv-account-resolution" }
 spl-token-2022 = { version = "1.0",  path = "../../program-2022", features = ["no-entrypoint"] }
-spl-transfer-hook-interface = { version = "0.4" , path = "../interface" }
+spl-transfer-hook-interface = { version = "0.5" , path = "../interface" }
 spl-type-length-value = { version = "0.3" , path = "../../../libraries/type-length-value" }
 
 [dev-dependencies]
diff --git a/token/transfer-hook/interface/Cargo.toml b/token/transfer-hook/interface/Cargo.toml
index 96188bec7..b747c3c8c 100644
--- a/token/transfer-hook/interface/Cargo.toml
+++ b/token/transfer-hook/interface/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "spl-transfer-hook-interface"
-version = "0.4.1"
+version = "0.5.0"
 description = "Solana Program Library Transfer Hook Interface"
 authors = ["Solana Labs Maintainers <maintainers@solanalabs.com>"]
 repository = "https://github.com/solana-labs/solana-program-library"
diff --git a/token/transfer-hook/interface/README.md b/token/transfer-hook/interface/README.md
index 84dab20d0..376cdb1b1 100644
--- a/token/transfer-hook/interface/README.md
+++ b/token/transfer-hook/interface/README.md
@@ -142,7 +142,7 @@ program:
 
 The `spl-transfer-hook-interface` library provides offchain and onchain helpers
 for resolving the additional accounts required. See
-[invoke.rs](https://github.com/solana-labs/solana-program-library/tree/master/token/transfer-hook/interface/src/invoke.rs)
+[onchain.rs](https://github.com/solana-labs/solana-program-library/tree/master/token/transfer-hook/interface/src/onchain.rs)
 for usage on-chain, and
 [offchain.rs](https://github.com/solana-labs/solana-program-library/tree/master/token/transfer-hook/interface/src/offchain.rs)
 for fetching the additional required account metas with any async off-chain client


Test patch:
diff --git a/governance/test-sdk/Cargo.toml b/governance/test-sdk/Cargo.toml
index 0aec82828..bdbd543a0 100644
--- a/governance/test-sdk/Cargo.toml
+++ b/governance/test-sdk/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "spl-governance-test-sdk"
-version = "0.1.3"
+version = "0.1.4"
 description = "Solana Program Library Governance Program Test SDK"
 authors = ["Solana Labs Maintainers <maintainers@solanalabs.com>"]
 repository = "https://github.com/solana-labs/solana-program-library"
@@ -19,5 +19,7 @@ serde_derive = "1.0.103"
 solana-program = "1.17.13"
 solana-program-test = "1.17.13"
 solana-sdk = "1.17.13"
-spl-token = { version = "4.0", path = "../../token/program", features = [ "no-entrypoint" ] }
+spl-token = { version = "4.0", path = "../../token/program", features = [
+    "no-entrypoint",
+] }
 thiserror = "1.0"
diff --git a/stake-pool/program/tests/helpers/mod.rs b/stake-pool/program/tests/helpers/mod.rs
index d82dbe5ad..23f146578 100644
--- a/stake-pool/program/tests/helpers/mod.rs
+++ b/stake-pool/program/tests/helpers/mod.rs
@@ -897,15 +897,17 @@ impl StakePoolAccounts {
     }
 
     pub fn calculate_fee(&self, amount: u64) -> u64 {
-        amount * self.epoch_fee.numerator / self.epoch_fee.denominator
+        (amount * self.epoch_fee.numerator + self.epoch_fee.denominator - 1)
+            / self.epoch_fee.denominator
     }
 
     pub fn calculate_withdrawal_fee(&self, pool_tokens: u64) -> u64 {
-        pool_tokens * self.withdrawal_fee.numerator / self.withdrawal_fee.denominator
+        (pool_tokens * self.withdrawal_fee.numerator + self.withdrawal_fee.denominator - 1)
+            / self.withdrawal_fee.denominator
     }
 
     pub fn calculate_inverse_withdrawal_fee(&self, pool_tokens: u64) -> u64 {
-        pool_tokens * self.withdrawal_fee.denominator
+        (pool_tokens * self.withdrawal_fee.denominator + self.withdrawal_fee.denominator - 1)
             / (self.withdrawal_fee.denominator - self.withdrawal_fee.numerator)
     }
 
@@ -914,7 +916,8 @@ impl StakePoolAccounts {
     }
 
     pub fn calculate_sol_deposit_fee(&self, pool_tokens: u64) -> u64 {
-        pool_tokens * self.sol_deposit_fee.numerator / self.sol_deposit_fee.denominator
+        (pool_tokens * self.sol_deposit_fee.numerator + self.sol_deposit_fee.denominator - 1)
+            / self.sol_deposit_fee.denominator
     }
 
     pub fn calculate_sol_referral_fee(&self, deposit_fee_collected: u64) -> u64 {
diff --git a/stake-pool/program/tests/update_validator_list_balance.rs b/stake-pool/program/tests/update_validator_list_balance.rs
index 9a6043598..31b4e484b 100644
--- a/stake-pool/program/tests/update_validator_list_balance.rs
+++ b/stake-pool/program/tests/update_validator_list_balance.rs
@@ -141,7 +141,7 @@ async fn setup(
 
     let last_blockhash = context
         .banks_client
-        .get_new_latest_blockhash(&context.last_blockhash)
+        .get_new_latest_blockhash(&last_blockhash)
         .await
         .unwrap();
 
@@ -661,7 +661,7 @@ async fn merge_transient_stake_after_remove() {
 
 #[tokio::test]
 async fn success_with_burned_tokens() {
-    let num_validators = 5;
+    let num_validators = 1;
     let (mut context, last_blockhash, stake_pool_accounts, _, deposit_accounts, _, _, mut slot) =
         setup(num_validators).await;
 
@@ -693,6 +693,16 @@ async fn success_with_burned_tokens() {
     .await
     .unwrap();
 
+    let slots_per_epoch = context.genesis_config().epoch_schedule.slots_per_epoch;
+    slot += slots_per_epoch;
+    context.warp_to_slot(slot).unwrap();
+
+    let last_blockhash = context
+        .banks_client
+        .get_new_latest_blockhash(&last_blockhash)
+        .await
+        .unwrap();
+
     let mint_info = get_account(
         &mut context.banks_client,
         &stake_pool_accounts.pool_mint.pubkey(),
@@ -701,10 +711,6 @@ async fn success_with_burned_tokens() {
     let mint = Mint::unpack(&mint_info.data).unwrap();
     assert_ne!(mint.supply, stake_pool.pool_token_supply);
 
-    let slots_per_epoch = context.genesis_config().epoch_schedule.slots_per_epoch;
-    slot += slots_per_epoch;
-    context.warp_to_slot(slot).unwrap();
-
     stake_pool_accounts
         .update_all(
             &mut context.banks_client,
diff --git a/stake-pool/program/tests/withdraw_sol.rs b/stake-pool/program/tests/withdraw_sol.rs
index 67b60a7a2..e8eb2423a 100644
--- a/stake-pool/program/tests/withdraw_sol.rs
+++ b/stake-pool/program/tests/withdraw_sol.rs
@@ -214,7 +214,7 @@ async fn fail_overdraw_reserve() {
         .await;
     assert!(error.is_none(), "{:?}", error);
 
-    // try to withdraw one lamport, will overdraw
+    // try to withdraw one lamport after fees, will overdraw
     let error = stake_pool_accounts
         .withdraw_sol(
             &mut context.banks_client,
@@ -222,7 +222,7 @@ async fn fail_overdraw_reserve() {
             &context.last_blockhash,
             &user,
             &pool_token_account,
-            1,
+            2,
             None,
         )
         .await
diff --git a/stake-pool/program/tests/withdraw_with_fee.rs b/stake-pool/program/tests/withdraw_with_fee.rs
index b55283fbd..7a8e000c1 100644
--- a/stake-pool/program/tests/withdraw_with_fee.rs
+++ b/stake-pool/program/tests/withdraw_with_fee.rs
@@ -6,10 +6,10 @@ mod helpers;
 use {
     bincode::deserialize,
     helpers::*,
-    solana_program::{borsh0_10::try_from_slice_unchecked, pubkey::Pubkey, stake},
+    solana_program::{pubkey::Pubkey, stake},
     solana_program_test::*,
     solana_sdk::signature::{Keypair, Signer},
-    spl_stake_pool::{minimum_stake_lamports, state},
+    spl_stake_pool::minimum_stake_lamports,
 };
 
 #[tokio::test]
@@ -183,20 +183,8 @@ async fn success_empty_out_stake_with_fee() {
             .await;
     let lamports_to_withdraw =
         validator_stake_account.lamports - minimum_stake_lamports(&meta, stake_minimum_delegation);
-    let stake_pool_account = get_account(
-        &mut context.banks_client,
-        &stake_pool_accounts.stake_pool.pubkey(),
-    )
-    .await;
-    let stake_pool =
-        try_from_slice_unchecked::<state::StakePool>(stake_pool_account.data.as_slice()).unwrap();
-    let fee = stake_pool.stake_withdrawal_fee;
-    let inverse_fee = state::Fee {
-        numerator: fee.denominator - fee.numerator,
-        denominator: fee.denominator,
-    };
     let pool_tokens_to_withdraw =
-        lamports_to_withdraw * inverse_fee.denominator / inverse_fee.numerator;
+        stake_pool_accounts.calculate_inverse_withdrawal_fee(lamports_to_withdraw);
 
     let last_blockhash = context
         .banks_client
diff --git a/token/cli/tests/command.rs b/token/cli/tests/command.rs
index 20111a9ce..bc7d92276 100644
--- a/token/cli/tests/command.rs
+++ b/token/cli/tests/command.rs
@@ -46,7 +46,7 @@ use {
         },
         token::Token,
     },
-    spl_token_group_interface::state::TokenGroup,
+    spl_token_group_interface::state::{TokenGroup, TokenGroupMember},
     spl_token_metadata_interface::state::TokenMetadata,
     std::{ffi::OsString, path::PathBuf, str::FromStr, sync::Arc},
     tempfile::NamedTempFile,
@@ -3842,4 +3842,94 @@ async fn group(test_validator: &TestValidator, payer: &Keypair) {
         extension_pointer.group_address,
         Some(mint).try_into().unwrap()
     );
+
+    let new_max_size = 12;
+
+    // Update token-group max-size
+    process_test_command(
+        &config,
+        payer,
+        &[
+            "spl-token",
+            CommandName::UpdateGroupMaxSize.into(),
+            &mint.to_string(),
+            &new_max_size.to_string(),
+        ],
+    )
+    .await
+    .unwrap();
+
+    let account = config.rpc_client.get_account(&mint).await.unwrap();
+
+    let updated_mint_state = StateWithExtensionsOwned::<Mint>::unpack(account.data).unwrap();
+
+    let updated_extension = updated_mint_state.get_extension::<TokenGroup>().unwrap();
+    assert_eq!(updated_extension.max_size, new_max_size.into());
+
+    // Create member token
+    let result = process_test_command(
+        &config,
+        payer,
+        &[
+            "spl-token",
+            CommandName::CreateToken.into(),
+            "--program-id",
+            &program_id.to_string(),
+            "--enable-member",
+        ],
+    )
+    .await
+    .unwrap();
+
+    let value: serde_json::Value = serde_json::from_str(&result).unwrap();
+    let member_mint =
+        Pubkey::from_str(value["commandOutput"]["address"].as_str().unwrap()).unwrap();
+
+    // Initialize it as a member of the group
+    process_test_command(
+        &config,
+        payer,
+        &[
+            "spl-token",
+            CommandName::InitializeMember.into(),
+            &member_mint.to_string(),
+            &mint.to_string(),
+        ],
+    )
+    .await
+    .unwrap();
+
+    let account = config.rpc_client.get_account(&mint).await.unwrap();
+    let group_mint_state = StateWithExtensionsOwned::<Mint>::unpack(account.data).unwrap();
+    let extension = group_mint_state.get_extension::<TokenGroup>().unwrap();
+    assert_eq!(u32::from(extension.size), 1);
+
+    let account = config.rpc_client.get_account(&member_mint).await.unwrap();
+    let member_mint_state = StateWithExtensionsOwned::<Mint>::unpack(account.data).unwrap();
+    let extension = member_mint_state
+        .get_extension::<TokenGroupMember>()
+        .unwrap();
+    assert_eq!(extension.group, mint);
+    assert_eq!(extension.mint, member_mint);
+    assert_eq!(u32::from(extension.member_number), 1);
+
+    // update authority
+    process_test_command(
+        &config,
+        payer,
+        &[
+            "spl-token",
+            CommandName::Authorize.into(),
+            &mint.to_string(),
+            "group",
+            &mint.to_string(),
+        ],
+    )
+    .await
+    .unwrap();
+
+    let account = config.rpc_client.get_account(&mint).await.unwrap();
+    let mint_state = StateWithExtensionsOwned::<Mint>::unpack(account.data).unwrap();
+    let extension = mint_state.get_extension::<TokenGroup>().unwrap();
+    assert_eq!(extension.update_authority, Some(mint).try_into().unwrap());
 }
diff --git a/token/js/test/e2e-2022/transferFee.test.ts b/token/js/test/e2e-2022/transferFee.test.ts
index 8e5cad626..e007ce36a 100644
--- a/token/js/test/e2e-2022/transferFee.test.ts
+++ b/token/js/test/e2e-2022/transferFee.test.ts
@@ -44,18 +44,19 @@ describe('transferFee', () => {
     let sourceAccount: PublicKey;
     let destinationAccount: PublicKey;
     let mint: PublicKey;
-    let transferFeeConfigAuthority: Keypair;
-    let withdrawWithheldAuthority: Keypair;
+    let mintAuthority: Keypair;
     before(async () => {
         connection = await getConnection();
         payer = await newAccountWithLamports(connection, 1000000000);
-        transferFeeConfigAuthority = Keypair.generate();
-        withdrawWithheldAuthority = Keypair.generate();
     });
-    beforeEach(async () => {
-        const mintAuthority = Keypair.generate();
+
+    async function setupTransferFeeMint(
+        transferFeeConfigAuthority: PublicKey | null,
+        withdrawWithheldAuthority: PublicKey | null
+    ) {
         const mintKeypair = Keypair.generate();
         mint = mintKeypair.publicKey;
+        mintAuthority = Keypair.generate();
         const mintLen = getMintLen(MINT_EXTENSIONS);
         const mintLamports = await connection.getMinimumBalanceForRentExemption(mintLen);
         const mintTransaction = new Transaction().add(
@@ -68,8 +69,8 @@ describe('transferFee', () => {
             }),
             createInitializeTransferFeeConfigInstruction(
                 mint,
-                transferFeeConfigAuthority.publicKey,
-                withdrawWithheldAuthority.publicKey,
+                transferFeeConfigAuthority,
+                withdrawWithheldAuthority,
                 FEE_BASIS_POINTS,
                 MAX_FEE,
                 TEST_PROGRAM_ID
@@ -77,194 +78,272 @@ describe('transferFee', () => {
             createInitializeMintInstruction(mint, TEST_TOKEN_DECIMALS, mintAuthority.publicKey, null, TEST_PROGRAM_ID)
         );
         await sendAndConfirmTransaction(connection, mintTransaction, [payer, mintKeypair], undefined);
+    }
 
-        owner = Keypair.generate();
-        sourceAccount = await createAccount(
-            connection,
-            payer,
-            mint,
-            owner.publicKey,
-            undefined,
-            undefined,
-            TEST_PROGRAM_ID
-        );
-        await mintTo(
-            connection,
-            payer,
-            mint,
-            sourceAccount,
-            mintAuthority,
-            MINT_AMOUNT,
-            [],
-            undefined,
-            TEST_PROGRAM_ID
-        );
+    describe('with authorities set', () => {
+        let transferFeeConfigAuthority: Keypair;
+        let withdrawWithheldAuthority: Keypair;
+        beforeEach(async () => {
+            transferFeeConfigAuthority = Keypair.generate();
+            withdrawWithheldAuthority = Keypair.generate();
 
-        const accountKeypair = Keypair.generate();
-        destinationAccount = await createAccount(
-            connection,
-            payer,
-            mint,
-            owner.publicKey,
-            accountKeypair,
-            undefined,
-            TEST_PROGRAM_ID
-        );
+            await setupTransferFeeMint(transferFeeConfigAuthority.publicKey, withdrawWithheldAuthority.publicKey);
 
-        await transferChecked(
-            connection,
-            payer,
-            sourceAccount,
-            mint,
-            destinationAccount,
-            owner,
-            TRANSFER_AMOUNT,
-            TEST_TOKEN_DECIMALS,
-            [],
-            undefined,
-            TEST_PROGRAM_ID
-        );
-    });
-    it('initializes', async () => {
-        const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID);
-        const transferFeeConfig = getTransferFeeConfig(mintInfo);
-        expect(transferFeeConfig).to.not.be.null;
-        if (transferFeeConfig !== null) {
-            expect(transferFeeConfig.transferFeeConfigAuthority).to.eql(transferFeeConfigAuthority.publicKey);
-            expect(transferFeeConfig.withdrawWithheldAuthority).to.eql(withdrawWithheldAuthority.publicKey);
-            expect(transferFeeConfig.olderTransferFee.transferFeeBasisPoints).to.eql(FEE_BASIS_POINTS);
-            expect(transferFeeConfig.olderTransferFee.maximumFee).to.eql(MAX_FEE);
-            expect(transferFeeConfig.newerTransferFee.transferFeeBasisPoints).to.eql(FEE_BASIS_POINTS);
-            expect(transferFeeConfig.newerTransferFee.maximumFee).to.eql(MAX_FEE);
-            expect(transferFeeConfig.withheldAmount).to.eql(BigInt(0));
-        }
+            owner = Keypair.generate();
+            sourceAccount = await createAccount(
+                connection,
+                payer,
+                mint,
+                owner.publicKey,
+                undefined,
+                undefined,
+                TEST_PROGRAM_ID
+            );
+            await mintTo(
+                connection,
+                payer,
+                mint,
+                sourceAccount,
+                mintAuthority,
+                MINT_AMOUNT,
+                [],
+                undefined,
+                TEST_PROGRAM_ID
+            );
 
-        const accountInfo = await getAccount(connection, destinationAccount, undefined, TEST_PROGRAM_ID);
-        const transferFeeAmount = getTransferFeeAmount(accountInfo);
-        expect(transferFeeAmount).to.not.be.null;
-        if (transferFeeAmount !== null) {
-            expect(transferFeeAmount.withheldAmount).to.eql(FEE);
-        }
-    });
-    it('transferCheckedWithFee', async () => {
-        await transferCheckedWithFee(
-            connection,
-            payer,
-            sourceAccount,
-            mint,
-            destinationAccount,
-            owner,
-            TRANSFER_AMOUNT,
-            TEST_TOKEN_DECIMALS,
-            FEE,
-            [],
-            undefined,
-            TEST_PROGRAM_ID
-        );
-        const accountInfo = await getAccount(connection, destinationAccount, undefined, TEST_PROGRAM_ID);
-        const transferFeeAmount = getTransferFeeAmount(accountInfo);
-        expect(transferFeeAmount).to.not.be.null;
-        if (transferFeeAmount !== null) {
-            expect(transferFeeAmount.withheldAmount).to.eql(FEE * BigInt(2));
-        }
-    });
-    it('withdrawWithheldTokensFromAccounts', async () => {
-        await withdrawWithheldTokensFromAccounts(
-            connection,
-            payer,
-            mint,
-            destinationAccount,
-            withdrawWithheldAuthority,
-            [],
-            [destinationAccount],
-            undefined,
-            TEST_PROGRAM_ID
-        );
-        const accountInfo = await getAccount(connection, destinationAccount, undefined, TEST_PROGRAM_ID);
-        expect(accountInfo.amount).to.eql(TRANSFER_AMOUNT);
-        const transferFeeAmount = getTransferFeeAmount(accountInfo);
-        expect(transferFeeAmount).to.not.be.null;
-        if (transferFeeAmount !== null) {
-            expect(transferFeeAmount.withheldAmount).to.eql(BigInt(0));
-        }
-    });
-    it('harvestWithheldTokensToMint', async () => {
-        await harvestWithheldTokensToMint(connection, payer, mint, [destinationAccount], undefined, TEST_PROGRAM_ID);
-        const accountInfo = await getAccount(connection, destinationAccount, undefined, TEST_PROGRAM_ID);
-        const transferFeeAmount = getTransferFeeAmount(accountInfo);
-        expect(transferFeeAmount).to.not.be.null;
-        if (transferFeeAmount !== null) {
-            expect(transferFeeAmount.withheldAmount).to.eql(BigInt(0));
-        }
-        const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID);
-        const transferFeeConfig = getTransferFeeConfig(mintInfo);
-        expect(transferFeeConfig).to.not.be.null;
-        if (transferFeeConfig !== null) {
-            expect(transferFeeConfig.withheldAmount).to.eql(FEE);
-        }
-    });
-    it('withdrawWithheldTokensFromMint', async () => {
-        await harvestWithheldTokensToMint(connection, payer, mint, [destinationAccount], undefined, TEST_PROGRAM_ID);
-        await withdrawWithheldTokensFromMint(
-            connection,
-            payer,
-            mint,
-            destinationAccount,
-            withdrawWithheldAuthority,
-            [],
-            undefined,
-            TEST_PROGRAM_ID
-        );
-        const accountInfo = await getAccount(connection, destinationAccount, undefined, TEST_PROGRAM_ID);
-        expect(accountInfo.amount).to.eql(TRANSFER_AMOUNT);
-        const transferFeeAmount = getTransferFeeAmount(accountInfo);
-        expect(transferFeeAmount).to.not.be.null;
-        if (transferFeeAmount !== null) {
-            expect(transferFeeAmount.withheldAmount).to.eql(BigInt(0));
-        }
-        const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID);
-        const transferFeeConfig = getTransferFeeConfig(mintInfo);
-        expect(transferFeeConfig).to.not.be.null;
-        if (transferFeeConfig !== null) {
-            expect(transferFeeConfig.withheldAmount).to.eql(BigInt(0));
-        }
-    });
-    it('transferFeeConfigAuthority', async () => {
-        await setAuthority(
-            connection,
-            payer,
-            mint,
-            transferFeeConfigAuthority,
-            AuthorityType.TransferFeeConfig,
-            null,
-            [],
-            undefined,
-            TEST_PROGRAM_ID
-        );
-        const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID);
-        const transferFeeConfig = getTransferFeeConfig(mintInfo);
-        expect(transferFeeConfig).to.not.be.null;
-        if (transferFeeConfig !== null) {
-            expect(transferFeeConfig.transferFeeConfigAuthority).to.eql(PublicKey.default);
-        }
+            const accountKeypair = Keypair.generate();
+            destinationAccount = await createAccount(
+                connection,
+                payer,
+                mint,
+                owner.publicKey,
+                accountKeypair,
+                undefined,
+                TEST_PROGRAM_ID
+            );
+
+            await transferChecked(
+                connection,
+                payer,
+                sourceAccount,
+                mint,
+                destinationAccount,
+                owner,
+                TRANSFER_AMOUNT,
+                TEST_TOKEN_DECIMALS,
+                [],
+                undefined,
+                TEST_PROGRAM_ID
+            );
+        });
+        it('initializes', async () => {
+            const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID);
+            const transferFeeConfig = getTransferFeeConfig(mintInfo);
+            expect(transferFeeConfig).to.not.be.null;
+            if (transferFeeConfig !== null) {
+                expect(transferFeeConfig.transferFeeConfigAuthority).to.eql(transferFeeConfigAuthority.publicKey);
+                expect(transferFeeConfig.withdrawWithheldAuthority).to.eql(withdrawWithheldAuthority.publicKey);
+                expect(transferFeeConfig.olderTransferFee.transferFeeBasisPoints).to.eql(FEE_BASIS_POINTS);
+                expect(transferFeeConfig.olderTransferFee.maximumFee).to.eql(MAX_FEE);
+                expect(transferFeeConfig.newerTransferFee.transferFeeBasisPoints).to.eql(FEE_BASIS_POINTS);
+                expect(transferFeeConfig.newerTransferFee.maximumFee).to.eql(MAX_FEE);
+                expect(transferFeeConfig.withheldAmount).to.eql(BigInt(0));
+            }
+
+            const accountInfo = await getAccount(connection, destinationAccount, undefined, TEST_PROGRAM_ID);
+            const transferFeeAmount = getTransferFeeAmount(accountInfo);
+            expect(transferFeeAmount).to.not.be.null;
+            if (transferFeeAmount !== null) {
+                expect(transferFeeAmount.withheldAmount).to.eql(FEE);
+            }
+        });
+        it('transferCheckedWithFee', async () => {
+            await transferCheckedWithFee(
+                connection,
+                payer,
+                sourceAccount,
+                mint,
+                destinationAccount,
+                owner,
+                TRANSFER_AMOUNT,
+                TEST_TOKEN_DECIMALS,
+                FEE,
+                [],
+                undefined,
+                TEST_PROGRAM_ID
+            );
+            const accountInfo = await getAccount(connection, destinationAccount, undefined, TEST_PROGRAM_ID);
+            const transferFeeAmount = getTransferFeeAmount(accountInfo);
+            expect(transferFeeAmount).to.not.be.null;
+            if (transferFeeAmount !== null) {
+                expect(transferFeeAmount.withheldAmount).to.eql(FEE * BigInt(2));
+            }
+        });
+        it('withdrawWithheldTokensFromAccounts', async () => {
+            await withdrawWithheldTokensFromAccounts(
+                connection,
+                payer,
+                mint,
+                destinationAccount,
+                withdrawWithheldAuthority,
+                [],
+                [destinationAccount],
+                undefined,
+                TEST_PROGRAM_ID
+            );
+            const accountInfo = await getAccount(connection, destinationAccount, undefined, TEST_PROGRAM_ID);
+            expect(accountInfo.amount).to.eql(TRANSFER_AMOUNT);
+            const transferFeeAmount = getTransferFeeAmount(accountInfo);
+            expect(transferFeeAmount).to.not.be.null;
+            if (transferFeeAmount !== null) {
+                expect(transferFeeAmount.withheldAmount).to.eql(BigInt(0));
+            }
+        });
+        it('harvestWithheldTokensToMint', async () => {
+            await harvestWithheldTokensToMint(
+                connection,
+                payer,
+                mint,
+                [destinationAccount],
+                undefined,
+                TEST_PROGRAM_ID
+            );
+            const accountInfo = await getAccount(connection, destinationAccount, undefined, TEST_PROGRAM_ID);
+            const transferFeeAmount = getTransferFeeAmount(accountInfo);
+            expect(transferFeeAmount).to.not.be.null;
+            if (transferFeeAmount !== null) {
+                expect(transferFeeAmount.withheldAmount).to.eql(BigInt(0));
+            }
+            const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID);
+            const transferFeeConfig = getTransferFeeConfig(mintInfo);
+            expect(transferFeeConfig).to.not.be.null;
+            if (transferFeeConfig !== null) {
+                expect(transferFeeConfig.withheldAmount).to.eql(FEE);
+            }
+        });
+        it('withdrawWithheldTokensFromMint', async () => {
+            await harvestWithheldTokensToMint(
+                connection,
+                payer,
+                mint,
+                [destinationAccount],
+                undefined,
+                TEST_PROGRAM_ID
+            );
+            await withdrawWithheldTokensFromMint(
+                connection,
+                payer,
+                mint,
+                destinationAccount,
+                withdrawWithheldAuthority,
+                [],
+                undefined,
+                TEST_PROGRAM_ID
+            );
+            const accountInfo = await getAccount(connection, destinationAccount, undefined, TEST_PROGRAM_ID);
+            expect(accountInfo.amount).to.eql(TRANSFER_AMOUNT);
+            const transferFeeAmount = getTransferFeeAmount(accountInfo);
+            expect(transferFeeAmount).to.not.be.null;
+            if (transferFeeAmount !== null) {
+                expect(transferFeeAmount.withheldAmount).to.eql(BigInt(0));
+            }
+            const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID);
+            const transferFeeConfig = getTransferFeeConfig(mintInfo);
+            expect(transferFeeConfig).to.not.be.null;
+            if (transferFeeConfig !== null) {
+                expect(transferFeeConfig.withheldAmount).to.eql(BigInt(0));
+            }
+        });
+        it('transferFeeConfigAuthority', async () => {
+            await setAuthority(
+                connection,
+                payer,
+                mint,
+                transferFeeConfigAuthority,
+                AuthorityType.TransferFeeConfig,
+                null,
+                [],
+                undefined,
+                TEST_PROGRAM_ID
+            );
+            const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID);
+            const transferFeeConfig = getTransferFeeConfig(mintInfo);
+            expect(transferFeeConfig).to.not.be.null;
+            if (transferFeeConfig !== null) {
+                expect(transferFeeConfig.transferFeeConfigAuthority).to.eql(PublicKey.default);
+            }
+        });
+        it('withdrawWithheldAuthority', async () => {
+            await setAuthority(
+                connection,
+                payer,
+                mint,
+                withdrawWithheldAuthority,
+                AuthorityType.WithheldWithdraw,
+                null,
+                [],
+                undefined,
+                TEST_PROGRAM_ID
+            );
+            const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID);
+            const transferFeeConfig = getTransferFeeConfig(mintInfo);
+            expect(transferFeeConfig).to.not.be.null;
+            if (transferFeeConfig !== null) {
+                expect(transferFeeConfig.withdrawWithheldAuthority).to.eql(PublicKey.default);
+            }
+        });
     });
-    it('withdrawWithheldAuthority', async () => {
-        await setAuthority(
-            connection,
-            payer,
-            mint,
-            withdrawWithheldAuthority,
-            AuthorityType.WithheldWithdraw,
-            null,
-            [],
-            undefined,
-            TEST_PROGRAM_ID
-        );
-        const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID);
-        const transferFeeConfig = getTransferFeeConfig(mintInfo);
-        expect(transferFeeConfig).to.not.be.null;
-        if (transferFeeConfig !== null) {
-            expect(transferFeeConfig.withdrawWithheldAuthority).to.eql(PublicKey.default);
-        }
+
+    describe('with null authorities', () => {
+        it('initializes with null transfer fee config authority', async () => {
+            const withdrawWithheldAuthority = Keypair.generate();
+            await setupTransferFeeMint(null, withdrawWithheldAuthority.publicKey);
+
+            const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID);
+            const transferFeeConfig = getTransferFeeConfig(mintInfo);
+            expect(transferFeeConfig).to.not.be.null;
+            if (transferFeeConfig !== null) {
+                expect(transferFeeConfig.transferFeeConfigAuthority).to.eql(PublicKey.default);
+                expect(transferFeeConfig.withdrawWithheldAuthority).to.eql(withdrawWithheldAuthority.publicKey);
+                expect(transferFeeConfig.olderTransferFee.transferFeeBasisPoints).to.eql(FEE_BASIS_POINTS);
+                expect(transferFeeConfig.olderTransferFee.maximumFee).to.eql(MAX_FEE);
+                expect(transferFeeConfig.newerTransferFee.transferFeeBasisPoints).to.eql(FEE_BASIS_POINTS);
+                expect(transferFeeConfig.newerTransferFee.maximumFee).to.eql(MAX_FEE);
+                expect(transferFeeConfig.withheldAmount).to.eql(BigInt(0));
+            }
+        });
+        it('initializes with null withdraw withheld authority', async () => {
+            const transferFeeConfigAuthority = Keypair.generate();
+            await setupTransferFeeMint(transferFeeConfigAuthority.publicKey, null);
+
+            const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID);
+            const transferFeeConfig = getTransferFeeConfig(mintInfo);
+            expect(transferFeeConfig).to.not.be.null;
+            if (transferFeeConfig !== null) {
+                expect(transferFeeConfig.transferFeeConfigAuthority).to.eql(transferFeeConfigAuthority.publicKey);
+                expect(transferFeeConfig.withdrawWithheldAuthority).to.eql(PublicKey.default);
+                expect(transferFeeConfig.olderTransferFee.transferFeeBasisPoints).to.eql(FEE_BASIS_POINTS);
+                expect(transferFeeConfig.olderTransferFee.maximumFee).to.eql(MAX_FEE);
+                expect(transferFeeConfig.newerTransferFee.transferFeeBasisPoints).to.eql(FEE_BASIS_POINTS);
+                expect(transferFeeConfig.newerTransferFee.maximumFee).to.eql(MAX_FEE);
+                expect(transferFeeConfig.withheldAmount).to.eql(BigInt(0));
+            }
+        });
+        it('initializes with both authorities null', async () => {
+            await setupTransferFeeMint(null, null);
+
+            const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID);
+            const transferFeeConfig = getTransferFeeConfig(mintInfo);
+            expect(transferFeeConfig).to.not.be.null;
+            if (transferFeeConfig !== null) {
+                expect(transferFeeConfig.transferFeeConfigAuthority).to.eql(PublicKey.default);
+                expect(transferFeeConfig.withdrawWithheldAuthority).to.eql(PublicKey.default);
+                expect(transferFeeConfig.olderTransferFee.transferFeeBasisPoints).to.eql(FEE_BASIS_POINTS);
+                expect(transferFeeConfig.olderTransferFee.maximumFee).to.eql(MAX_FEE);
+                expect(transferFeeConfig.newerTransferFee.transferFeeBasisPoints).to.eql(FEE_BASIS_POINTS);
+                expect(transferFeeConfig.newerTransferFee.maximumFee).to.eql(MAX_FEE);
+                expect(transferFeeConfig.withheldAmount).to.eql(BigInt(0));
+            }
+        });
     });
 });
diff --git a/token/js/test/unit/transferfee.test.ts b/token/js/test/unit/transferfee.test.ts
index f329de327..bfde797d7 100644
--- a/token/js/test/unit/transferfee.test.ts
+++ b/token/js/test/unit/transferfee.test.ts
@@ -1,98 +1,167 @@
-import { TransferFee, TransferFeeConfig, calculateFee, calculateEpochFee, ONE_IN_BASIS_POINTS } from '../../src';
+import {
+    calculateFee,
+    calculateEpochFee,
+    ONE_IN_BASIS_POINTS,
+    createInitializeTransferFeeConfigInstruction,
+    decodeInitializeTransferFeeConfigInstructionUnchecked,
+} from '../../src';
 import { expect } from 'chai';
-import { PublicKey } from '@solana/web3.js';
+import { Keypair, PublicKey } from '@solana/web3.js';
 
-describe('calculateFee', () => {
-    it('should return 0 fee when transferFeeBasisPoints is 0', () => {
-        const transferFee = {
-            epoch: 1n,
-            maximumFee: 100n,
-            transferFeeBasisPoints: 0,
-        };
-        const preFeeAmount = 100n;
-        const fee = calculateFee(transferFee, preFeeAmount);
-        expect(fee).to.eql(0n);
+describe('transferFee', () => {
+    describe('encoding/decoding `InitializeTransferFeeConfig` instructions', () => {
+        it('should encode and decode with both authorities', () => {
+            const mint = Keypair.generate().publicKey;
+            const transferFeeConfigAuthority = Keypair.generate().publicKey;
+            const withdrawWithheldAuthority = Keypair.generate().publicKey;
+            const instruction = createInitializeTransferFeeConfigInstruction(
+                mint,
+                transferFeeConfigAuthority,
+                withdrawWithheldAuthority,
+                100,
+                100n
+            );
+            const decoded = decodeInitializeTransferFeeConfigInstructionUnchecked(instruction);
+            expect(decoded.data.transferFeeConfigAuthority).to.eql(transferFeeConfigAuthority);
+            expect(decoded.data.withdrawWithheldAuthority).to.eql(withdrawWithheldAuthority);
+            expect(decoded.data.transferFeeBasisPoints).to.eql(100);
+            expect(decoded.data.maximumFee).to.eql(100n);
+        });
+        it('should encode and decode with no transfer fee config authority', () => {
+            const mint = Keypair.generate().publicKey;
+            const withdrawWithheldAuthority = Keypair.generate().publicKey;
+            const instruction = createInitializeTransferFeeConfigInstruction(
+                mint,
+                null,
+                withdrawWithheldAuthority,
+                100,
+                100n
+            );
+            const decoded = decodeInitializeTransferFeeConfigInstructionUnchecked(instruction);
+            expect(decoded.data.transferFeeConfigAuthority).to.eql(null);
+            expect(decoded.data.withdrawWithheldAuthority).to.eql(withdrawWithheldAuthority);
+            expect(decoded.data.transferFeeBasisPoints).to.eql(100);
+            expect(decoded.data.maximumFee).to.eql(100n);
+        });
+        it('should encode and decode with no withdraw withheld authority', () => {
+            const mint = Keypair.generate().publicKey;
+            const transferFeeConfigAuthority = Keypair.generate().publicKey;
+            const instruction = createInitializeTransferFeeConfigInstruction(
+                mint,
+                transferFeeConfigAuthority,
+                null,
+                100,
+                100n
+            );
+            const decoded = decodeInitializeTransferFeeConfigInstructionUnchecked(instruction);
+            expect(decoded.data.transferFeeConfigAuthority).to.eql(transferFeeConfigAuthority);
+            expect(decoded.data.withdrawWithheldAuthority).to.eql(null);
+            expect(decoded.data.transferFeeBasisPoints).to.eql(100);
+            expect(decoded.data.maximumFee).to.eql(100n);
+        });
+        it('should encode and decode with no authorities', () => {
+            const mint = Keypair.generate().publicKey;
+            const instruction = createInitializeTransferFeeConfigInstruction(mint, null, null, 100, 100n);
+            const decoded = decodeInitializeTransferFeeConfigInstructionUnchecked(instruction);
+            expect(decoded.data.transferFeeConfigAuthority).to.eql(null);
+            expect(decoded.data.withdrawWithheldAuthority).to.eql(null);
+            expect(decoded.data.transferFeeBasisPoints).to.eql(100);
+            expect(decoded.data.maximumFee).to.eql(100n);
+        });
     });
 
-    it('should return 0 fee when preFeeAmount is 0', () => {
-        const transferFee = {
-            epoch: 1n,
-            maximumFee: 100n,
-            transferFeeBasisPoints: 100,
-        };
-        const preFeeAmount = 0n;
-        const fee = calculateFee(transferFee, preFeeAmount);
-        expect(fee).to.eql(0n);
-    });
+    describe('calculateFee', () => {
+        it('should return 0 fee when transferFeeBasisPoints is 0', () => {
+            const transferFee = {
+                epoch: 1n,
+                maximumFee: 100n,
+                transferFeeBasisPoints: 0,
+            };
+            const preFeeAmount = 100n;
+            const fee = calculateFee(transferFee, preFeeAmount);
+            expect(fee).to.eql(0n);
+        });
 
-    it('should calculate the fee correctly when preFeeAmount is non-zero', () => {
-        const transferFee = {
-            epoch: 1n,
-            maximumFee: 100n,
-            transferFeeBasisPoints: 50,
-        };
-        const preFeeAmount = 500n;
-        const fee = calculateFee(transferFee, preFeeAmount);
-        expect(fee).to.eql(3n);
-    });
+        it('should return 0 fee when preFeeAmount is 0', () => {
+            const transferFee = {
+                epoch: 1n,
+                maximumFee: 100n,
+                transferFeeBasisPoints: 100,
+            };
+            const preFeeAmount = 0n;
+            const fee = calculateFee(transferFee, preFeeAmount);
+            expect(fee).to.eql(0n);
+        });
 
-    it('fee should be equal to maximum fee', () => {
-        const transferFee = {
-            epoch: 1n,
-            maximumFee: 5000n,
-            transferFeeBasisPoints: 50,
-        };
-        const preFeeAmount = transferFee.maximumFee;
-        const fee = calculateFee(transferFee, preFeeAmount * ONE_IN_BASIS_POINTS);
-        expect(fee).to.eql(transferFee.maximumFee);
-    });
-    it('fee should be equal to maximum fee when added 1 to preFeeAmount', () => {
-        const transferFee = {
-            epoch: 1n,
-            maximumFee: 5000n,
-            transferFeeBasisPoints: 50,
-        };
-        const preFeeAmount = transferFee.maximumFee;
-        const fee = calculateFee(transferFee, preFeeAmount * ONE_IN_BASIS_POINTS + 1n);
-        expect(fee).to.eql(transferFee.maximumFee);
+        it('should calculate the fee correctly when preFeeAmount is non-zero', () => {
+            const transferFee = {
+                epoch: 1n,
+                maximumFee: 100n,
+                transferFeeBasisPoints: 50,
+            };
+            const preFeeAmount = 500n;
+            const fee = calculateFee(transferFee, preFeeAmount);
+            expect(fee).to.eql(3n);
+        });
+
+        it('fee should be equal to maximum fee', () => {
+            const transferFee = {
+                epoch: 1n,
+                maximumFee: 5000n,
+                transferFeeBasisPoints: 50,
+            };
+            const preFeeAmount = transferFee.maximumFee;
+            const fee = calculateFee(transferFee, preFeeAmount * ONE_IN_BASIS_POINTS);
+            expect(fee).to.eql(transferFee.maximumFee);
+        });
+        it('fee should be equal to maximum fee when added 1 to preFeeAmount', () => {
+            const transferFee = {
+                epoch: 1n,
+                maximumFee: 5000n,
+                transferFeeBasisPoints: 50,
+            };
+            const preFeeAmount = transferFee.maximumFee;
+            const fee = calculateFee(transferFee, preFeeAmount * ONE_IN_BASIS_POINTS + 1n);
+            expect(fee).to.eql(transferFee.maximumFee);
+        });
     });
-});
 
-describe('calculateEpochFee', () => {
-    const transferFeeConfig = {
-        transferFeeConfigAuthority: PublicKey.default,
-        withdrawWithheldAuthority: PublicKey.default,
-        withheldAmount: 500n,
-        olderTransferFee: {
-            epoch: 1n,
-            maximumFee: 100n,
-            transferFeeBasisPoints: 50,
-        },
-        newerTransferFee: {
-            epoch: 2n,
-            maximumFee: 200n,
-            transferFeeBasisPoints: 75,
-        },
-    };
+    describe('calculateEpochFee', () => {
+        const transferFeeConfig = {
+            transferFeeConfigAuthority: PublicKey.default,
+            withdrawWithheldAuthority: PublicKey.default,
+            withheldAmount: 500n,
+            olderTransferFee: {
+                epoch: 1n,
+                maximumFee: 100n,
+                transferFeeBasisPoints: 50,
+            },
+            newerTransferFee: {
+                epoch: 2n,
+                maximumFee: 200n,
+                transferFeeBasisPoints: 75,
+            },
+        };
 
-    it('should return olderTransferFee when epoch is less than newerTransferFee.epoch', () => {
-        const preFeeAmount = 200n;
-        const epoch = 1n;
-        const fee = calculateEpochFee(transferFeeConfig, epoch, preFeeAmount);
-        expect(fee).to.eql(1n);
-    });
+        it('should return olderTransferFee when epoch is less than newerTransferFee.epoch', () => {
+            const preFeeAmount = 200n;
+            const epoch = 1n;
+            const fee = calculateEpochFee(transferFeeConfig, epoch, preFeeAmount);
+            expect(fee).to.eql(1n);
+        });
 
-    it('should return newerTransferFee when epoch is greater than or equal to newerTransferFee.epoch', () => {
-        const preFeeAmount = 200n;
-        const epoch = 2n;
-        const fee = calculateEpochFee(transferFeeConfig, epoch, preFeeAmount);
-        expect(fee).to.eql(2n);
-    });
+        it('should return newerTransferFee when epoch is greater than or equal to newerTransferFee.epoch', () => {
+            const preFeeAmount = 200n;
+            const epoch = 2n;
+            const fee = calculateEpochFee(transferFeeConfig, epoch, preFeeAmount);
+            expect(fee).to.eql(2n);
+        });
 
-    it('should cap the fee to the maximumFee when calculated fee exceeds maximumFee', () => {
-        const preFeeAmount = 500n;
-        const epoch = 2n;
-        const fee = calculateEpochFee(transferFeeConfig, epoch, preFeeAmount);
-        expect(fee).to.eql(4n);
+        it('should cap the fee to the maximumFee when calculated fee exceeds maximumFee', () => {
+            const preFeeAmount = 500n;
+            const epoch = 2n;
+            const fee = calculateEpochFee(transferFeeConfig, epoch, preFeeAmount);
+            expect(fee).to.eql(4n);
+        });
     });
 });
diff --git a/token/program-2022-test/Cargo.toml b/token/program-2022-test/Cargo.toml
index abc5dd6a3..9c6adb787 100644
--- a/token/program-2022-test/Cargo.toml
+++ b/token/program-2022-test/Cargo.toml
@@ -31,6 +31,6 @@ spl-tlv-account-resolution = { version = "0.5.0", path = "../../libraries/tlv-ac
 spl-token-client = { version = "0.8", path = "../client" }
 spl-token-group-interface = { version = "0.1", path = "../../token-group/interface" }
 spl-token-metadata-interface = { version = "0.2", path = "../../token-metadata/interface" }
-spl-transfer-hook-example = { version = "0.4", path="../transfer-hook/example", features = ["no-entrypoint"] }
-spl-transfer-hook-interface = { version = "0.4", path="../transfer-hook/interface" }
+spl-transfer-hook-example = { version = "0.5", path="../transfer-hook/example", features = ["no-entrypoint"] }
+spl-transfer-hook-interface = { version = "0.5", path="../transfer-hook/interface" }
 test-case = "3.3"