From 64657df0f0f0b96f5f0d2beb8b1c22c146f49903 Mon Sep 17 00:00:00 2001 From: Loris Leiva Date: Fri, 5 Jun 2026 12:37:38 +0100 Subject: [PATCH] Migrate JS tests to a Kit client and LiteSVM --- Makefile | 12 +- clients/js/README.md | 11 +- clients/js/package.json | 2 + clients/js/pnpm-lock.yaml | 391 ++++++++++++------ clients/js/test/_setup.ts | 254 ++---------- clients/js/test/batch.test.ts | 25 +- clients/js/test/burnChecked.test.ts | 242 ++++------- clients/js/test/createAssociatedToken.test.ts | 50 +-- .../createAssociatedTokenIdempotent.test.ts | 50 +-- clients/js/test/createMint.test.ts | 71 +--- clients/js/test/initializeAccount.test.ts | 48 +-- clients/js/test/initializeMint.test.ts | 50 +-- clients/js/test/mintTo.test.ts | 46 +-- clients/js/test/mintToATA.test.ts | 181 +++----- clients/js/test/plugin.test.ts | 96 ----- clients/js/test/transfer.test.ts | 44 +- clients/js/test/transferToATA.test.ts | 187 ++++----- scripts/restart-test-validator.sh | 44 -- 18 files changed, 653 insertions(+), 1151 deletions(-) delete mode 100644 clients/js/test/plugin.test.ts delete mode 100755 scripts/restart-test-validator.sh diff --git a/Makefile b/Makefile index a4c53ba8..50ee4971 100644 --- a/Makefile +++ b/Makefile @@ -76,9 +76,6 @@ build-doc-%: test-doc-%: cargo $(nightly) test --doc --all-features --manifest-path $(call make-path,$*)/Cargo.toml $(ARGS) -test-%: - SBF_OUT_DIR=$(PWD)/target/deploy cargo $(nightly) test --manifest-path $(call make-path,$*)/Cargo.toml $(ARGS) - format-check-js-%: cd $(call make-path,$*) && pnpm install && pnpm format $(ARGS) @@ -86,15 +83,10 @@ lint-js-%: cd $(call make-path,$*) && pnpm install && pnpm lint $(ARGS) test-js-%: - make restart-test-validator cd $(call make-path,$*) && pnpm install && pnpm build && pnpm test $(ARGS) - make stop-test-validator -restart-test-validator: - ./scripts/restart-test-validator.sh - -stop-test-validator: - pkill -f solana-test-validator +test-%: + SBF_OUT_DIR=$(PWD)/target/deploy cargo $(nightly) test --manifest-path $(call make-path,$*)/Cargo.toml $(ARGS) generate-fixtures: mkdir -p ./target/fixtures && RUST_LOG=error EJECT_FUZZ_FIXTURES=../target/fixtures cargo test-sbf --features mollusk-svm/fuzz --manifest-path program/Cargo.toml diff --git a/clients/js/README.md b/clients/js/README.md index 2230de37..40cc219e 100644 --- a/clients/js/README.md +++ b/clients/js/README.md @@ -4,22 +4,21 @@ A generated JavaScript library for the Token program. ## Getting started -To build and test your JavaScript client from the root of the repository, you may use the following command. +The JS client tests use [LiteSVM](https://github.com/LiteSVM/litesvm) in-process, so no local validator is needed. To build and test your JavaScript client from the root of the repository, you may use the following command. ```sh -pnpm clients:js:test +make test-js-clients-js ``` -This will start a new local validator, if one is not already running, and run the tests for your JavaScript client. +This installs dependencies, builds the client, and runs the test suite. ## Available client scripts. Alternatively, you can go into the client directory and run the tests directly. ```sh -# Build your programs and start the validator. -pnpm programs:build -pnpm validator:restart +# Build the program `.so` that LiteSVM loads. +make build-sbf-pinocchio-program # Go into the client directory and run the tests. cd clients/js diff --git a/clients/js/package.json b/clients/js/package.json index d413f418..9521f1f6 100644 --- a/clients/js/package.json +++ b/clients/js/package.json @@ -53,6 +53,8 @@ "@solana/eslint-config-solana": "^3.0.3", "@solana/kit": "^6.5.0", "@solana/kit-client-rpc": "^0.9.0", + "@solana/kit-plugin-litesvm": "^0.10.0", + "@solana/kit-plugin-signer": "^0.10.0", "@types/node": "^24", "@typescript-eslint/eslint-plugin": "^7.16.1", "@typescript-eslint/parser": "^7.16.1", diff --git a/clients/js/pnpm-lock.yaml b/clients/js/pnpm-lock.yaml index 7047b8e0..1c35cb38 100644 --- a/clients/js/pnpm-lock.yaml +++ b/clients/js/pnpm-lock.yaml @@ -21,9 +21,15 @@ importers: '@solana/kit-client-rpc': specifier: ^0.9.0 version: 0.9.0(@solana/kit@6.9.0(typescript@5.9.3)) + '@solana/kit-plugin-litesvm': + specifier: ^0.10.0 + version: 0.10.0(@solana/kit@6.9.0(typescript@5.9.3))(typescript@5.9.3) + '@solana/kit-plugin-signer': + specifier: ^0.10.0 + version: 0.10.0(@solana/kit@6.9.0(typescript@5.9.3)) '@types/node': specifier: ^24 - version: 24.12.4 + version: 24.13.0 '@typescript-eslint/eslint-plugin': specifier: ^7.16.1 version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) @@ -50,7 +56,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.0.15 - version: 4.1.8(@types/node@24.12.4)(vite@8.0.16(@types/node@24.12.4)(esbuild@0.27.7)) + version: 4.1.8(@types/node@24.13.0)(vite@8.0.16(@types/node@24.13.0)(esbuild@0.27.7)) packages: @@ -384,128 +390,128 @@ packages: '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - '@rollup/rollup-android-arm-eabi@4.61.0': - resolution: {integrity: sha512-dnxczajOqt0gesZlN5pGQ1s1imQVrsmCw5G2Ci4oM+0WvNz3pyRnlWrT7McoZIb8VlFwCawdmbWRmxRn7HI+VQ==} + '@rollup/rollup-android-arm-eabi@4.61.1': + resolution: {integrity: sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.61.0': - resolution: {integrity: sha512-Bp3JpGP00Vu3f238ivRrjf7z3xSzVPXqCmaJYA9t2c+c8vKYvOzmXF7LkkeUalTEGd6cZcSWe+PFIP3Vy48fRg==} + '@rollup/rollup-android-arm64@4.61.1': + resolution: {integrity: sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.61.0': - resolution: {integrity: sha512-zaYIpr670mUmmZ1tVzUFplbQbG7h3Gugx3L5FoqhsC2m/YnLlR1a7zVLmXNPy+iY1tFPEbNG+HHBXZGyId0G5w==} + '@rollup/rollup-darwin-arm64@4.61.1': + resolution: {integrity: sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.61.0': - resolution: {integrity: sha512-+P49fvkv2dSoeevUW+lgZ/I2JHSsJCK1Lyjj7Cu6E4UHG4tS9XIefzIjo5qhgELjAclnen1rLzK2PMKJdo+Dyg==} + '@rollup/rollup-darwin-x64@4.61.1': + resolution: {integrity: sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.61.0': - resolution: {integrity: sha512-l3FAAOyKJXH2ea6KNFN+MMgC/rnE94YGLXs2ehYqDcCoHt1DpvgWX75BhUJxN38XojP7Ul+4H8PRn7EdyqSDrw==} + '@rollup/rollup-freebsd-arm64@4.61.1': + resolution: {integrity: sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.61.0': - resolution: {integrity: sha512-VokPN3TSctKj65cyCNPaUh4vMFA8awxOot/0sp+4J7ZlNRKQEhXhawqPwajoi8H5ZFt61i0ugZJuTKXBjGJ17Q==} + '@rollup/rollup-freebsd-x64@4.61.1': + resolution: {integrity: sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.61.0': - resolution: {integrity: sha512-DxH0P3wxm+Yzs/p3zrk9dw1rURu8p0Nv5+MRK/L7OtnLNg5rLZraSBFZ8iUXOd9f2BlhJyEpIZUH/emjq4UJ4g==} + '@rollup/rollup-linux-arm-gnueabihf@4.61.1': + resolution: {integrity: sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.61.0': - resolution: {integrity: sha512-T6ZvMNe84kAz6TBWHC7hGAoEtzP1LWYw/AqayGWEF6uISt3Abk/st06LqRD9THd7Xz3NxzurUpzAuEAUbZf+nw==} + '@rollup/rollup-linux-arm-musleabihf@4.61.1': + resolution: {integrity: sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.61.0': - resolution: {integrity: sha512-q/4hzvQkDs8b4jIBab1pnLiiM0ayTZsN2amBFPDzuyZxjEd4wDwx0UJFYM3cOZzSf5Kw8fnWSprJzIBMkcR44Q==} + '@rollup/rollup-linux-arm64-gnu@4.61.1': + resolution: {integrity: sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.61.0': - resolution: {integrity: sha512-vvYWX3akdEAY6km+9wAqFDnk6pQsbJKVnj7xawcvs/+fdlYBGp+U+Qq/lLfpIxYIZvZLHMAKD9HLdacSx/r3dw==} + '@rollup/rollup-linux-arm64-musl@4.61.1': + resolution: {integrity: sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.61.0': - resolution: {integrity: sha512-DePa5cqOxDP/Zp0VOXpeWaGew5iIv5DXp9NYbzkX5PFQyWVX9184WCTh3hvr/7lhXo8ZVlbFLkz8+o/q1dU6gA==} + '@rollup/rollup-linux-loong64-gnu@4.61.1': + resolution: {integrity: sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-loong64-musl@4.61.0': - resolution: {integrity: sha512-LV8aWMB8UChglMCEzs7RkN0GsH29RJaLLqwm9fCIjlqwxQTiWAqNcc7wjBkH31hV0PU/yVxGYvrYsgfea2qw6g==} + '@rollup/rollup-linux-loong64-musl@4.61.1': + resolution: {integrity: sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.61.0': - resolution: {integrity: sha512-QoNSnwQtaeNu5grdBbsL0tt1uyl5EnS8DA8Mr3nluMXbhdQNyhN+G4tBax7VCdxLKj8YJ0/4OO9Ho84jMnJtKA==} + '@rollup/rollup-linux-ppc64-gnu@4.61.1': + resolution: {integrity: sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-ppc64-musl@4.61.0': - resolution: {integrity: sha512-/zZp5MKapIIApE8trN8qLGNSiRN9TUoaUZ1cmVu4XnVdd5LQLOXTtyi+vtfUbNnT3iyjzpPqYeKXmvJ+gJGYWw==} + '@rollup/rollup-linux-ppc64-musl@4.61.1': + resolution: {integrity: sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.61.0': - resolution: {integrity: sha512-RbrzcD3aJ1k3UbtMRRBNwojdVVyXjuVAFTfn/xPa6EEl6GE9Sm/akPgFTb9aAC9pMKGJ6CtWxaGrqWcabH+ySg==} + '@rollup/rollup-linux-riscv64-gnu@4.61.1': + resolution: {integrity: sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.61.0': - resolution: {integrity: sha512-ZF+onDsBso8PJf1XaG9lB+O9RnBpKGnY6OrzC4CSHrtC1jb6jWLTKK4bRqdoCXHd22gyr2hiYmEAm8Wns/BOCw==} + '@rollup/rollup-linux-riscv64-musl@4.61.1': + resolution: {integrity: sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.61.0': - resolution: {integrity: sha512-Atk0aSIk5Zx2Wuh9dgRQgLP0Koc8hOeYpbWryMXyk8G8/HmPkwPPkMqIIDhrXHHYqfUzSJA/I7IWSBv8xSmRBA==} + '@rollup/rollup-linux-s390x-gnu@4.61.1': + resolution: {integrity: sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.61.0': - resolution: {integrity: sha512-0uMOcf3eZ5K+K4cYHkdxShFMPlPXCOdfDFEFn9dNYAEEd2cVvmOfH7zFgRVoDgmtQ1m9k5q7qfrHzyMAubKYUA==} + '@rollup/rollup-linux-x64-gnu@4.61.1': + resolution: {integrity: sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.61.0': - resolution: {integrity: sha512-mvFtE4A/t/7hRJ7X8Ozmu8FsIkAUat2nzl12pgU337BRmq87AQUJztwHz2Zv5/tjo9/C95E66CK03SI/ToEDJw==} + '@rollup/rollup-linux-x64-musl@4.61.1': + resolution: {integrity: sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==} cpu: [x64] os: [linux] - '@rollup/rollup-openbsd-x64@4.61.0': - resolution: {integrity: sha512-z9b9+aTxvt8n2rNltMPvyaUfB8NJ+CVyOrGK/MdIKHx7B+lXmZpm/XbRsU7Rpf3fRqJ2uS6mBJiJveCtq8LHDg==} + '@rollup/rollup-openbsd-x64@4.61.1': + resolution: {integrity: sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.61.0': - resolution: {integrity: sha512-jXaXFqKMehsOc+g8R6oo33RRC6w07G9jDBxAE5eAKX7mOcCbZloYIPNhfG9Wl+P9O9IWHFO4OJgPi1Ml2qkt7w==} + '@rollup/rollup-openharmony-arm64@4.61.1': + resolution: {integrity: sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.61.0': - resolution: {integrity: sha512-OXNWVFocS2IA4+QplhTZZ2a+8hPZR7T8KuozsNmJKK8y7cp83StHvGksfHzPG3wczWTczyWHVQuqeiTUbjiyBg==} + '@rollup/rollup-win32-arm64-msvc@4.61.1': + resolution: {integrity: sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.61.0': - resolution: {integrity: sha512-AlAbNtBO637LxSldqV43z0FfXoGfl2TW1DgAg/bs7aQswFbDewz2SJm3BUhiGfbOVtW571xbc9p+REdxhyN/Eg==} + '@rollup/rollup-win32-ia32-msvc@4.61.1': + resolution: {integrity: sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.61.0': - resolution: {integrity: sha512-QRSrQXyJ1M4tjNXdR0/G/IgV6lzfQQJYBjlWIEYkY2Xs86DRl/iEpQ4blMDjJxSl7n19eDKKXMg0AmuBVYy8pQ==} + '@rollup/rollup-win32-x64-gnu@4.61.1': + resolution: {integrity: sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.61.0': - resolution: {integrity: sha512-tkuFxhvKO/HlGd0VsINF6vHSYH8AF8W0TcNxKDK6JZmrehngFj78pToc8iemtnvwilDjs2G/qSzYFhe9U8q+fw==} + '@rollup/rollup-win32-x64-msvc@4.61.1': + resolution: {integrity: sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==} cpu: [x64] os: [win32] @@ -514,6 +520,11 @@ packages: peerDependencies: '@solana/kit': ^6.4.0 + '@solana-program/token@0.13.0': + resolution: {integrity: sha512-/Apjrd5lwOJGrPB0J5Rv7EBeclvyEBQPAGA85Scm7wBH+GpkbdLDM9uK3TNg8jjFKyWQYai/JtPHbrx7VgFLSg==} + peerDependencies: + '@solana/kit': ^6.5.0 + '@solana/accounts@6.9.0': resolution: {integrity: sha512-g36AJreJrgf9AAjOfbdFHEFUTymBgzbWHoEDElZ+fDKvqBINDiUVKzDApwc7C7kGPMFqQBaoEHnQRxf2IqfKZQ==} engines: {node: '>=20.18.0'} @@ -671,11 +682,21 @@ packages: peerDependencies: '@solana/kit': ^6.5.0 + '@solana/kit-plugin-instruction-plan@0.10.0': + resolution: {integrity: sha512-h99B+1Vp5QWU/M/q0UXlTxKJt781vWw5aAopnbgVCy0F3hNaSDZ/gio126Oz0/cipGOnyaJf3NnV79GvxaEqZA==} + peerDependencies: + '@solana/kit': ^6.8.0 + '@solana/kit-plugin-instruction-plan@0.9.0': resolution: {integrity: sha512-IdqvhitisHRmo1wzo04Uwy87dZAMi0zQ7Kuj0Nib089N/FUJbGviZlx7XIyodRsEK2HWcnJGVvf/rm5/Pa/KMg==} peerDependencies: '@solana/kit': ^6.5.0 + '@solana/kit-plugin-litesvm@0.10.0': + resolution: {integrity: sha512-ks/sbFyYJOpa4BrdzEpp2karA66N4b1DFnWol3bui/UzJm+YJZ65xnOyeX9U8PC8uF/TPVkgf6oYvsxyLqrxIw==} + peerDependencies: + '@solana/kit': ^6.8.0 + '@solana/kit-plugin-payer@0.9.0': resolution: {integrity: sha512-TLaz2QDVmhcDse8rHMDP8FnCmVzH6hC0f3SAWSBBqrpLzjP/ZwJ8wpRWEXob5Il/LJK1eDajoRwCVjRg1p7q6Q==} peerDependencies: @@ -686,6 +707,11 @@ packages: peerDependencies: '@solana/kit': ^6.5.0 + '@solana/kit-plugin-signer@0.10.0': + resolution: {integrity: sha512-3Gw3R6nQg/2r2+4cSaUZHj5zdbtb0SuA1mkDMd6uH7B+fdH1Ouxnulv1/sN8J0VxBo7tS+YDQA5t0arxX5gj+w==} + peerDependencies: + '@solana/kit': ^6.8.0 + '@solana/kit@6.9.0': resolution: {integrity: sha512-k7BRz7Akfv8wiRtlCR/xUyDLfuMfYMelMR1+AC5KgwaRRJReDF0BucMLNN1In7WoI+KuWwr1OKv4na/oKpyeAQ==} engines: {node: '>=20.18.0'} @@ -947,8 +973,8 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@24.12.4': - resolution: {integrity: sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==} + '@types/node@24.13.0': + resolution: {integrity: sha512-5vtOqGQr4NJKeEzV441FcOi2MeG9UTWq9LqVLGneDdu4vlX17H8kQ2PA2UmNwCUGPVDj4oBjNhS7ReVEIWJJrg==} '@types/semver@7.7.1': resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} @@ -1583,6 +1609,46 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + litesvm-darwin-arm64@1.1.0: + resolution: {integrity: sha512-SjcivEOOjBk65U6TgIeMJ7CCnHNKQXHx0qf6K6GIFZC1aHTg7ePrEi+WhAQD6VUBMdDHIMCVKC/uXnXPi6EKIw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + litesvm-darwin-x64@1.1.0: + resolution: {integrity: sha512-hTs+eZ9sHVZXhjggpnn/8A/E+Nt/E6Gf8E2ejdWWL9bBQKmq1Y0VcrDpORbIvqqRpTLHXqbxCuH1wQB2C8frJg==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + litesvm-linux-arm64-gnu@1.1.0: + resolution: {integrity: sha512-6EjJ6+E+1SUXdJmCyeyhvlKhNncccqQNH241+P8d4E72rE3zuFxeCtLHhusCQk2p/Xau3dBI0qTLogZ1F1IGSA==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + + litesvm-linux-arm64-musl@1.1.0: + resolution: {integrity: sha512-mNuBOfX6GnDFT2i/kYPWud7eZGe57dDP0u4lwiSTQPRE0BxQbGZT2aEwX8LTwbonhbc6HSt50LamaZZzK4h4ig==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + + litesvm-linux-x64-gnu@1.1.0: + resolution: {integrity: sha512-Ot8RgUVlMKzKJi2nVDxaHVo0hjB5vtYTomYNIf26mIA32DOy0+dQfwOqUhynhvvSMxN3VFec3r/OtCnk6lRBrw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + + litesvm-linux-x64-musl@1.1.0: + resolution: {integrity: sha512-6kmneOIsTBSActELRTwxIYVJOVaLm3P6uwlmkqc9BUtDAQ7bRdRmwREWSbM8XxKBGw2LjiUfgRJ5WJGYo8fUFg==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + + litesvm@1.1.0: + resolution: {integrity: sha512-UOlMIEst50gSUyPnC2pGjGLygH8iC/GOqnNXQIHc8iGwD76m44ReeA/0h0vu/AIieZ2zG5/ERLxFV0kdNxkNsA==} + engines: {node: '>= 20'} + load-tsconfig@0.2.5: resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1651,8 +1717,9 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - obug@2.1.1: - resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + obug@2.1.2: + resolution: {integrity: sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg==} + engines: {node: '>=12.20.0'} once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -1789,16 +1856,16 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - rollup@4.61.0: - resolution: {integrity: sha512-T9mWdbWfQtp0B5lv/HX+wrhYsmXRlcWnXXmJbXqKJhlRaoS6KMhq0gpyzW4UJfclcxrEdLnTgjT2NjruLONu0g==} + rollup@4.61.1: + resolution: {integrity: sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - semver@7.8.1: - resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} + semver@7.8.2: + resolution: {integrity: sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==} engines: {node: '>=10'} hasBin: true @@ -1966,8 +2033,8 @@ packages: ufo@1.6.4: resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} - undici-types@7.16.0: - resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} undici-types@8.3.0: resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} @@ -2335,85 +2402,90 @@ snapshots: '@rolldown/pluginutils@1.0.1': {} - '@rollup/rollup-android-arm-eabi@4.61.0': + '@rollup/rollup-android-arm-eabi@4.61.1': optional: true - '@rollup/rollup-android-arm64@4.61.0': + '@rollup/rollup-android-arm64@4.61.1': optional: true - '@rollup/rollup-darwin-arm64@4.61.0': + '@rollup/rollup-darwin-arm64@4.61.1': optional: true - '@rollup/rollup-darwin-x64@4.61.0': + '@rollup/rollup-darwin-x64@4.61.1': optional: true - '@rollup/rollup-freebsd-arm64@4.61.0': + '@rollup/rollup-freebsd-arm64@4.61.1': optional: true - '@rollup/rollup-freebsd-x64@4.61.0': + '@rollup/rollup-freebsd-x64@4.61.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.61.0': + '@rollup/rollup-linux-arm-gnueabihf@4.61.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.61.0': + '@rollup/rollup-linux-arm-musleabihf@4.61.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.61.0': + '@rollup/rollup-linux-arm64-gnu@4.61.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.61.0': + '@rollup/rollup-linux-arm64-musl@4.61.1': optional: true - '@rollup/rollup-linux-loong64-gnu@4.61.0': + '@rollup/rollup-linux-loong64-gnu@4.61.1': optional: true - '@rollup/rollup-linux-loong64-musl@4.61.0': + '@rollup/rollup-linux-loong64-musl@4.61.1': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.61.0': + '@rollup/rollup-linux-ppc64-gnu@4.61.1': optional: true - '@rollup/rollup-linux-ppc64-musl@4.61.0': + '@rollup/rollup-linux-ppc64-musl@4.61.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.61.0': + '@rollup/rollup-linux-riscv64-gnu@4.61.1': optional: true - '@rollup/rollup-linux-riscv64-musl@4.61.0': + '@rollup/rollup-linux-riscv64-musl@4.61.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.61.0': + '@rollup/rollup-linux-s390x-gnu@4.61.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.61.0': + '@rollup/rollup-linux-x64-gnu@4.61.1': optional: true - '@rollup/rollup-linux-x64-musl@4.61.0': + '@rollup/rollup-linux-x64-musl@4.61.1': optional: true - '@rollup/rollup-openbsd-x64@4.61.0': + '@rollup/rollup-openbsd-x64@4.61.1': optional: true - '@rollup/rollup-openharmony-arm64@4.61.0': + '@rollup/rollup-openharmony-arm64@4.61.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.61.0': + '@rollup/rollup-win32-arm64-msvc@4.61.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.61.0': + '@rollup/rollup-win32-ia32-msvc@4.61.1': optional: true - '@rollup/rollup-win32-x64-gnu@4.61.0': + '@rollup/rollup-win32-x64-gnu@4.61.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.61.0': + '@rollup/rollup-win32-x64-msvc@4.61.1': optional: true '@solana-program/system@0.12.2(@solana/kit@6.9.0(typescript@5.9.3))': dependencies: '@solana/kit': 6.9.0(typescript@5.9.3) + '@solana-program/token@0.13.0(@solana/kit@6.9.0(typescript@5.9.3))': + dependencies: + '@solana-program/system': 0.12.2(@solana/kit@6.9.0(typescript@5.9.3)) + '@solana/kit': 6.9.0(typescript@5.9.3) + '@solana/accounts@6.9.0(typescript@5.9.3)': dependencies: '@solana/addresses': 6.9.0(typescript@5.9.3) @@ -2561,10 +2633,25 @@ snapshots: '@solana/kit-plugin-payer': 0.9.0(@solana/kit@6.9.0(typescript@5.9.3)) '@solana/kit-plugin-rpc': 0.9.0(@solana/kit@6.9.0(typescript@5.9.3)) + '@solana/kit-plugin-instruction-plan@0.10.0(@solana/kit@6.9.0(typescript@5.9.3))': + dependencies: + '@solana/kit': 6.9.0(typescript@5.9.3) + '@solana/kit-plugin-instruction-plan@0.9.0(@solana/kit@6.9.0(typescript@5.9.3))': dependencies: '@solana/kit': 6.9.0(typescript@5.9.3) + '@solana/kit-plugin-litesvm@0.10.0(@solana/kit@6.9.0(typescript@5.9.3))(typescript@5.9.3)': + dependencies: + '@solana/kit': 6.9.0(typescript@5.9.3) + '@solana/kit-plugin-instruction-plan': 0.10.0(@solana/kit@6.9.0(typescript@5.9.3)) + litesvm: 1.1.0(typescript@5.9.3) + transitivePeerDependencies: + - bufferutil + - fastestsmallesttextencoderdecoder + - typescript + - utf-8-validate + '@solana/kit-plugin-payer@0.9.0(@solana/kit@6.9.0(typescript@5.9.3))': dependencies: '@solana/kit': 6.9.0(typescript@5.9.3) @@ -2573,6 +2660,10 @@ snapshots: dependencies: '@solana/kit': 6.9.0(typescript@5.9.3) + '@solana/kit-plugin-signer@0.10.0(@solana/kit@6.9.0(typescript@5.9.3))': + dependencies: + '@solana/kit': 6.9.0(typescript@5.9.3) + '@solana/kit@6.9.0(typescript@5.9.3)': dependencies: '@solana/accounts': 6.9.0(typescript@5.9.3) @@ -2932,9 +3023,9 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/node@24.12.4': + '@types/node@24.13.0': dependencies: - undici-types: 7.16.0 + undici-types: 7.18.2 '@types/semver@7.7.1': {} @@ -3010,7 +3101,7 @@ snapshots: debug: 4.4.3 globby: 11.1.0 is-glob: 4.0.3 - semver: 7.8.1 + semver: 7.8.2 tsutils: 3.21.0(typescript@5.9.3) optionalDependencies: typescript: 5.9.3 @@ -3025,7 +3116,7 @@ snapshots: globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.9 - semver: 7.8.1 + semver: 7.8.2 ts-api-utils: 1.4.3(typescript@5.9.3) optionalDependencies: typescript: 5.9.3 @@ -3042,7 +3133,7 @@ snapshots: '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.9.3) eslint: 8.57.1 eslint-scope: 5.1.1 - semver: 7.8.1 + semver: 7.8.2 transitivePeerDependencies: - supports-color - typescript @@ -3079,13 +3170,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.8(vite@8.0.16(@types/node@24.12.4)(esbuild@0.27.7))': + '@vitest/mocker@4.1.8(vite@8.0.16(@types/node@24.13.0)(esbuild@0.27.7))': dependencies: '@vitest/spy': 4.1.8 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.16(@types/node@24.12.4)(esbuild@0.27.7) + vite: 8.0.16(@types/node@24.13.0)(esbuild@0.27.7) '@vitest/pretty-format@4.1.8': dependencies: @@ -3430,7 +3521,7 @@ snapshots: dependencies: magic-string: 0.30.21 mlly: 1.8.2 - rollup: 4.61.0 + rollup: 4.61.1 flat-cache@3.2.0: dependencies: @@ -3607,6 +3698,42 @@ snapshots: lines-and-columns@1.2.4: {} + litesvm-darwin-arm64@1.1.0: + optional: true + + litesvm-darwin-x64@1.1.0: + optional: true + + litesvm-linux-arm64-gnu@1.1.0: + optional: true + + litesvm-linux-arm64-musl@1.1.0: + optional: true + + litesvm-linux-x64-gnu@1.1.0: + optional: true + + litesvm-linux-x64-musl@1.1.0: + optional: true + + litesvm@1.1.0(typescript@5.9.3): + dependencies: + '@solana-program/system': 0.12.2(@solana/kit@6.9.0(typescript@5.9.3)) + '@solana-program/token': 0.13.0(@solana/kit@6.9.0(typescript@5.9.3)) + '@solana/kit': 6.9.0(typescript@5.9.3) + optionalDependencies: + litesvm-darwin-arm64: 1.1.0 + litesvm-darwin-x64: 1.1.0 + litesvm-linux-arm64-gnu: 1.1.0 + litesvm-linux-arm64-musl: 1.1.0 + litesvm-linux-x64-gnu: 1.1.0 + litesvm-linux-x64-musl: 1.1.0 + transitivePeerDependencies: + - bufferutil + - fastestsmallesttextencoderdecoder + - typescript + - utf-8-validate + load-tsconfig@0.2.5: {} locate-path@6.0.0: @@ -3665,7 +3792,7 @@ snapshots: object-assign@4.1.1: {} - obug@2.1.1: {} + obug@2.1.2: {} once@1.4.0: dependencies: @@ -3782,42 +3909,42 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.3 '@rolldown/binding-win32-x64-msvc': 1.0.3 - rollup@4.61.0: + rollup@4.61.1: dependencies: '@types/estree': 1.0.9 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.61.0 - '@rollup/rollup-android-arm64': 4.61.0 - '@rollup/rollup-darwin-arm64': 4.61.0 - '@rollup/rollup-darwin-x64': 4.61.0 - '@rollup/rollup-freebsd-arm64': 4.61.0 - '@rollup/rollup-freebsd-x64': 4.61.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.61.0 - '@rollup/rollup-linux-arm-musleabihf': 4.61.0 - '@rollup/rollup-linux-arm64-gnu': 4.61.0 - '@rollup/rollup-linux-arm64-musl': 4.61.0 - '@rollup/rollup-linux-loong64-gnu': 4.61.0 - '@rollup/rollup-linux-loong64-musl': 4.61.0 - '@rollup/rollup-linux-ppc64-gnu': 4.61.0 - '@rollup/rollup-linux-ppc64-musl': 4.61.0 - '@rollup/rollup-linux-riscv64-gnu': 4.61.0 - '@rollup/rollup-linux-riscv64-musl': 4.61.0 - '@rollup/rollup-linux-s390x-gnu': 4.61.0 - '@rollup/rollup-linux-x64-gnu': 4.61.0 - '@rollup/rollup-linux-x64-musl': 4.61.0 - '@rollup/rollup-openbsd-x64': 4.61.0 - '@rollup/rollup-openharmony-arm64': 4.61.0 - '@rollup/rollup-win32-arm64-msvc': 4.61.0 - '@rollup/rollup-win32-ia32-msvc': 4.61.0 - '@rollup/rollup-win32-x64-gnu': 4.61.0 - '@rollup/rollup-win32-x64-msvc': 4.61.0 + '@rollup/rollup-android-arm-eabi': 4.61.1 + '@rollup/rollup-android-arm64': 4.61.1 + '@rollup/rollup-darwin-arm64': 4.61.1 + '@rollup/rollup-darwin-x64': 4.61.1 + '@rollup/rollup-freebsd-arm64': 4.61.1 + '@rollup/rollup-freebsd-x64': 4.61.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.61.1 + '@rollup/rollup-linux-arm-musleabihf': 4.61.1 + '@rollup/rollup-linux-arm64-gnu': 4.61.1 + '@rollup/rollup-linux-arm64-musl': 4.61.1 + '@rollup/rollup-linux-loong64-gnu': 4.61.1 + '@rollup/rollup-linux-loong64-musl': 4.61.1 + '@rollup/rollup-linux-ppc64-gnu': 4.61.1 + '@rollup/rollup-linux-ppc64-musl': 4.61.1 + '@rollup/rollup-linux-riscv64-gnu': 4.61.1 + '@rollup/rollup-linux-riscv64-musl': 4.61.1 + '@rollup/rollup-linux-s390x-gnu': 4.61.1 + '@rollup/rollup-linux-x64-gnu': 4.61.1 + '@rollup/rollup-linux-x64-musl': 4.61.1 + '@rollup/rollup-openbsd-x64': 4.61.1 + '@rollup/rollup-openharmony-arm64': 4.61.1 + '@rollup/rollup-win32-arm64-msvc': 4.61.1 + '@rollup/rollup-win32-ia32-msvc': 4.61.1 + '@rollup/rollup-win32-x64-gnu': 4.61.1 + '@rollup/rollup-win32-x64-msvc': 4.61.1 fsevents: 2.3.3 run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 - semver@7.8.1: {} + semver@7.8.2: {} shebang-command@2.0.0: dependencies: @@ -3935,7 +4062,7 @@ snapshots: picocolors: 1.1.1 postcss-load-config: 6.0.1(postcss@8.5.15) resolve-from: 5.0.0 - rollup: 4.61.0 + rollup: 4.61.1 source-map: 0.7.6 sucrase: 3.35.1 tinyexec: 0.3.2 @@ -3973,7 +4100,7 @@ snapshots: ufo@1.6.4: {} - undici-types@7.16.0: {} + undici-types@7.18.2: {} undici-types@8.3.0: {} @@ -3981,7 +4108,7 @@ snapshots: dependencies: punycode: 2.3.1 - vite@8.0.16(@types/node@24.12.4)(esbuild@0.27.7): + vite@8.0.16(@types/node@24.13.0)(esbuild@0.27.7): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -3989,14 +4116,14 @@ snapshots: rolldown: 1.0.3 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 24.12.4 + '@types/node': 24.13.0 esbuild: 0.27.7 fsevents: 2.3.3 - vitest@4.1.8(@types/node@24.12.4)(vite@8.0.16(@types/node@24.12.4)(esbuild@0.27.7)): + vitest@4.1.8(@types/node@24.13.0)(vite@8.0.16(@types/node@24.13.0)(esbuild@0.27.7)): dependencies: '@vitest/expect': 4.1.8 - '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@24.12.4)(esbuild@0.27.7)) + '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@24.13.0)(esbuild@0.27.7)) '@vitest/pretty-format': 4.1.8 '@vitest/runner': 4.1.8 '@vitest/snapshot': 4.1.8 @@ -4005,7 +4132,7 @@ snapshots: es-module-lexer: 2.1.0 expect-type: 1.3.0 magic-string: 0.30.21 - obug: 2.1.1 + obug: 2.1.2 pathe: 2.0.3 picomatch: 4.0.4 std-env: 4.1.0 @@ -4013,10 +4140,10 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 8.0.16(@types/node@24.12.4)(esbuild@0.27.7) + vite: 8.0.16(@types/node@24.13.0)(esbuild@0.27.7) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 24.12.4 + '@types/node': 24.13.0 transitivePeerDependencies: - msw diff --git a/clients/js/test/_setup.ts b/clients/js/test/_setup.ts index edd56fa3..45d16945 100644 --- a/clients/js/test/_setup.ts +++ b/clients/js/test/_setup.ts @@ -1,260 +1,94 @@ -import { getCreateAccountInstruction } from '@solana-program/system'; -import { - Address, - TransactionMessage, - Commitment, - Rpc, - RpcSubscriptions, - SolanaRpcApi, - SolanaRpcSubscriptionsApi, - TransactionMessageWithBlockhashLifetime, - TransactionMessageWithFeePayer, - TransactionPlan, - TransactionPlanResult, - TransactionPlanner, - TransactionSigner, - airdropFactory, - appendTransactionMessageInstructions, - assertIsSendableTransaction, - assertIsTransactionWithBlockhashLifetime, - createSolanaRpc, - createSolanaRpcSubscriptions, - createTransactionMessage, - createTransactionPlanExecutor, - createTransactionPlanner, - generateKeyPairSigner, - getSignatureFromTransaction, - lamports, - pipe, - sendAndConfirmTransactionFactory, - setTransactionMessageFeePayerSigner, - setTransactionMessageLifetimeUsingBlockhash, - signTransactionMessageWithSigners, -} from '@solana/kit'; +import path from 'node:path'; + +import { systemProgram } from '@solana-program/system'; +import { Address, TransactionSigner, createClient, generateKeyPairSigner, lamports } from '@solana/kit'; +import { litesvm } from '@solana/kit-plugin-litesvm'; +import { airdropSigner, generatedSigner } from '@solana/kit-plugin-signer'; + import { TOKEN_PROGRAM_ADDRESS, + associatedTokenProgram, findAssociatedTokenPda, - getInitializeAccountInstruction, - getInitializeMintInstruction, - getMintSize, - getMintToATAInstructionPlan, - getMintToInstruction, getTokenSize, + tokenProgram, } from '../src'; -type Client = { - rpc: Rpc; - rpcSubscriptions: RpcSubscriptions; - sendTransactionPlan: (transactionPlan: TransactionPlan) => Promise; -}; - -export const createDefaultSolanaClient = (): Client => { - const rpc = createSolanaRpc('http://127.0.0.1:8899'); - const rpcSubscriptions = createSolanaRpcSubscriptions('ws://127.0.0.1:8900'); - - const sendAndConfirm = sendAndConfirmTransactionFactory({ - rpc, - rpcSubscriptions, - }); - const transactionPlanExecutor = createTransactionPlanExecutor({ - executeTransactionMessage: async (context, transactionMessage) => { - const signedTransaction = await signTransactionMessageWithSigners(transactionMessage); - context.transaction = signedTransaction; - assertIsSendableTransaction(signedTransaction); - assertIsTransactionWithBlockhashLifetime(signedTransaction); - await sendAndConfirm(signedTransaction, { commitment: 'confirmed' }); - return signedTransaction; - }, - }); - - const sendTransactionPlan = async (transactionPlan: TransactionPlan) => { - return transactionPlanExecutor(transactionPlan); - }; - - return { rpc, rpcSubscriptions, sendTransactionPlan }; +const TOKEN_BINARY_PATH = path.resolve(__dirname, '..', '..', '..', 'target', 'deploy', 'pinocchio_token_program.so'); + +export const createTestClient = () => { + return createClient() + .use(generatedSigner()) + .use(litesvm()) + .use(airdropSigner(lamports(1_000_000_000n))) + .use(client => { + // Load the token program into the LiteSVM instance from its compiled + // `.so` file. This must run after the `litesvm()` plugin so that + // `client.svm` is available. The system and associated-token + // programs are LiteSVM builtins and need no loading. + client.svm.addProgramFromFile(TOKEN_PROGRAM_ADDRESS, TOKEN_BINARY_PATH); + return client; + }) + .use(systemProgram()) + .use(tokenProgram()) + .use(associatedTokenProgram()); }; -export const generateKeyPairSignerWithSol = async (client: Client, putativeLamports: bigint = 1_000_000_000n) => { - const signer = await generateKeyPairSigner(); - await airdropFactory(client)({ - recipientAddress: signer.address, - lamports: lamports(putativeLamports), - commitment: 'confirmed', - }); - return signer; -}; +export type TestClient = Awaited>; -export const createDefaultTransaction = async (client: Client, feePayer: TransactionSigner) => { - const { value: latestBlockhash } = await client.rpc.getLatestBlockhash().send(); - return pipe( - createTransactionMessage({ version: 0 }), - tx => setTransactionMessageFeePayerSigner(feePayer, tx), - tx => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx), - ); -}; - -export const signAndSendTransaction = async ( - client: Client, - transactionMessage: TransactionMessage & TransactionMessageWithFeePayer & TransactionMessageWithBlockhashLifetime, - commitment: Commitment = 'confirmed', -) => { - const signedTransaction = await signTransactionMessageWithSigners(transactionMessage); - const signature = getSignatureFromTransaction(signedTransaction); - assertIsSendableTransaction(signedTransaction); - assertIsTransactionWithBlockhashLifetime(signedTransaction); - await sendAndConfirmTransactionFactory(client)(signedTransaction, { - commitment, - }); - return signature; -}; - -export const createDefaultTransactionPlanner = (client: Client, feePayer: TransactionSigner): TransactionPlanner => { - return createTransactionPlanner({ - createTransactionMessage: async () => { - const { value: latestBlockhash } = await client.rpc.getLatestBlockhash().send(); - - return pipe( - createTransactionMessage({ version: 0 }), - tx => setTransactionMessageFeePayerSigner(feePayer, tx), - tx => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx), - ); - }, - }); -}; - -export const getBalance = async (client: Client, address: Address) => - (await client.rpc.getBalance(address, { commitment: 'confirmed' }).send()).value; - -export const createMint = async ( - client: Client, - payer: TransactionSigner, - mintAuthority: Address, - decimals: number = 0, -): Promise
=> { - const space = BigInt(getMintSize()); - const [transactionMessage, rent, mint] = await Promise.all([ - createDefaultTransaction(client, payer), - client.rpc.getMinimumBalanceForRentExemption(space).send(), - generateKeyPairSigner(), - ]); - const instructions = [ - getCreateAccountInstruction({ - payer, - newAccount: mint, - lamports: rent, - space, - programAddress: TOKEN_PROGRAM_ADDRESS, - }), - getInitializeMintInstruction({ - mint: mint.address, - decimals, - mintAuthority, - }), - ]; - await pipe( - transactionMessage, - tx => appendTransactionMessageInstructions(instructions, tx), - tx => signAndSendTransaction(client, tx), - ); - - return mint.address; -}; - -export const createToken = async ( - client: Client, - payer: TransactionSigner, - mint: Address, - owner: Address, -): Promise
=> { +export const createToken = async (client: TestClient, mint: Address, owner: Address): Promise
=> { const space = BigInt(getTokenSize()); - const [transactionMessage, rent, token] = await Promise.all([ - createDefaultTransaction(client, payer), + const [rent, token] = await Promise.all([ client.rpc.getMinimumBalanceForRentExemption(space).send(), generateKeyPairSigner(), ]); - const instructions = [ - getCreateAccountInstruction({ - payer, + await client.sendTransaction([ + client.system.instructions.createAccount({ newAccount: token, lamports: rent, space, programAddress: TOKEN_PROGRAM_ADDRESS, }), - getInitializeAccountInstruction({ account: token.address, mint, owner }), - ]; - await pipe( - transactionMessage, - tx => appendTransactionMessageInstructions(instructions, tx), - tx => signAndSendTransaction(client, tx), - ); + client.token.instructions.initializeAccount({ account: token.address, mint, owner }), + ]); return token.address; }; export const createTokenWithAmount = async ( - client: Client, - payer: TransactionSigner, + client: TestClient, mintAuthority: TransactionSigner, mint: Address, owner: Address, amount: bigint, ): Promise
=> { const space = BigInt(getTokenSize()); - const [transactionMessage, rent, token] = await Promise.all([ - createDefaultTransaction(client, payer), + const [rent, token] = await Promise.all([ client.rpc.getMinimumBalanceForRentExemption(space).send(), generateKeyPairSigner(), ]); - const instructions = [ - getCreateAccountInstruction({ - payer, + await client.sendTransaction([ + client.system.instructions.createAccount({ newAccount: token, lamports: rent, space, programAddress: TOKEN_PROGRAM_ADDRESS, }), - getInitializeAccountInstruction({ account: token.address, mint, owner }), - getMintToInstruction({ mint, token: token.address, mintAuthority, amount }), - ]; - await pipe( - transactionMessage, - tx => appendTransactionMessageInstructions(instructions, tx), - tx => signAndSendTransaction(client, tx), - ); + client.token.instructions.initializeAccount({ account: token.address, mint, owner }), + client.token.instructions.mintTo({ mint, token: token.address, mintAuthority, amount }), + ]); return token.address; }; export const createTokenPdaWithAmount = async ( - client: Client, - payer: TransactionSigner, + client: TestClient, mintAuthority: TransactionSigner, mint: Address, owner: Address, amount: bigint, decimals: number, ): Promise
=> { - const [token] = await findAssociatedTokenPda({ - owner, - mint, - tokenProgram: TOKEN_PROGRAM_ADDRESS, - }); - - const transactionPlan = await createDefaultTransactionPlanner( - client, - payer, - )( - getMintToATAInstructionPlan({ - payer, - ata: token, - owner, - mint, - mintAuthority, - amount, - decimals, - }), - ); - - await client.sendTransactionPlan(transactionPlan); + await client.token.instructions.mintToATA({ owner, mint, mintAuthority, amount, decimals }).sendTransaction(); + const [token] = await findAssociatedTokenPda({ owner, mint, tokenProgram: TOKEN_PROGRAM_ADDRESS }); return token; }; diff --git a/clients/js/test/batch.test.ts b/clients/js/test/batch.test.ts index a6a28537..6694714c 100644 --- a/clients/js/test/batch.test.ts +++ b/clients/js/test/batch.test.ts @@ -1,18 +1,15 @@ -import { systemProgram } from '@solana-program/system'; import { AccountRole, decompileTransactionMessage, generateKeyPairSigner, - getBase64Encoder, getCompiledTransactionMessageDecoder, - getTransactionDecoder, Instruction, InstructionWithData, none, ReadonlyUint8Array, some, + Transaction, } from '@solana/kit'; -import { createLocalClient } from '@solana/kit-client-rpc'; import { expect, it } from 'vitest'; import { AccountState, @@ -25,12 +22,13 @@ import { parseBatchInstruction, TOKEN_PROGRAM_ADDRESS, TokenInstruction, - tokenProgram, } from '../src'; +import { createTestClient } from './_setup'; + it('batches multiple token instructions together', async () => { // Given a local validator client with some generated keypairs. - const client = await createLocalClient().use(systemProgram()).use(tokenProgram()); + const client = await createTestClient(); const [mint, token, mintAuthority, tokenOwner] = await Promise.all([ generateKeyPairSigner(), generateKeyPairSigner(), @@ -96,7 +94,7 @@ it('batches multiple token instructions together', async () => { it('fails to batch nested batch instructions', async () => { // Given a local validator client with some generated keypairs. - const client = await createLocalClient().use(systemProgram()).use(tokenProgram()); + const client = await createTestClient(); const [mint, token, mintAuthority, tokenOwner] = await Promise.all([ generateKeyPairSigner(), generateKeyPairSigner(), @@ -211,7 +209,7 @@ it('parses batch instructions including its inner instructions', async () => { it('parses batch instructions from a fetched transaction', async () => { // Given a client with some generated keypairs. - const client = await createLocalClient().use(systemProgram()).use(tokenProgram()); + const client = await createTestClient(); const [mint, token, mintAuthority, tokenOwner] = await Promise.all([ generateKeyPairSigner(), generateKeyPairSigner(), @@ -255,13 +253,10 @@ it('parses batch instructions from a fetched transaction', async () => { ]), ]); - // And given we access the batch instruction from the fetched transaction. - const transactionResult = await client.rpc - .getTransaction(result.context.signature, { encoding: 'base64', maxSupportedTransactionVersion: 0 }) - .send(); - expect(transactionResult).toBeTruthy(); - const transactionBytes = getBase64Encoder().encode(transactionResult!.transaction[0]); - const transaction = getTransactionDecoder().decode(transactionBytes); + // And given we access the batch instruction from the executed transaction, + // which the transaction plan executor stores on the result context. + const transaction = result.context.transaction as Transaction; + expect(transaction).toBeTruthy(); const compiledMessage = getCompiledTransactionMessageDecoder().decode(transaction.messageBytes); const message = decompileTransactionMessage(compiledMessage); const batchInstruction = message.instructions.find( diff --git a/clients/js/test/burnChecked.test.ts b/clients/js/test/burnChecked.test.ts index 8c3d3a21..c11af1b4 100644 --- a/clients/js/test/burnChecked.test.ts +++ b/clients/js/test/burnChecked.test.ts @@ -1,41 +1,27 @@ -import { appendTransactionMessageInstruction, generateKeyPairSigner, pipe } from '@solana/kit'; +import { generateKeyPairSigner } from '@solana/kit'; import { expect, it } from 'vitest'; -import { fetchMint, fetchToken, getApproveInstruction, getBurnCheckedInstruction } from '../src'; -import { - createDefaultSolanaClient, - createDefaultTransaction, - createMint, - createTokenWithAmount, - generateKeyPairSignerWithSol, - signAndSendTransaction, -} from './_setup'; +import { fetchMint, fetchToken } from '../src'; +import { createTestClient, createTokenWithAmount } from './_setup'; it('burns tokens with correct decimals', async () => { // Given a mint with 9 decimals and a token account with 200 tokens. - const client = createDefaultSolanaClient(); - const [payer, mintAuthority, owner] = await Promise.all([ - generateKeyPairSignerWithSol(client), + const client = await createTestClient(); + const [mintAuthority, owner, mint] = await Promise.all([ + generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), ]); - const mint = await createMint(client, payer, mintAuthority.address, 9); - const token = await createTokenWithAmount(client, payer, mintAuthority, mint, owner.address, 200n); + await client.token.instructions + .createMint({ newMint: mint, decimals: 9, mintAuthority: mintAuthority.address }) + .sendTransaction(); + const token = await createTokenWithAmount(client, mintAuthority, mint.address, owner.address, 200n); // When we burn 25 tokens with the correct decimals (9). - const burnChecked = getBurnCheckedInstruction({ - account: token, - mint, - authority: owner, - amount: 25n, - decimals: 9, - }); - await pipe( - await createDefaultTransaction(client, payer), - tx => appendTransactionMessageInstruction(burnChecked, tx), - tx => signAndSendTransaction(client, tx), - ); - - const { data: mintData } = await fetchMint(client.rpc, mint); + await client.token.instructions + .burnChecked({ account: token, mint: mint.address, authority: owner, amount: 25n, decimals: 9 }) + .sendTransaction(); + + const { data: mintData } = await fetchMint(client.rpc, mint.address); expect(mintData.supply).toBe(175n); // Then we expect the token account to have 175 tokens remaining. @@ -45,42 +31,27 @@ it('burns tokens with correct decimals', async () => { it('burns tokens using a delegate', async () => { // Given a token account with 100 tokens and a delegate approved for 60 tokens. - const client = createDefaultSolanaClient(); - const [payer, mintAuthority, owner, delegate] = await Promise.all([ - generateKeyPairSignerWithSol(client), + const client = await createTestClient(); + const [mintAuthority, owner, delegate, mint] = await Promise.all([ + generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), ]); - const mint = await createMint(client, payer, mintAuthority.address, 9); - const token = await createTokenWithAmount(client, payer, mintAuthority, mint, owner.address, 100n); + await client.token.instructions + .createMint({ newMint: mint, decimals: 9, mintAuthority: mintAuthority.address }) + .sendTransaction(); + const token = await createTokenWithAmount(client, mintAuthority, mint.address, owner.address, 100n); // Approve delegate for 60 tokens. - const approve = getApproveInstruction({ - source: token, - delegate: delegate.address, - owner, - amount: 60n, - }); - await pipe( - await createDefaultTransaction(client, payer), - tx => appendTransactionMessageInstruction(approve, tx), - tx => signAndSendTransaction(client, tx), - ); + await client.token.instructions + .approve({ source: token, delegate: delegate.address, owner, amount: 60n }) + .sendTransaction(); // When the delegate burns 30 tokens. - const burnChecked = getBurnCheckedInstruction({ - account: token, - mint, - authority: delegate, - amount: 30n, - decimals: 9, - }); - await pipe( - await createDefaultTransaction(client, payer), - tx => appendTransactionMessageInstruction(burnChecked, tx), - tx => signAndSendTransaction(client, tx), - ); + await client.token.instructions + .burnChecked({ account: token, mint: mint.address, authority: delegate, amount: 30n, decimals: 9 }) + .sendTransaction(); // Then the token account should have 70 tokens remaining. const { data: tokenData } = await fetchToken(client.rpc, token); @@ -90,151 +61,116 @@ it('burns tokens using a delegate', async () => { it('fails when decimals mismatch', async () => { // Given a mint with 9 decimals and a token account with tokens. - const client = createDefaultSolanaClient(); - const [payer, mintAuthority, owner] = await Promise.all([ - generateKeyPairSignerWithSol(client), + const client = await createTestClient(); + const [mintAuthority, owner, mint] = await Promise.all([ + generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), ]); - const mint = await createMint(client, payer, mintAuthority.address, 9); - const token = await createTokenWithAmount(client, payer, mintAuthority, mint, owner.address, 100n); + await client.token.instructions + .createMint({ newMint: mint, decimals: 9, mintAuthority: mintAuthority.address }) + .sendTransaction(); + const token = await createTokenWithAmount(client, mintAuthority, mint.address, owner.address, 100n); // When we try to burn with incorrect decimals (6 instead of 9). - const burnChecked = getBurnCheckedInstruction({ - account: token, - mint, - authority: owner, - amount: 40n, - decimals: 6, // Wrong! Should be 9 - }); - const transactionMessage = await pipe(await createDefaultTransaction(client, payer), tx => - appendTransactionMessageInstruction(burnChecked, tx), - ); - // Then it should fail with MintDecimalsMismatch error. - await expect(signAndSendTransaction(client, transactionMessage)).rejects.toThrow(); + await expect( + client.token.instructions + .burnChecked({ account: token, mint: mint.address, authority: owner, amount: 40n, decimals: 6 }) + .sendTransaction(), + ).rejects.toThrow(); }); it('fails when burning more than account balance', async () => { // Given a token account with only 50 tokens. - const client = createDefaultSolanaClient(); - const [payer, mintAuthority, owner] = await Promise.all([ - generateKeyPairSignerWithSol(client), + const client = await createTestClient(); + const [mintAuthority, owner, mint] = await Promise.all([ + generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), ]); - const mint = await createMint(client, payer, mintAuthority.address, 9); - const token = await createTokenWithAmount(client, payer, mintAuthority, mint, owner.address, 50n); + await client.token.instructions + .createMint({ newMint: mint, decimals: 9, mintAuthority: mintAuthority.address }) + .sendTransaction(); + const token = await createTokenWithAmount(client, mintAuthority, mint.address, owner.address, 50n); // When we try to burn 150 tokens (more than available). - const burnChecked = getBurnCheckedInstruction({ - account: token, - mint, - authority: owner, - amount: 150n, - decimals: 9, - }); - const transactionMessage = await pipe(await createDefaultTransaction(client, payer), tx => - appendTransactionMessageInstruction(burnChecked, tx), - ); - // Then it should fail with InsufficientFunds error. - await expect(signAndSendTransaction(client, transactionMessage)).rejects.toThrow(); + await expect( + client.token.instructions + .burnChecked({ account: token, mint: mint.address, authority: owner, amount: 150n, decimals: 9 }) + .sendTransaction(), + ).rejects.toThrow(); }); it('fails when authority is not a signer', async () => { // Given a token account with tokens. - const client = createDefaultSolanaClient(); - const [payer, mintAuthority, owner, wrongAuthority] = await Promise.all([ - generateKeyPairSignerWithSol(client), + const client = await createTestClient(); + const [mintAuthority, owner, wrongAuthority, mint] = await Promise.all([ + generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), ]); - const mint = await createMint(client, payer, mintAuthority.address, 9); - const token = await createTokenWithAmount(client, payer, mintAuthority, mint, owner.address, 100n); + await client.token.instructions + .createMint({ newMint: mint, decimals: 9, mintAuthority: mintAuthority.address }) + .sendTransaction(); + const token = await createTokenWithAmount(client, mintAuthority, mint.address, owner.address, 100n); // When we try to burn with wrong authority (not the owner). - const burnChecked = getBurnCheckedInstruction({ - account: token, - mint, - authority: wrongAuthority, // Wrong authority! - amount: 40n, - decimals: 9, - }); - const transactionMessage = await pipe(await createDefaultTransaction(client, payer), tx => - appendTransactionMessageInstruction(burnChecked, tx), - ); - // Then it should fail (owner mismatch or missing signature). - await expect(signAndSendTransaction(client, transactionMessage)).rejects.toThrow(); + await expect( + client.token.instructions + .burnChecked({ account: token, mint: mint.address, authority: wrongAuthority, amount: 40n, decimals: 9 }) + .sendTransaction(), + ).rejects.toThrow(); }); it('fails when delegate has insufficient delegated amount', async () => { // Given a token account with 100 tokens and a delegate approved for only 20 tokens. - const client = createDefaultSolanaClient(); - const [payer, mintAuthority, owner, delegate] = await Promise.all([ - generateKeyPairSignerWithSol(client), + const client = await createTestClient(); + const [mintAuthority, owner, delegate, mint] = await Promise.all([ + generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), ]); - const mint = await createMint(client, payer, mintAuthority.address, 9); - const token = await createTokenWithAmount(client, payer, mintAuthority, mint, owner.address, 100n); + await client.token.instructions + .createMint({ newMint: mint, decimals: 9, mintAuthority: mintAuthority.address }) + .sendTransaction(); + const token = await createTokenWithAmount(client, mintAuthority, mint.address, owner.address, 100n); // Approve delegate for only 20 tokens. - const approve = getApproveInstruction({ - source: token, - delegate: delegate.address, - owner, - amount: 20n, - }); - await pipe( - await createDefaultTransaction(client, payer), - tx => appendTransactionMessageInstruction(approve, tx), - tx => signAndSendTransaction(client, tx), - ); + await client.token.instructions + .approve({ source: token, delegate: delegate.address, owner, amount: 20n }) + .sendTransaction(); // When the delegate tries to burn 50 tokens (more than delegated). - const burnChecked = getBurnCheckedInstruction({ - account: token, - mint, - authority: delegate, - amount: 50n, - decimals: 9, - }); - const transactionMessage = await pipe(await createDefaultTransaction(client, payer), tx => - appendTransactionMessageInstruction(burnChecked, tx), - ); - // Then it should fail with InsufficientFunds error. - await expect(signAndSendTransaction(client, transactionMessage)).rejects.toThrow(); + await expect( + client.token.instructions + .burnChecked({ account: token, mint: mint.address, authority: delegate, amount: 50n, decimals: 9 }) + .sendTransaction(), + ).rejects.toThrow(); }); it('burns zero tokens successfully', async () => { // Given a token account with tokens. - const client = createDefaultSolanaClient(); - const [payer, mintAuthority, owner] = await Promise.all([ - generateKeyPairSignerWithSol(client), + const client = await createTestClient(); + const [mintAuthority, owner, mint] = await Promise.all([ + generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), ]); - const mint = await createMint(client, payer, mintAuthority.address, 9); - const token = await createTokenWithAmount(client, payer, mintAuthority, mint, owner.address, 100n); + await client.token.instructions + .createMint({ newMint: mint, decimals: 9, mintAuthority: mintAuthority.address }) + .sendTransaction(); + const token = await createTokenWithAmount(client, mintAuthority, mint.address, owner.address, 100n); // When we burn 0 tokens (edge case). - const burnChecked = getBurnCheckedInstruction({ - account: token, - mint, - authority: owner, - amount: 0n, - decimals: 9, - }); - await pipe( - await createDefaultTransaction(client, payer), - tx => appendTransactionMessageInstruction(burnChecked, tx), - tx => signAndSendTransaction(client, tx), - ); + await client.token.instructions + .burnChecked({ account: token, mint: mint.address, authority: owner, amount: 0n, decimals: 9 }) + .sendTransaction(); // Then the balance should remain unchanged. const { data: tokenData } = await fetchToken(client.rpc, token); diff --git a/clients/js/test/createAssociatedToken.test.ts b/clients/js/test/createAssociatedToken.test.ts index 2164edca..8d6308af 100644 --- a/clients/js/test/createAssociatedToken.test.ts +++ b/clients/js/test/createAssociatedToken.test.ts @@ -1,54 +1,38 @@ -import { Account, appendTransactionMessageInstruction, generateKeyPairSigner, none, pipe } from '@solana/kit'; +import { Account, generateKeyPairSigner, none } from '@solana/kit'; import { expect, it } from 'vitest'; -import { - AccountState, - TOKEN_PROGRAM_ADDRESS, - Token, - fetchToken, - findAssociatedTokenPda, - getCreateAssociatedTokenInstructionAsync, -} from '../src'; -import { - createDefaultSolanaClient, - createDefaultTransaction, - createMint, - generateKeyPairSignerWithSol, - signAndSendTransaction, -} from './_setup'; +import { AccountState, TOKEN_PROGRAM_ADDRESS, Token, fetchToken, findAssociatedTokenPda } from '../src'; +import { createTestClient } from './_setup'; it('creates a new associated token account', async () => { // Given a mint account, its mint authority and a token owner. - const client = createDefaultSolanaClient(); - const [payer, mintAuthority, owner] = await Promise.all([ - generateKeyPairSignerWithSol(client), + const client = await createTestClient(); + const [mintAuthority, owner, mint] = await Promise.all([ + generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), ]); - const mint = await createMint(client, payer, mintAuthority.address); + await client.token.instructions + .createMint({ newMint: mint, decimals: 0, mintAuthority: mintAuthority.address }) + .sendTransaction(); // When we create and initialize a token account at this address. - const createAta = await getCreateAssociatedTokenInstructionAsync({ - payer, - mint, - owner: owner.address, - }); - - await pipe( - await createDefaultTransaction(client, payer), - tx => appendTransactionMessageInstruction(createAta, tx), - tx => signAndSendTransaction(client, tx), - ); + await client.associatedToken.instructions + .createAssociatedToken({ + mint: mint.address, + owner: owner.address, + }) + .sendTransaction(); // Then we expect the token account to exist and have the following data. const [ata] = await findAssociatedTokenPda({ - mint, + mint: mint.address, owner: owner.address, tokenProgram: TOKEN_PROGRAM_ADDRESS, }); expect(await fetchToken(client.rpc, ata)).toMatchObject(>{ address: ata, data: { - mint, + mint: mint.address, owner: owner.address, amount: 0n, delegate: none(), diff --git a/clients/js/test/createAssociatedTokenIdempotent.test.ts b/clients/js/test/createAssociatedTokenIdempotent.test.ts index 06be7ae3..6d32c769 100644 --- a/clients/js/test/createAssociatedTokenIdempotent.test.ts +++ b/clients/js/test/createAssociatedTokenIdempotent.test.ts @@ -1,54 +1,38 @@ -import { Account, appendTransactionMessageInstruction, generateKeyPairSigner, none, pipe } from '@solana/kit'; +import { Account, generateKeyPairSigner, none } from '@solana/kit'; import { expect, it } from 'vitest'; -import { - AccountState, - TOKEN_PROGRAM_ADDRESS, - Token, - fetchToken, - findAssociatedTokenPda, - getCreateAssociatedTokenIdempotentInstructionAsync, -} from '../src'; -import { - createDefaultSolanaClient, - createDefaultTransaction, - createMint, - generateKeyPairSignerWithSol, - signAndSendTransaction, -} from './_setup'; +import { AccountState, TOKEN_PROGRAM_ADDRESS, Token, fetchToken, findAssociatedTokenPda } from '../src'; +import { createTestClient } from './_setup'; it('creates a new associated token account', async () => { // Given a mint account, its mint authority and a token owner. - const client = createDefaultSolanaClient(); - const [payer, mintAuthority, owner] = await Promise.all([ - generateKeyPairSignerWithSol(client), + const client = await createTestClient(); + const [mintAuthority, owner, mint] = await Promise.all([ + generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), ]); - const mint = await createMint(client, payer, mintAuthority.address); + await client.token.instructions + .createMint({ newMint: mint, decimals: 0, mintAuthority: mintAuthority.address }) + .sendTransaction(); // When we create and initialize a token account at this address. - const createAta = await getCreateAssociatedTokenIdempotentInstructionAsync({ - payer, - mint, - owner: owner.address, - }); - - await pipe( - await createDefaultTransaction(client, payer), - tx => appendTransactionMessageInstruction(createAta, tx), - tx => signAndSendTransaction(client, tx), - ); + await client.associatedToken.instructions + .createAssociatedTokenIdempotent({ + mint: mint.address, + owner: owner.address, + }) + .sendTransaction(); // Then we expect the token account to exist and have the following data. const [ata] = await findAssociatedTokenPda({ - mint, + mint: mint.address, owner: owner.address, tokenProgram: TOKEN_PROGRAM_ADDRESS, }); expect(await fetchToken(client.rpc, ata)).toMatchObject(>{ address: ata, data: { - mint, + mint: mint.address, owner: owner.address, amount: 0n, delegate: none(), diff --git a/clients/js/test/createMint.test.ts b/clients/js/test/createMint.test.ts index a2d41168..4371f9a2 100644 --- a/clients/js/test/createMint.test.ts +++ b/clients/js/test/createMint.test.ts @@ -1,26 +1,17 @@ import { Account, generateKeyPairSigner, none, some } from '@solana/kit'; -import { createLocalClient } from '@solana/kit-client-rpc'; import { expect, it } from 'vitest'; -import { fetchMint, getCreateMintInstructionPlan, Mint, tokenProgram } from '../src'; -import { createDefaultSolanaClient, createDefaultTransactionPlanner, generateKeyPairSignerWithSol } from './_setup'; +import { fetchMint, Mint } from '../src'; +import { createTestClient } from './_setup'; it('creates and initializes a new mint account', async () => { // Given an authority and a mint account. - const client = createDefaultSolanaClient(); - const authority = await generateKeyPairSignerWithSol(client); - const mint = await generateKeyPairSigner(); + const client = await createTestClient(); + const [authority, mint] = await Promise.all([generateKeyPairSigner(), generateKeyPairSigner()]); // When we create and initialize a mint account at this address. - const instructionPlan = getCreateMintInstructionPlan({ - payer: authority, - newMint: mint, - decimals: 2, - mintAuthority: authority.address, - }); - - const transactionPlanner = createDefaultTransactionPlanner(client, authority); - const transactionPlan = await transactionPlanner(instructionPlan); - await client.sendTransactionPlan(transactionPlan); + await client.token.instructions + .createMint({ newMint: mint, decimals: 2, mintAuthority: authority.address }) + .sendTransaction(); // Then we expect the mint account to exist and have the following data. const mintAccount = await fetchMint(client.rpc, mint.address); @@ -38,26 +29,22 @@ it('creates and initializes a new mint account', async () => { it('creates a new mint account with a freeze authority', async () => { // Given an authority and a mint account. - const client = createDefaultSolanaClient(); - const [payer, mintAuthority, freezeAuthority, mint] = await Promise.all([ - generateKeyPairSignerWithSol(client), + const client = await createTestClient(); + const [mintAuthority, freezeAuthority, mint] = await Promise.all([ generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), ]); // When we create and initialize a mint account at this address. - const instructionPlan = getCreateMintInstructionPlan({ - payer: payer, - newMint: mint, - decimals: 2, - mintAuthority: mintAuthority.address, - freezeAuthority: freezeAuthority.address, - }); - - const transactionPlanner = createDefaultTransactionPlanner(client, payer); - const transactionPlan = await transactionPlanner(instructionPlan); - await client.sendTransactionPlan(transactionPlan); + await client.token.instructions + .createMint({ + newMint: mint, + decimals: 2, + mintAuthority: mintAuthority.address, + freezeAuthority: freezeAuthority.address, + }) + .sendTransaction(); // Then we expect the mint account to exist and have the following data. const mintAccount = await fetchMint(client.rpc, mint.address); @@ -69,27 +56,3 @@ it('creates a new mint account with a freeze authority', async () => { }, }); }); - -it('creates and initializes a new mint account using the token program plugin', async () => { - // Given a client with the token program plugin, and a mint account. - const client = await createLocalClient().use(tokenProgram()); - const mint = await generateKeyPairSigner(); - - // When we send the "create mint" instruction plan. - await client.token.instructions - .createMint({ newMint: mint, decimals: 2, mintAuthority: client.payer.address }) - .sendTransaction(); - - // Then we expect the mint account to exist and have the following data. - const mintAccount = await client.token.accounts.mint.fetch(mint.address); - expect(mintAccount).toMatchObject({ - address: mint.address, - data: { - mintAuthority: some(client.payer.address), - supply: 0n, - decimals: 2, - isInitialized: true, - freezeAuthority: none(), - }, - }); -}); diff --git a/clients/js/test/initializeAccount.test.ts b/clients/js/test/initializeAccount.test.ts index 5f78cccf..c6ee1d85 100644 --- a/clients/js/test/initializeAccount.test.ts +++ b/clients/js/test/initializeAccount.test.ts @@ -1,63 +1,45 @@ -import { getCreateAccountInstruction } from '@solana-program/system'; -import { Account, appendTransactionMessageInstructions, generateKeyPairSigner, none, pipe } from '@solana/kit'; +import { Account, generateKeyPairSigner, none } from '@solana/kit'; import { expect, it } from 'vitest'; -import { - AccountState, - TOKEN_PROGRAM_ADDRESS, - Token, - fetchToken, - getInitializeAccountInstruction, - getTokenSize, -} from '../src'; -import { - createDefaultSolanaClient, - createDefaultTransaction, - createMint, - generateKeyPairSignerWithSol, - signAndSendTransaction, -} from './_setup'; +import { AccountState, TOKEN_PROGRAM_ADDRESS, Token, fetchToken, getTokenSize } from '../src'; +import { createTestClient } from './_setup'; it('creates and initializes a new token account', async () => { // Given a mint account, its mint authority and two generated keypairs // for the token to be created and its owner. - const client = createDefaultSolanaClient(); - const [payer, mintAuthority, token, owner] = await Promise.all([ - generateKeyPairSignerWithSol(client), + const client = await createTestClient(); + const [mintAuthority, token, owner, mint] = await Promise.all([ + generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), ]); - const mint = await createMint(client, payer, mintAuthority.address); + await client.token.instructions + .createMint({ newMint: mint, decimals: 0, mintAuthority: mintAuthority.address }) + .sendTransaction(); // When we create and initialize a token account at this address. const space = BigInt(getTokenSize()); const rent = await client.rpc.getMinimumBalanceForRentExemption(space).send(); - const instructions = [ - getCreateAccountInstruction({ - payer, + await client.sendTransaction([ + client.system.instructions.createAccount({ newAccount: token, lamports: rent, space, programAddress: TOKEN_PROGRAM_ADDRESS, }), - getInitializeAccountInstruction({ + client.token.instructions.initializeAccount({ account: token.address, - mint, + mint: mint.address, owner: owner.address, }), - ]; - await pipe( - await createDefaultTransaction(client, payer), - tx => appendTransactionMessageInstructions(instructions, tx), - tx => signAndSendTransaction(client, tx), - ); + ]); // Then we expect the token account to exist and have the following data. const tokenAccount = await fetchToken(client.rpc, token.address); expect(tokenAccount).toMatchObject(>{ address: token.address, data: { - mint, + mint: mint.address, owner: owner.address, amount: 0n, delegate: none(), diff --git a/clients/js/test/initializeMint.test.ts b/clients/js/test/initializeMint.test.ts index 38abbc74..90e07f3e 100644 --- a/clients/js/test/initializeMint.test.ts +++ b/clients/js/test/initializeMint.test.ts @@ -1,42 +1,29 @@ -import { getCreateAccountInstruction } from '@solana-program/system'; -import { Account, appendTransactionMessageInstructions, generateKeyPairSigner, none, pipe, some } from '@solana/kit'; +import { Account, generateKeyPairSigner, none, some } from '@solana/kit'; import { expect, it } from 'vitest'; -import { Mint, TOKEN_PROGRAM_ADDRESS, fetchMint, getInitializeMintInstruction, getMintSize } from '../src'; -import { - createDefaultSolanaClient, - createDefaultTransaction, - generateKeyPairSignerWithSol, - signAndSendTransaction, -} from './_setup'; +import { Mint, TOKEN_PROGRAM_ADDRESS, fetchMint, getMintSize } from '../src'; +import { createTestClient } from './_setup'; it('creates and initializes a new mint account', async () => { // Given an authority and a mint account. - const client = createDefaultSolanaClient(); - const authority = await generateKeyPairSignerWithSol(client); - const mint = await generateKeyPairSigner(); + const client = await createTestClient(); + const [authority, mint] = await Promise.all([generateKeyPairSigner(), generateKeyPairSigner()]); // When we create and initialize a mint account at this address. const space = BigInt(getMintSize()); const rent = await client.rpc.getMinimumBalanceForRentExemption(space).send(); - const instructions = [ - getCreateAccountInstruction({ - payer: authority, + await client.sendTransaction([ + client.system.instructions.createAccount({ newAccount: mint, lamports: rent, space, programAddress: TOKEN_PROGRAM_ADDRESS, }), - getInitializeMintInstruction({ + client.token.instructions.initializeMint({ mint: mint.address, decimals: 2, mintAuthority: authority.address, }), - ]; - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions(instructions, tx), - tx => signAndSendTransaction(client, tx), - ); + ]); // Then we expect the mint account to exist and have the following data. const mintAccount = await fetchMint(client.rpc, mint.address); @@ -54,9 +41,8 @@ it('creates and initializes a new mint account', async () => { it('creates a new mint account with a freeze authority', async () => { // Given an authority and a mint account. - const client = createDefaultSolanaClient(); - const [payer, mintAuthority, freezeAuthority, mint] = await Promise.all([ - generateKeyPairSignerWithSol(client), + const client = await createTestClient(); + const [mintAuthority, freezeAuthority, mint] = await Promise.all([ generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), @@ -65,26 +51,20 @@ it('creates a new mint account with a freeze authority', async () => { // When we create and initialize a mint account at this address. const space = BigInt(getMintSize()); const rent = await client.rpc.getMinimumBalanceForRentExemption(space).send(); - const instructions = [ - getCreateAccountInstruction({ - payer, + await client.sendTransaction([ + client.system.instructions.createAccount({ newAccount: mint, lamports: rent, space, programAddress: TOKEN_PROGRAM_ADDRESS, }), - getInitializeMintInstruction({ + client.token.instructions.initializeMint({ mint: mint.address, decimals: 0, mintAuthority: mintAuthority.address, freezeAuthority: freezeAuthority.address, }), - ]; - await pipe( - await createDefaultTransaction(client, payer), - tx => appendTransactionMessageInstructions(instructions, tx), - tx => signAndSendTransaction(client, tx), - ); + ]); // Then we expect the mint account to exist and have the following data. const mintAccount = await fetchMint(client.rpc, mint.address); diff --git a/clients/js/test/mintTo.test.ts b/clients/js/test/mintTo.test.ts index 060f81e5..5d89614f 100644 --- a/clients/js/test/mintTo.test.ts +++ b/clients/js/test/mintTo.test.ts @@ -1,42 +1,34 @@ -import { appendTransactionMessageInstruction, generateKeyPairSigner, pipe } from '@solana/kit'; +import { generateKeyPairSigner } from '@solana/kit'; import { expect, it } from 'vitest'; -import { Mint, Token, fetchMint, fetchToken, getMintToInstruction } from '../src'; -import { - createDefaultSolanaClient, - createDefaultTransaction, - createMint, - createToken, - generateKeyPairSignerWithSol, - signAndSendTransaction, -} from './_setup'; +import { Mint, Token, fetchMint, fetchToken } from '../src'; +import { createTestClient, createToken } from './_setup'; it('mints tokens to a token account', async () => { // Given a mint account and a token account. - const client = createDefaultSolanaClient(); - const [payer, mintAuthority, owner] = await Promise.all([ - generateKeyPairSignerWithSol(client), + const client = await createTestClient(); + const [mintAuthority, owner, mint] = await Promise.all([ + generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), ]); - const mint = await createMint(client, payer, mintAuthority.address); - const token = await createToken(client, payer, mint, owner.address); + await client.token.instructions + .createMint({ newMint: mint, decimals: 0, mintAuthority: mintAuthority.address }) + .sendTransaction(); + const token = await createToken(client, mint.address, owner.address); // When the mint authority mints tokens to the token account. - const mintTo = getMintToInstruction({ - mint, - token, - mintAuthority, - amount: 100n, - }); - await pipe( - await createDefaultTransaction(client, payer), - tx => appendTransactionMessageInstruction(mintTo, tx), - tx => signAndSendTransaction(client, tx), - ); + await client.token.instructions + .mintTo({ + mint: mint.address, + token, + mintAuthority, + amount: 100n, + }) + .sendTransaction(); // Then we expect the mint and token accounts to have the following updated data. const [{ data: mintData }, { data: tokenData }] = await Promise.all([ - fetchMint(client.rpc, mint), + fetchMint(client.rpc, mint.address), fetchToken(client.rpc, token), ]); expect(mintData).toMatchObject({ supply: 100n }); diff --git a/clients/js/test/mintToATA.test.ts b/clients/js/test/mintToATA.test.ts index 6421c755..17c140c0 100644 --- a/clients/js/test/mintToATA.test.ts +++ b/clients/js/test/mintToATA.test.ts @@ -1,57 +1,36 @@ import { Account, generateKeyPairSigner, none } from '@solana/kit'; import { expect, it } from 'vitest'; -import { - AccountState, - TOKEN_PROGRAM_ADDRESS, - Token, - getMintToATAInstructionPlan, - getMintToATAInstructionPlanAsync, - fetchToken, - findAssociatedTokenPda, -} from '../src'; -import { - createDefaultSolanaClient, - createDefaultTransactionPlanner, - createMint, - generateKeyPairSignerWithSol, -} from './_setup'; +import { AccountState, TOKEN_PROGRAM_ADDRESS, Token, fetchToken, findAssociatedTokenPda } from '../src'; +import { createTestClient } from './_setup'; -it('creates a new associated token account with an initial balance', async () => { +it('mints to an associated token account at an explicit address', async () => { // Given a mint account, its mint authority, a token owner and the ATA. - const client = createDefaultSolanaClient(); - const [payer, mintAuthority, owner] = await Promise.all([ - generateKeyPairSignerWithSol(client), + const client = await createTestClient(); + const [mintAuthority, owner, mint] = await Promise.all([ + generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), ]); const decimals = 2; - const mint = await createMint(client, payer, mintAuthority.address, decimals); + await client.token.instructions + .createMint({ newMint: mint, decimals, mintAuthority: mintAuthority.address }) + .sendTransaction(); const [ata] = await findAssociatedTokenPda({ - mint, + mint: mint.address, owner: owner.address, tokenProgram: TOKEN_PROGRAM_ADDRESS, }); - // When we mint to a token account at this address. - const instructionPlan = getMintToATAInstructionPlan({ - payer, - ata, - mint, - owner: owner.address, - mintAuthority, - amount: 1_000n, - decimals, - }); - - const transactionPlanner = createDefaultTransactionPlanner(client, payer); - const transactionPlan = await transactionPlanner(instructionPlan); - await client.sendTransactionPlan(transactionPlan); + // When we mint to the explicit ATA. + await client.token.instructions + .mintToATA({ ata, mint: mint.address, owner: owner.address, mintAuthority, amount: 1_000n, decimals }) + .sendTransaction(); // Then we expect the token account to exist and have the following data. expect(await fetchToken(client.rpc, ata)).toMatchObject(>{ address: ata, data: { - mint, + mint: mint.address, owner: owner.address, amount: 1000n, delegate: none(), @@ -63,42 +42,34 @@ it('creates a new associated token account with an initial balance', async () => }); }); -it('derives a new associated token account with an initial balance', async () => { - // Given a mint account, its mint authority, a token owner and the ATA. - const client = createDefaultSolanaClient(); - const [payer, mintAuthority, owner] = await Promise.all([ - generateKeyPairSignerWithSol(client), +it('mints to an associated token account that it auto-derives', async () => { + // Given a mint account, its mint authority and a token owner. + const client = await createTestClient(); + const [mintAuthority, owner, mint] = await Promise.all([ + generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), ]); const decimals = 2; - const mint = await createMint(client, payer, mintAuthority.address, decimals); + await client.token.instructions + .createMint({ newMint: mint, decimals, mintAuthority: mintAuthority.address }) + .sendTransaction(); - // When we mint to a token account for the mint. - const instructionPlan = await getMintToATAInstructionPlanAsync({ - payer, - mint, - owner: owner.address, - mintAuthority, - amount: 1_000n, - decimals, - }); + // When we mint to the owner, with the ATA auto-derived. + await client.token.instructions + .mintToATA({ mint: mint.address, owner: owner.address, mintAuthority, amount: 1_000n, decimals }) + .sendTransaction(); - const transactionPlanner = createDefaultTransactionPlanner(client, payer); - const transactionPlan = await transactionPlanner(instructionPlan); - await client.sendTransactionPlan(transactionPlan); - - // Then we expect the token account to exist and have the following data. + // Then we expect the derived ATA to exist and have the following data. const [ata] = await findAssociatedTokenPda({ - mint, + mint: mint.address, owner: owner.address, tokenProgram: TOKEN_PROGRAM_ADDRESS, }); - expect(await fetchToken(client.rpc, ata)).toMatchObject(>{ address: ata, data: { - mint, + mint: mint.address, owner: owner.address, amount: 1000n, delegate: none(), @@ -110,99 +81,43 @@ it('derives a new associated token account with an initial balance', async () => }); }); -it('uses an explicit ATA when provided to the async variant', async () => { - // Given a mint account, its mint authority, a token owner and a pre-derived ATA. - const client = createDefaultSolanaClient(); - const [payer, mintAuthority, owner] = await Promise.all([ - generateKeyPairSignerWithSol(client), - generateKeyPairSigner(), - generateKeyPairSigner(), - ]); - const decimals = 2; - const mint = await createMint(client, payer, mintAuthority.address, decimals); - const [ata] = await findAssociatedTokenPda({ - mint, - owner: owner.address, - tokenProgram: TOKEN_PROGRAM_ADDRESS, - }); - - // When we mint via the async variant with an explicit ATA. - const instructionPlan = await getMintToATAInstructionPlanAsync({ - payer, - ata, - mint, - owner: owner.address, - mintAuthority, - amount: 1_000n, - decimals, - }); - - const transactionPlanner = createDefaultTransactionPlanner(client, payer); - const transactionPlan = await transactionPlanner(instructionPlan); - await client.sendTransactionPlan(transactionPlan); - - // Then the explicit ATA should hold the minted tokens. - expect(await fetchToken(client.rpc, ata)).toMatchObject(>{ - address: ata, - data: { - mint, - owner: owner.address, - amount: 1000n, - state: AccountState.Initialized, - }, - }); -}); - it('also mints to an existing associated token account', async () => { // Given a mint account, its mint authority, a token owner and the ATA. - const client = createDefaultSolanaClient(); - const [payer, mintAuthority, owner] = await Promise.all([ - generateKeyPairSignerWithSol(client), + const client = await createTestClient(); + const [mintAuthority, owner, mint] = await Promise.all([ + generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), ]); const decimals = 2; - const mint = await createMint(client, payer, mintAuthority.address, decimals); + await client.token.instructions + .createMint({ newMint: mint, decimals, mintAuthority: mintAuthority.address }) + .sendTransaction(); const [ata] = await findAssociatedTokenPda({ - mint, + mint: mint.address, owner: owner.address, tokenProgram: TOKEN_PROGRAM_ADDRESS, }); - // When we create and initialize a token account at this address. - const instructionPlan = getMintToATAInstructionPlan({ - payer, - ata, - mint, - owner: owner.address, - mintAuthority, - amount: 1_000n, - decimals, - }); + // When we mint to the ATA a first time. + await client.token.instructions + .mintToATA({ ata, mint: mint.address, owner: owner.address, mintAuthority, amount: 1_000n, decimals }) + .sendTransaction(); - const transactionPlanner = createDefaultTransactionPlanner(client, payer); - const transactionPlan = await transactionPlanner(instructionPlan); - await client.sendTransactionPlan(transactionPlan); + // Expire the blockhash so the next (otherwise identical) transaction has a + // distinct signature in LiteSVM, which has no passage of time of its own. + client.svm.expireBlockhash(); // And then we mint additional tokens to the same account. - const instructionPlan2 = getMintToATAInstructionPlan({ - payer, - ata, - mint, - owner: owner.address, - mintAuthority, - amount: 1_000n, - decimals, - }); - - const transactionPlan2 = await transactionPlanner(instructionPlan2); - await client.sendTransactionPlan(transactionPlan2); + await client.token.instructions + .mintToATA({ ata, mint: mint.address, owner: owner.address, mintAuthority, amount: 1_000n, decimals }) + .sendTransaction(); // Then we expect the token account to exist and have the following data. expect(await fetchToken(client.rpc, ata)).toMatchObject(>{ address: ata, data: { - mint, + mint: mint.address, owner: owner.address, amount: 2000n, delegate: none(), diff --git a/clients/js/test/plugin.test.ts b/clients/js/test/plugin.test.ts deleted file mode 100644 index 84a15f29..00000000 --- a/clients/js/test/plugin.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { Account, generateKeyPairSigner } from '@solana/kit'; -import { createLocalClient } from '@solana/kit-client-rpc'; -import { expect, it } from 'vitest'; -import { AccountState, fetchToken, findAssociatedTokenPda, Token, TOKEN_PROGRAM_ADDRESS, tokenProgram } from '../src'; -import { - createDefaultSolanaClient, - createMint, - createTokenPdaWithAmount, - generateKeyPairSignerWithSol, -} from './_setup'; - -it('plugin mintToATA defaults payer and auto-derives ATA', async () => { - // Given a mint account, its mint authority and a token owner. - const client = await createLocalClient().use(tokenProgram()); - const mintAuthority = await generateKeyPairSigner(); - const owner = await generateKeyPairSigner(); - const mint = await generateKeyPairSigner(); - - // And a mint created via the plugin. - await client.token.instructions - .createMint({ newMint: mint, decimals: 2, mintAuthority: mintAuthority.address }) - .sendTransaction(); - - // When we mint to the owner via the plugin (payer defaulted, ATA derived). - await client.token.instructions - .mintToATA({ - mint: mint.address, - owner: owner.address, - mintAuthority, - amount: 1_000n, - decimals: 2, - }) - .sendTransaction(); - - // Then we expect the derived ATA to exist with the correct balance. - const [ata] = await findAssociatedTokenPda({ - mint: mint.address, - owner: owner.address, - tokenProgram: TOKEN_PROGRAM_ADDRESS, - }); - - expect(await fetchToken(client.rpc, ata)).toMatchObject(>{ - address: ata, - data: { - mint: mint.address, - owner: owner.address, - amount: 1000n, - state: AccountState.Initialized, - }, - }); -}); - -it('plugin transferToATA defaults payer and auto-derives source + destination', async () => { - // Given a mint account and ownerA's ATA with 100 tokens. - const baseClient = createDefaultSolanaClient(); - const [payer, mintAuthority, ownerA, ownerB] = await Promise.all([ - generateKeyPairSignerWithSol(baseClient), - generateKeyPairSigner(), - generateKeyPairSigner(), - generateKeyPairSigner(), - ]); - const decimals = 2; - const mint = await createMint(baseClient, payer, mintAuthority.address, decimals); - await createTokenPdaWithAmount(baseClient, payer, mintAuthority, mint, ownerA.address, 100n, decimals); - - // When ownerA transfers 50 tokens to ownerB via the plugin (payer defaulted, source + destination derived). - const client = await createLocalClient().use(tokenProgram()); - await client.token.instructions - .transferToATA({ - mint, - authority: ownerA, - recipient: ownerB.address, - amount: 50n, - decimals, - }) - .sendTransaction(); - - // Then we expect both ATAs to have the correct balances. - const [sourceAta] = await findAssociatedTokenPda({ - owner: ownerA.address, - mint, - tokenProgram: TOKEN_PROGRAM_ADDRESS, - }); - const [destAta] = await findAssociatedTokenPda({ - owner: ownerB.address, - mint, - tokenProgram: TOKEN_PROGRAM_ADDRESS, - }); - - expect(await fetchToken(client.rpc, sourceAta)).toMatchObject(>{ - data: { amount: 50n }, - }); - expect(await fetchToken(client.rpc, destAta)).toMatchObject(>{ - data: { amount: 50n }, - }); -}); diff --git a/clients/js/test/transfer.test.ts b/clients/js/test/transfer.test.ts index da84d02d..6641dcbc 100644 --- a/clients/js/test/transfer.test.ts +++ b/clients/js/test/transfer.test.ts @@ -1,48 +1,34 @@ -import { appendTransactionMessageInstruction, generateKeyPairSigner, pipe } from '@solana/kit'; +import { generateKeyPairSigner } from '@solana/kit'; import { expect, it } from 'vitest'; -import { Mint, Token, fetchMint, fetchToken, getTransferInstruction } from '../src'; -import { - createDefaultSolanaClient, - createDefaultTransaction, - createMint, - createToken, - createTokenWithAmount, - generateKeyPairSignerWithSol, - signAndSendTransaction, -} from './_setup'; +import { Mint, Token, fetchMint, fetchToken } from '../src'; +import { createTestClient, createToken, createTokenWithAmount } from './_setup'; it('transfers tokens from one account to another', async () => { // Given a mint account and two token accounts. // One with 100 tokens and the other with 0 tokens. - const client = createDefaultSolanaClient(); - const [payer, mintAuthority, ownerA, ownerB] = await Promise.all([ - generateKeyPairSignerWithSol(client), + const client = await createTestClient(); + const [mintAuthority, ownerA, ownerB, mint] = await Promise.all([ + generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), ]); - const mint = await createMint(client, payer, mintAuthority.address); + await client.token.instructions + .createMint({ newMint: mint, decimals: 0, mintAuthority: mintAuthority.address }) + .sendTransaction(); const [tokenA, tokenB] = await Promise.all([ - createTokenWithAmount(client, payer, mintAuthority, mint, ownerA.address, 100n), - createToken(client, payer, mint, ownerB.address), + createTokenWithAmount(client, mintAuthority, mint.address, ownerA.address, 100n), + createToken(client, mint.address, ownerB.address), ]); // When owner A transfers 50 tokens to owner B. - const transfer = getTransferInstruction({ - source: tokenA, - destination: tokenB, - authority: ownerA, - amount: 50n, - }); - await pipe( - await createDefaultTransaction(client, payer), - tx => appendTransactionMessageInstruction(transfer, tx), - tx => signAndSendTransaction(client, tx), - ); + await client.token.instructions + .transfer({ source: tokenA, destination: tokenB, authority: ownerA, amount: 50n }) + .sendTransaction(); // Then we expect the mint and token accounts to have the following updated data. const [{ data: mintData }, { data: tokenDataA }, { data: tokenDataB }] = await Promise.all([ - fetchMint(client.rpc, mint), + fetchMint(client.rpc, mint.address), fetchToken(client.rpc, tokenA), fetchToken(client.rpc, tokenB), ]); diff --git a/clients/js/test/transferToATA.test.ts b/clients/js/test/transferToATA.test.ts index bae4fda5..08d54769 100644 --- a/clients/js/test/transferToATA.test.ts +++ b/clients/js/test/transferToATA.test.ts @@ -1,62 +1,44 @@ import { generateKeyPairSigner } from '@solana/kit'; import { expect, it } from 'vitest'; -import { - Mint, - TOKEN_PROGRAM_ADDRESS, - Token, - fetchMint, - fetchToken, - findAssociatedTokenPda, - getTransferToATAInstructionPlan, - getTransferToATAInstructionPlanAsync, -} from '../src'; -import { - createDefaultSolanaClient, - createDefaultTransactionPlanner, - createMint, - createTokenPdaWithAmount, - createTokenWithAmount, - generateKeyPairSignerWithSol, -} from './_setup'; +import { Mint, TOKEN_PROGRAM_ADDRESS, Token, fetchMint, fetchToken, findAssociatedTokenPda } from '../src'; +import { createTestClient, createTokenPdaWithAmount, createTokenWithAmount } from './_setup'; -it('transfers tokens from one account to a new ATA', async () => { +it('transfers tokens from an explicit source to an explicit destination ATA', async () => { // Given a mint account, one token account with 100 tokens, and a second owner. - const client = createDefaultSolanaClient(); - const [payer, mintAuthority, ownerA, ownerB] = await Promise.all([ - generateKeyPairSignerWithSol(client), + const client = await createTestClient(); + const [mintAuthority, ownerA, ownerB, mint] = await Promise.all([ + generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), ]); const decimals = 2; - const mint = await createMint(client, payer, mintAuthority.address, decimals); - const tokenA = await createTokenWithAmount(client, payer, mintAuthority, mint, ownerA.address, 100n); - + await client.token.instructions + .createMint({ newMint: mint, decimals, mintAuthority: mintAuthority.address }) + .sendTransaction(); + const tokenA = await createTokenWithAmount(client, mintAuthority, mint.address, ownerA.address, 100n); const [tokenB] = await findAssociatedTokenPda({ owner: ownerB.address, - mint, + mint: mint.address, tokenProgram: TOKEN_PROGRAM_ADDRESS, }); // When owner A transfers 50 tokens to owner B. - const instructionPlan = getTransferToATAInstructionPlan({ - payer, - mint, - source: tokenA, - authority: ownerA, - destination: tokenB, - recipient: ownerB.address, - amount: 50n, - decimals, - }); - - const transactionPlanner = createDefaultTransactionPlanner(client, payer); - const transactionPlan = await transactionPlanner(instructionPlan); - await client.sendTransactionPlan(transactionPlan); + await client.token.instructions + .transferToATA({ + mint: mint.address, + source: tokenA, + authority: ownerA, + destination: tokenB, + recipient: ownerB.address, + amount: 50n, + decimals, + }) + .sendTransaction(); // Then we expect the mint and token accounts to have the following updated data. const [{ data: mintData }, { data: tokenDataA }, { data: tokenDataB }] = await Promise.all([ - fetchMint(client.rpc, mint), + fetchMint(client.rpc, mint.address), fetchToken(client.rpc, tokenA), fetchToken(client.rpc, tokenB), ]); @@ -65,43 +47,41 @@ it('transfers tokens from one account to a new ATA', async () => { expect(tokenDataB).toMatchObject({ amount: 50n }); }); -it('derives a new ATA and transfers tokens to it', async () => { +it('derives a new destination ATA and transfers tokens to it', async () => { // Given a mint account, one token account with 100 tokens, and a second owner. - const client = createDefaultSolanaClient(); - const [payer, mintAuthority, ownerA, ownerB] = await Promise.all([ - generateKeyPairSignerWithSol(client), + const client = await createTestClient(); + const [mintAuthority, ownerA, ownerB, mint] = await Promise.all([ + generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), ]); const decimals = 2; - const mint = await createMint(client, payer, mintAuthority.address, decimals); - const tokenA = await createTokenWithAmount(client, payer, mintAuthority, mint, ownerA.address, 100n); - - // When owner A transfers 50 tokens to owner B. - const instructionPlan = await getTransferToATAInstructionPlanAsync({ - payer, - mint, - source: tokenA, - authority: ownerA, - recipient: ownerB.address, - amount: 50n, - decimals, - }); - - const transactionPlanner = createDefaultTransactionPlanner(client, payer); - const transactionPlan = await transactionPlanner(instructionPlan); - await client.sendTransactionPlan(transactionPlan); + await client.token.instructions + .createMint({ newMint: mint, decimals, mintAuthority: mintAuthority.address }) + .sendTransaction(); + const tokenA = await createTokenWithAmount(client, mintAuthority, mint.address, ownerA.address, 100n); + + // When owner A transfers 50 tokens to owner B, with the destination derived. + await client.token.instructions + .transferToATA({ + mint: mint.address, + source: tokenA, + authority: ownerA, + recipient: ownerB.address, + amount: 50n, + decimals, + }) + .sendTransaction(); // Then we expect the mint and token accounts to have the following updated data. const [tokenB] = await findAssociatedTokenPda({ owner: ownerB.address, - mint, + mint: mint.address, tokenProgram: TOKEN_PROGRAM_ADDRESS, }); - const [{ data: mintData }, { data: tokenDataA }, { data: tokenDataB }] = await Promise.all([ - fetchMint(client.rpc, mint), + fetchMint(client.rpc, mint.address), fetchToken(client.rpc, tokenA), fetchToken(client.rpc, tokenB), ]); @@ -110,42 +90,41 @@ it('derives a new ATA and transfers tokens to it', async () => { expect(tokenDataB).toMatchObject({ amount: 50n }); }); -it('transfers tokens from one account to an existing ATA', async () => { +it('transfers tokens to an existing destination ATA', async () => { // Given a mint account and two token accounts. // One with 90 tokens and the other with 10 tokens. - const client = createDefaultSolanaClient(); - const [payer, mintAuthority, ownerA, ownerB] = await Promise.all([ - generateKeyPairSignerWithSol(client), + const client = await createTestClient(); + const [mintAuthority, ownerA, ownerB, mint] = await Promise.all([ + generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), ]); const decimals = 2; - const mint = await createMint(client, payer, mintAuthority.address, decimals); + await client.token.instructions + .createMint({ newMint: mint, decimals, mintAuthority: mintAuthority.address }) + .sendTransaction(); const [tokenA, tokenB] = await Promise.all([ - createTokenWithAmount(client, payer, mintAuthority, mint, ownerA.address, 90n), - createTokenPdaWithAmount(client, payer, mintAuthority, mint, ownerB.address, 10n, decimals), + createTokenWithAmount(client, mintAuthority, mint.address, ownerA.address, 90n), + createTokenPdaWithAmount(client, mintAuthority, mint.address, ownerB.address, 10n, decimals), ]); // When owner A transfers 50 tokens to owner B. - const instructionPlan = getTransferToATAInstructionPlan({ - payer, - mint, - source: tokenA, - authority: ownerA, - destination: tokenB, - recipient: ownerB.address, - amount: 50n, - decimals, - }); - - const transactionPlanner = createDefaultTransactionPlanner(client, payer); - const transactionPlan = await transactionPlanner(instructionPlan); - await client.sendTransactionPlan(transactionPlan); + await client.token.instructions + .transferToATA({ + mint: mint.address, + source: tokenA, + authority: ownerA, + destination: tokenB, + recipient: ownerB.address, + amount: 50n, + decimals, + }) + .sendTransaction(); // Then we expect the mint and token accounts to have the following updated data. const [{ data: mintData }, { data: tokenDataA }, { data: tokenDataB }] = await Promise.all([ - fetchMint(client.rpc, mint), + fetchMint(client.rpc, mint.address), fetchToken(client.rpc, tokenA), fetchToken(client.rpc, tokenB), ]); @@ -154,42 +133,34 @@ it('transfers tokens from one account to an existing ATA', async () => { expect(tokenDataB).toMatchObject({ amount: 60n }); }); -it('derives source and destination ATAs and transfers tokens', async () => { +it('derives both source and destination ATAs and transfers tokens', async () => { // Given a mint account and ownerA's ATA with 100 tokens. - const client = createDefaultSolanaClient(); - const [payer, mintAuthority, ownerA, ownerB] = await Promise.all([ - generateKeyPairSignerWithSol(client), + const client = await createTestClient(); + const [mintAuthority, ownerA, ownerB, mint] = await Promise.all([ + generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), generateKeyPairSigner(), ]); const decimals = 2; - const mint = await createMint(client, payer, mintAuthority.address, decimals); - const tokenA = await createTokenPdaWithAmount(client, payer, mintAuthority, mint, ownerA.address, 100n, decimals); + await client.token.instructions + .createMint({ newMint: mint, decimals, mintAuthority: mintAuthority.address }) + .sendTransaction(); + const tokenA = await createTokenPdaWithAmount(client, mintAuthority, mint.address, ownerA.address, 100n, decimals); // When owner A transfers 50 tokens to owner B without specifying source or destination. - const instructionPlan = await getTransferToATAInstructionPlanAsync({ - payer, - mint, - authority: ownerA, - recipient: ownerB.address, - amount: 50n, - decimals, - }); - - const transactionPlanner = createDefaultTransactionPlanner(client, payer); - const transactionPlan = await transactionPlanner(instructionPlan); - await client.sendTransactionPlan(transactionPlan); + await client.token.instructions + .transferToATA({ mint: mint.address, authority: ownerA, recipient: ownerB.address, amount: 50n, decimals }) + .sendTransaction(); // Then we expect both ATAs to have the correct balances. const [tokenB] = await findAssociatedTokenPda({ owner: ownerB.address, - mint, + mint: mint.address, tokenProgram: TOKEN_PROGRAM_ADDRESS, }); - const [{ data: mintData }, { data: tokenDataA }, { data: tokenDataB }] = await Promise.all([ - fetchMint(client.rpc, mint), + fetchMint(client.rpc, mint.address), fetchToken(client.rpc, tokenA), fetchToken(client.rpc, tokenB), ]); diff --git a/scripts/restart-test-validator.sh b/scripts/restart-test-validator.sh deleted file mode 100755 index c2373967..00000000 --- a/scripts/restart-test-validator.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env bash - -here="$(dirname "$0")" -src_root="$(readlink -f "${here}/..")" -cd "${src_root}" - -ARGS=( - -r - -q - --bpf-program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA ./target/deploy/pinocchio_token_program.so -) -PORT=8899 -PID=$(lsof -t -i:$PORT) - -if [ -n "$PID" ]; then - echo "Detected test validator running on PID $PID. Restarting..." - kill "$PID" - sleep 1 -fi - -echo "Starting Solana test validator..." -solana-test-validator "${ARGS[@]}" & -VALIDATOR_PID=$! - -# Wait for test validator to move past slot 0. -echo -n "Waiting for validator to stabilize" -for i in {1..8}; do - if ! kill -0 "$VALIDATOR_PID" 2>/dev/null; then - echo -e "\nTest validator exited early." - exit 1 - fi - - SLOT=$(solana slot -ul 2>/dev/null) - if [[ "$SLOT" =~ ^[0-9]+$ ]] && [ "$SLOT" -gt 8 ]; then - echo -e "\nTest validator is ready. Slot: $SLOT" - exit 0 - fi - - echo -n "." - sleep 1 -done - -echo -e "\nTimed out waiting for test validator to stabilize." -exit 1