Bladeren bron

feat: Anchor bankrun (#113)

* Skip ignoring spl2.0 dependency issue (#2)

* basics, bankrun seperated

* .ghaignore programs

* npm dependencies fix

* tokens :construction:

* tokens escrow :construction:

* anchor action bug

* anchor action bug

* build errors

* added tokens bankrun

* tokens done

* cargo.toml remove pin
Ayush 1 jaar geleden
bovenliggende
commit
97505283f8
100 gewijzigde bestanden met toevoegingen van 11992 en 15221 verwijderingen
  1. 8 13
      .github/.ghaignore
  2. 7 1
      .github/workflows/anchor.yml
  3. 4 1
      basics/account-data/anchor/package.json
  4. 264 661
      basics/account-data/anchor/pnpm-lock.yaml
  5. 51 0
      basics/account-data/anchor/tests/bankrun.test.ts
  6. 4 1
      basics/checking-accounts/anchor/package.json
  7. 264 661
      basics/checking-accounts/anchor/pnpm-lock.yaml
  8. 53 0
      basics/checking-accounts/anchor/tests/bankrun.test.ts
  9. 8 6
      basics/close-account/anchor/package.json
  10. 264 670
      basics/close-account/anchor/pnpm-lock.yaml
  11. 53 0
      basics/close-account/anchor/tests/bankrun.test.ts
  12. 9 6
      basics/counter/anchor/package.json
  13. 268 666
      basics/counter/anchor/pnpm-lock.yaml
  14. 47 0
      basics/counter/anchor/tests/bankrun.test.ts
  15. 5 2
      basics/create-account/anchor/package.json
  16. 264 661
      basics/create-account/anchor/pnpm-lock.yaml
  17. 41 0
      basics/create-account/anchor/tests/bankrun.test.ts
  18. 4 1
      basics/cross-program-invocation/anchor/package.json
  19. 268 666
      basics/cross-program-invocation/anchor/pnpm-lock.yaml
  20. 65 0
      basics/cross-program-invocation/anchor/tests/bankrun.test.ts
  21. 1 1
      basics/favorites/anchor/Anchor.toml
  22. 4 1
      basics/favorites/anchor/package.json
  23. 179 520
      basics/favorites/anchor/pnpm-lock.yaml
  24. 84 0
      basics/favorites/anchor/tests/favorites-bankrun.test.ts
  25. 0 0
      basics/favorites/anchor/tests/favorites.test.ts
  26. 4 1
      basics/hello-solana/anchor/package.json
  27. 264 661
      basics/hello-solana/anchor/pnpm-lock.yaml
  28. 24 0
      basics/hello-solana/anchor/tests/bankrun.test.ts
  29. 4 1
      basics/pda-rent-payer/anchor/package.json
  30. 264 661
      basics/pda-rent-payer/anchor/pnpm-lock.yaml
  31. 58 0
      basics/pda-rent-payer/anchor/tests/bankrun.test.ts
  32. 0 2
      basics/pda-rent-payer/anchor/tests/test.ts
  33. 4 1
      basics/processing-instructions/anchor/package.json
  34. 264 661
      basics/processing-instructions/anchor/pnpm-lock.yaml
  35. 23 0
      basics/processing-instructions/anchor/tests/bankrun.test.ts
  36. 4 1
      basics/program-derived-addresses/anchor/package.json
  37. 264 661
      basics/program-derived-addresses/anchor/pnpm-lock.yaml
  38. 53 0
      basics/program-derived-addresses/anchor/tests/bankrun.test.ts
  39. 0 3
      basics/program-derived-addresses/anchor/tests/test.ts
  40. 0 1
      basics/program-derived-addresses/native/package.json
  41. 182 607
      basics/program-derived-addresses/native/pnpm-lock.yaml
  42. 0 1
      basics/program-derived-addresses/native/tests/test.ts
  43. 1 1
      basics/program-derived-addresses/native/tsconfig.json
  44. 9 6
      basics/realloc/anchor/package.json
  45. 268 666
      basics/realloc/anchor/pnpm-lock.yaml
  46. 79 0
      basics/realloc/anchor/tests/bankrun.test.ts
  47. 4 1
      basics/rent/anchor/package.json
  48. 264 661
      basics/rent/anchor/pnpm-lock.yaml
  49. 42 0
      basics/rent/anchor/tests/bankrun.test.ts
  50. 4 1
      basics/repository-layout/anchor/package.json
  51. 264 661
      basics/repository-layout/anchor/pnpm-lock.yaml
  52. 51 0
      basics/repository-layout/anchor/tests/bankrun.test.ts
  53. 4 1
      basics/transfer-sol/anchor/package.json
  54. 264 661
      basics/transfer-sol/anchor/pnpm-lock.yaml
  55. 75 0
      basics/transfer-sol/anchor/tests/bankrun.test.ts
  56. 1 0
      basics/transfer-sol/anchor/tests/test.ts
  57. 1 1
      biome.json
  58. 7 0
      package.json
  59. 675 0
      pnpm-lock.yaml
  60. 1 1
      tokens/create-token/anchor/Anchor.toml
  61. 7 1
      tokens/create-token/anchor/package.json
  62. 249 653
      tokens/create-token/anchor/pnpm-lock.yaml
  63. 34 0
      tokens/create-token/anchor/prepare.mjs
  64. 69 0
      tokens/create-token/anchor/tests/bankrun.test.ts
  65. 1 1
      tokens/escrow/anchor/Anchor.toml
  66. 5 2
      tokens/escrow/anchor/package.json
  67. 103 122
      tokens/escrow/anchor/pnpm-lock.yaml
  68. 177 0
      tokens/escrow/anchor/tests/bankrun.test.ts
  69. 0 0
      tokens/escrow/anchor/tests/escrow.test.ts
  70. 1 1
      tokens/nft-minter/anchor/Anchor.toml
  71. 8 4
      tokens/nft-minter/anchor/package.json
  72. 184 570
      tokens/nft-minter/anchor/pnpm-lock.yaml
  73. 34 0
      tokens/nft-minter/anchor/prepare.mjs
  74. 57 0
      tokens/nft-minter/anchor/tests/bankrun.test.ts
  75. 6 2
      tokens/nft-operations/anchor/package.json
  76. 113 0
      tokens/nft-operations/anchor/pnpm-lock.yaml
  77. 34 0
      tokens/nft-operations/anchor/prepare.mjs
  78. 150 0
      tokens/nft-operations/anchor/tests/bankrun.test.ts
  79. 1 1
      tokens/pda-mint-authority/anchor/Anchor.toml
  80. 7 1
      tokens/pda-mint-authority/anchor/package.json
  81. 185 570
      tokens/pda-mint-authority/anchor/pnpm-lock.yaml
  82. 34 0
      tokens/pda-mint-authority/anchor/prepare.mjs
  83. 69 0
      tokens/pda-mint-authority/anchor/tests/bankrun.test.ts
  84. 1 1
      tokens/spl-token-minter/anchor/Anchor.toml
  85. 7 1
      tokens/spl-token-minter/anchor/package.json
  86. 185 570
      tokens/spl-token-minter/anchor/pnpm-lock.yaml
  87. 34 0
      tokens/spl-token-minter/anchor/prepare.mjs
  88. 75 0
      tokens/spl-token-minter/anchor/tests/bankrun.test.ts
  89. 4 1
      tokens/token-2022/basics/anchor/package.json
  90. 268 666
      tokens/token-2022/basics/anchor/pnpm-lock.yaml
  91. 5 4
      tokens/token-2022/basics/anchor/tests/anchor.ts
  92. 149 0
      tokens/token-2022/basics/anchor/tests/bankrun.test.ts
  93. 2 0
      tokens/token-2022/cpi-guard/anchor/package.json
  94. 180 582
      tokens/token-2022/cpi-guard/anchor/pnpm-lock.yaml
  95. 803 236
      tokens/token-2022/default-account-state/anchor/pnpm-lock.yaml
  96. 723 180
      tokens/token-2022/group/anchor/pnpm-lock.yaml
  97. 1 1
      tokens/token-2022/group/anchor/programs/group/Cargo.toml
  98. 803 236
      tokens/token-2022/immutable-owner/anchor/pnpm-lock.yaml
  99. 803 236
      tokens/token-2022/interest-bearing/anchor/pnpm-lock.yaml
  100. 776 118
      tokens/token-2022/memo-transfer/anchor/pnpm-lock.yaml

+ 8 - 13
.github/.ghaignore

@@ -11,26 +11,21 @@ tokens/nft-minter/native
 tokens/transfer-tokens/native
 tokens/spl-token-minter/native
 tokens/create-token/native
-tokens/create-token/anchor
-tokens/nft-minter/anchor
-tokens/pda-mint-authority/anchor
-tokens/spl-token-minter/anchor
+
 tokens/token-swap/anchor
-tokens/transfer-tokens/anchor
 
 # not building
 oracles/pyth/anchor
 
-# avm broke hence can't build
-# can't use anchor.toml [toolchain] with stable rust
-# update these programs to use latest anchor version to fix
-tokens/token-2022/group/anchor
-tokens/token-2022/immutable-owner/anchor
-tokens/token-2022/interest-bearing/anchor
-tokens/token-2022/memo-transfer/anchor
-
 # not building
 compression/cutils/anchor
 compression/cnft-vault/anchor
 # builds but need to test on localhost
 compression/cnft-burn/anchor
+
+# test failing
+# https://github.com/solana-developers/helpers/issues/40
+tokens/escrow/anchor
+
+# not live
+tokens/token-2022/group/anchor

+ 7 - 1
.github/workflows/anchor.yml

@@ -125,12 +125,14 @@ jobs:
           function build_and_test() {
             local project=$1
             echo "Building and Testing $project"
-            cd "$project"
+            cd "$project" || return 1
 
             # Run anchor build
             if ! anchor build; then
               echo "::error::anchor build failed for $project"
               echo "$project: anchor build failed" >> $GITHUB_WORKSPACE/failed_projects.txt
+              rm -rf target
+              cd - > /dev/null
               return 1
             fi
 
@@ -138,6 +140,7 @@ jobs:
             if ! pnpm install --frozen-lockfile; then
               echo "::error::pnpm install failed for $project"
               echo "$project: pnpm install failed" >> $GITHUB_WORKSPACE/failed_projects.txt
+              cd - > /dev/null
               return 1
             fi
 
@@ -145,11 +148,14 @@ jobs:
             if ! anchor test; then
               echo "::error::anchor test failed for $project"
               echo "$project: anchor test failed" >> $GITHUB_WORKSPACE/failed_projects.txt
+              rm -rf target node_modules
+              cd - > /dev/null
               return 1
             fi
 
             echo "Build and tests succeeded for $project."
             rm -rf target node_modules
+            cd - > /dev/null
             return 0
           }
 

+ 4 - 1
basics/account-data/anchor/package.json

@@ -1,8 +1,11 @@
 {
   "dependencies": {
-    "@coral-xyz/anchor": "^0.30.0"
+    "@coral-xyz/anchor": "^0.30.0",
+    "@solana/web3.js": "^1.95.2"
   },
   "devDependencies": {
+    "anchor-bankrun": "^0.4.0",
+    "solana-bankrun": "^0.3.0",
     "@types/bn.js": "^5.1.0",
     "@types/chai": "^4.3.0",
     "@types/mocha": "^9.0.0",

File diff suppressed because it is too large
+ 264 - 661
basics/account-data/anchor/pnpm-lock.yaml


+ 51 - 0
basics/account-data/anchor/tests/bankrun.test.ts

@@ -0,0 +1,51 @@
+import { describe, it } from 'node:test';
+import * as anchor from '@coral-xyz/anchor';
+import { Keypair } from '@solana/web3.js';
+import { PublicKey } from '@solana/web3.js';
+import { BankrunProvider } from 'anchor-bankrun';
+import { startAnchor } from 'solana-bankrun';
+import type { AnchorProgramExample } from '../target/types/anchor_program_example';
+
+const IDL = require('../target/idl/anchor_program_example.json');
+const PROGRAM_ID = new PublicKey(IDL.address);
+
+describe('Account Data!', async () => {
+  const context = await startAnchor('', [{ name: 'anchor_program_example', programId: PROGRAM_ID }], []);
+  const provider = new BankrunProvider(context);
+
+  const payer = provider.wallet as anchor.Wallet;
+  const program = new anchor.Program<AnchorProgramExample>(IDL, provider);
+
+  // Generate a new keypair for the addressInfo account
+  const addressInfoAccount = new Keypair();
+
+  it('Create the address info account', async () => {
+    console.log(`Payer Address      : ${payer.publicKey}`);
+    console.log(`Address Info Acct  : ${addressInfoAccount.publicKey}`);
+
+    // Instruction Ix data
+    const addressInfo = {
+      name: 'Joe C',
+      houseNumber: 136,
+      street: 'Mile High Dr.',
+      city: 'Solana Beach',
+    };
+
+    await program.methods
+      .createAddressInfo(addressInfo.name, addressInfo.houseNumber, addressInfo.street, addressInfo.city)
+      .accounts({
+        addressInfo: addressInfoAccount.publicKey,
+        payer: payer.publicKey,
+      })
+      .signers([addressInfoAccount])
+      .rpc();
+  });
+
+  it("Read the new account's data", async () => {
+    const addressInfo = await program.account.addressInfo.fetch(addressInfoAccount.publicKey);
+    console.log(`Name     : ${addressInfo.name}`);
+    console.log(`House Num: ${addressInfo.houseNumber}`);
+    console.log(`Street   : ${addressInfo.street}`);
+    console.log(`City     : ${addressInfo.city}`);
+  });
+});

+ 4 - 1
basics/checking-accounts/anchor/package.json

@@ -1,8 +1,11 @@
 {
   "dependencies": {
-    "@coral-xyz/anchor": "^0.30.0"
+    "@coral-xyz/anchor": "^0.30.0",
+    "@solana/web3.js": "^1.95.2"
   },
   "devDependencies": {
+    "anchor-bankrun": "^0.4.0",
+    "solana-bankrun": "^0.3.0",
     "@types/bn.js": "^5.1.0",
     "@types/chai": "^4.3.0",
     "@types/mocha": "^9.0.0",

File diff suppressed because it is too large
+ 264 - 661
basics/checking-accounts/anchor/pnpm-lock.yaml


+ 53 - 0
basics/checking-accounts/anchor/tests/bankrun.test.ts

@@ -0,0 +1,53 @@
+import { describe, it } from 'node:test';
+import * as anchor from '@coral-xyz/anchor';
+import { Keypair, SystemProgram, Transaction, sendAndConfirmTransaction } from '@solana/web3.js';
+import { PublicKey } from '@solana/web3.js';
+import { BankrunProvider } from 'anchor-bankrun';
+import { startAnchor } from 'solana-bankrun';
+import type { AnchorProgramExample } from '../target/types/anchor_program_example';
+
+const IDL = require('../target/idl/anchor_program_example.json');
+const PROGRAM_ID = new PublicKey(IDL.address);
+
+describe('Bankrun example', async () => {
+  const context = await startAnchor('', [{ name: 'anchor_program_example', programId: PROGRAM_ID }], []);
+  const provider = new BankrunProvider(context);
+
+  const wallet = provider.wallet as anchor.Wallet;
+  const program = new anchor.Program<AnchorProgramExample>(IDL, provider);
+  const client = context.banksClient;
+
+  // We'll create this ahead of time.
+  // Our program will try to modify it.
+  const accountToChange = new Keypair();
+  // Our program will create this.
+  const accountToCreate = new Keypair();
+
+  it('Create an account owned by our program', async () => {
+    const instruction = SystemProgram.createAccount({
+      fromPubkey: provider.wallet.publicKey,
+      newAccountPubkey: accountToChange.publicKey,
+      lamports: await provider.connection.getMinimumBalanceForRentExemption(0),
+      space: 0,
+      programId: program.programId, // Our program
+    });
+
+    const transaction = new Transaction();
+    const blockhash = context.lastBlockhash;
+
+    transaction.recentBlockhash = blockhash;
+    transaction.add(instruction).sign(wallet.payer, accountToChange);
+    await client.processTransaction(transaction);
+  });
+
+  it('Check accounts', async () => {
+    await program.methods
+      .checkAccounts()
+      .accounts({
+        payer: wallet.publicKey,
+        accountToCreate: accountToCreate.publicKey,
+        accountToChange: accountToChange.publicKey,
+      })
+      .rpc();
+  });
+});

+ 8 - 6
basics/close-account/anchor/package.json

@@ -4,16 +4,18 @@
     "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
   },
   "dependencies": {
-    "@coral-xyz/anchor": "^0.30.0"
+    "@coral-xyz/anchor": "^0.30.0",
+    "@solana/web3.js": "^1.95.2"
   },
   "devDependencies": {
-    "chai": "^4.3.4",
-    "mocha": "^9.0.3",
-    "ts-mocha": "^10.0.0",
+    "anchor-bankrun": "^0.4.0",
+    "solana-bankrun": "^0.3.0",
     "@types/bn.js": "^5.1.0",
     "@types/chai": "^4.3.0",
     "@types/mocha": "^9.0.0",
-    "typescript": "^4.3.5",
-    "prettier": "^2.6.2"
+    "chai": "^4.3.4",
+    "mocha": "^9.0.3",
+    "ts-mocha": "^10.0.0",
+    "typescript": "^4.3.5"
   }
 }

File diff suppressed because it is too large
+ 264 - 670
basics/close-account/anchor/pnpm-lock.yaml


+ 53 - 0
basics/close-account/anchor/tests/bankrun.test.ts

@@ -0,0 +1,53 @@
+import assert from 'node:assert';
+import { describe, it } from 'node:test';
+import * as anchor from '@coral-xyz/anchor';
+import { PublicKey } from '@solana/web3.js';
+import { BankrunProvider } from 'anchor-bankrun';
+import { startAnchor } from 'solana-bankrun';
+import type { CloseAccountProgram } from '../target/types/close_account_program';
+
+const IDL = require('../target/idl/close_account_program.json');
+const PROGRAM_ID = new PublicKey(IDL.address);
+
+describe('close-an-account', async () => {
+  // Configure the client to use the local cluster.
+  const context = await startAnchor('', [{ name: 'close_account_program', programId: PROGRAM_ID }], []);
+  const provider = new BankrunProvider(context);
+
+  const payer = provider.wallet as anchor.Wallet;
+  const program = new anchor.Program<CloseAccountProgram>(IDL, provider);
+  // Derive the PDA for the user's account.
+  const [userAccountAddress] = PublicKey.findProgramAddressSync([Buffer.from('USER'), payer.publicKey.toBuffer()], program.programId);
+
+  it('Create Account', async () => {
+    await program.methods
+      .createUser('John Doe')
+      .accounts({
+        user: payer.publicKey,
+      })
+      .rpc();
+
+    // Fetch the account data
+    const userAccount = await program.account.userState.fetch(userAccountAddress);
+    assert.equal(userAccount.name, 'John Doe');
+    assert.equal(userAccount.user.toBase58(), payer.publicKey.toBase58());
+  });
+
+  it('Close Account', async () => {
+    await program.methods
+      .closeUser()
+      .accounts({
+        user: payer.publicKey,
+      })
+      .rpc();
+
+    // The account should no longer exist, returning null.
+    try {
+      const userAccount = await program.account.userState.fetchNullable(userAccountAddress);
+      assert.equal(userAccount, null);
+    } catch (err) {
+      // Won't return null and will throw an error in anchor-bankrun'
+      assert.equal(err.message, `Could not find ${userAccountAddress}`);
+    }
+  });
+});

+ 9 - 6
basics/counter/anchor/package.json

@@ -4,16 +4,19 @@
     "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
   },
   "dependencies": {
-    "@coral-xyz/anchor": "^0.30.0"
+    "@coral-xyz/anchor": "^0.30.0",
+    "@solana/web3.js": "^1.95.2"
   },
   "devDependencies": {
-    "chai": "^4.3.4",
-    "mocha": "^9.0.3",
-    "ts-mocha": "^10.0.0",
+    "anchor-bankrun": "^0.4.0",
+    "solana-bankrun": "^0.3.0",
     "@types/bn.js": "^5.1.0",
     "@types/chai": "^4.3.0",
     "@types/mocha": "^9.0.0",
-    "typescript": "^4.3.5",
-    "prettier": "^2.6.2"
+    "chai": "^4.3.4",
+    "mocha": "^9.0.3",
+    "prettier": "^2.6.2",
+    "ts-mocha": "^10.0.0",
+    "typescript": "^4.3.5"
   }
 }

File diff suppressed because it is too large
+ 268 - 666
basics/counter/anchor/pnpm-lock.yaml


+ 47 - 0
basics/counter/anchor/tests/bankrun.test.ts

@@ -0,0 +1,47 @@
+import { describe, it } from 'node:test';
+import * as anchor from '@coral-xyz/anchor';
+import type { Program } from '@coral-xyz/anchor';
+import { Keypair } from '@solana/web3.js';
+import { PublicKey } from '@solana/web3.js';
+import { BankrunProvider } from 'anchor-bankrun';
+import { assert } from 'chai';
+import { startAnchor } from 'solana-bankrun';
+import type { CounterAnchor } from '../target/types/counter_anchor';
+
+const IDL = require('../target/idl/counter_anchor.json');
+const PROGRAM_ID = new PublicKey(IDL.address);
+
+describe('counter_anchor', async () => {
+  // Configure the client to use the anchor-bankrun
+  const context = await startAnchor('', [{ name: 'counter_anchor', programId: PROGRAM_ID }], []);
+  const provider = new BankrunProvider(context);
+
+  const payer = provider.wallet as anchor.Wallet;
+  const program = new anchor.Program<CounterAnchor>(IDL, provider);
+
+  // Generate a new keypair for the counter account
+  const counterKeypair = new Keypair();
+
+  it('Initialize Counter', async () => {
+    await program.methods
+      .initializeCounter()
+      .accounts({
+        counter: counterKeypair.publicKey,
+        payer: payer.publicKey,
+      })
+      .signers([counterKeypair])
+      .rpc();
+
+    const currentCount = await program.account.counter.fetch(counterKeypair.publicKey);
+
+    assert(currentCount.count.toNumber() === 0, 'Expected initialized count to be 0');
+  });
+
+  it('Increment Counter', async () => {
+    await program.methods.increment().accounts({ counter: counterKeypair.publicKey }).rpc();
+
+    const currentCount = await program.account.counter.fetch(counterKeypair.publicKey);
+
+    assert(currentCount.count.toNumber() === 1, 'Expected  count to be 1');
+  });
+});

+ 5 - 2
basics/create-account/anchor/package.json

@@ -1,12 +1,15 @@
 {
   "dependencies": {
-    "@coral-xyz/anchor": "^0.30.0"
+    "@coral-xyz/anchor": "^0.30.0",
+    "@solana/web3.js": "^1.95.2"
   },
   "devDependencies": {
+    "anchor-bankrun": "^0.4.0",
+    "solana-bankrun": "^0.3.0",
     "@types/bn.js": "^5.1.0",
     "@types/chai": "^4.3.0",
     "@types/mocha": "^9.0.0",
-    "chai": "^4.3.4",
+    "chai": "^4.4.1",
     "mocha": "^9.0.3",
     "ts-mocha": "^10.0.0",
     "typescript": "^4.3.5"

File diff suppressed because it is too large
+ 264 - 661
basics/create-account/anchor/pnpm-lock.yaml


+ 41 - 0
basics/create-account/anchor/tests/bankrun.test.ts

@@ -0,0 +1,41 @@
+import { describe, it } from 'node:test';
+import * as anchor from '@coral-xyz/anchor';
+import { PublicKey } from '@solana/web3.js';
+import { Keypair, SystemProgram } from '@solana/web3.js';
+import { BankrunProvider } from 'anchor-bankrun';
+import { assert } from 'chai';
+import { startAnchor } from 'solana-bankrun';
+import type { CreateSystemAccount } from '../target/types/create_system_account';
+
+const IDL = require('../target/idl/create_system_account.json');
+const PROGRAM_ID = new PublicKey(IDL.address);
+
+describe('Create a system account', async () => {
+  const context = await startAnchor('', [{ name: 'create_system_account', programId: PROGRAM_ID }], []);
+  const provider = new BankrunProvider(context);
+
+  const wallet = provider.wallet as anchor.Wallet;
+  const program = new anchor.Program<CreateSystemAccount>(IDL, provider);
+  const connection = provider.connection;
+
+  it('Create the account', async () => {
+    // Generate a new keypair for the new account
+    const newKeypair = new Keypair();
+
+    await program.methods
+      .createSystemAccount()
+      .accounts({
+        payer: wallet.publicKey,
+        newAccount: newKeypair.publicKey,
+      })
+      .signers([newKeypair])
+      .rpc();
+
+    // Minimum balance for rent exemption for new account
+    const lamports = await connection.getMinimumBalanceForRentExemption(0);
+
+    // Check that the account was created
+    const accountInfo = await connection.getAccountInfo(newKeypair.publicKey);
+    assert(accountInfo.lamports === lamports);
+  });
+});

+ 4 - 1
basics/cross-program-invocation/anchor/package.json

@@ -4,9 +4,12 @@
     "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
   },
   "dependencies": {
-    "@coral-xyz/anchor": "^0.30.0"
+    "@coral-xyz/anchor": "^0.30.0",
+    "@solana/web3.js": "^1.95.2"
   },
   "devDependencies": {
+    "anchor-bankrun": "^0.4.0",
+    "solana-bankrun": "^0.3.0",
     "chai": "^4.3.4",
     "mocha": "^9.0.3",
     "ts-mocha": "^10.0.0",

File diff suppressed because it is too large
+ 268 - 666
basics/cross-program-invocation/anchor/pnpm-lock.yaml


+ 65 - 0
basics/cross-program-invocation/anchor/tests/bankrun.test.ts

@@ -0,0 +1,65 @@
+import { describe, it } from 'node:test';
+import * as anchor from '@coral-xyz/anchor';
+import { PublicKey } from '@solana/web3.js';
+import { BankrunProvider } from 'anchor-bankrun';
+import { startAnchor } from 'solana-bankrun';
+import type { Hand } from '../target/types/hand';
+import type { Lever } from '../target/types/lever';
+
+const HAND_IDL = require('../target/idl/hand.json');
+const LEVER_IDL = require('../target/idl/lever.json');
+const HAND_PROGRAM_ID = new PublicKey(HAND_IDL.address);
+const LEVER_PROGRAM_ID = new PublicKey(LEVER_IDL.address);
+
+describe('cpi', async () => {
+  const context = await startAnchor(
+    '',
+    [
+      {
+        name: 'hand',
+        programId: HAND_PROGRAM_ID,
+      },
+      {
+        name: 'lever',
+        programId: LEVER_PROGRAM_ID,
+      },
+    ],
+    [],
+  );
+  const provider = new BankrunProvider(context);
+
+  const hand = new anchor.Program<Hand>(HAND_IDL, provider);
+  const lever = new anchor.Program<Lever>(LEVER_IDL, provider);
+
+  // Generate a new keypair for the power account
+  const powerAccount = new anchor.web3.Keypair();
+
+  it('Initialize the lever!', async () => {
+    await lever.methods
+      .initialize()
+      .accounts({
+        power: powerAccount.publicKey,
+        user: provider.wallet.publicKey,
+      })
+      .signers([powerAccount])
+      .rpc();
+  });
+
+  it('Pull the lever!', async () => {
+    await hand.methods
+      .pullLever('Chris')
+      .accounts({
+        power: powerAccount.publicKey,
+      })
+      .rpc();
+  });
+
+  it('Pull it again!', async () => {
+    await hand.methods
+      .pullLever('Ashley')
+      .accounts({
+        power: powerAccount.publicKey,
+      })
+      .rpc();
+  });
+});

+ 1 - 1
basics/favorites/anchor/Anchor.toml

@@ -15,4 +15,4 @@ cluster = "Localnet"
 wallet = "~/.config/solana/id.json"
 
 [scripts]
-test = "pnpm ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
+test = "pnpm ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.test.ts"

+ 4 - 1
basics/favorites/anchor/package.json

@@ -5,10 +5,13 @@
   },
   "dependencies": {
     "@coral-xyz/anchor": "^0.30.0",
-    "@solana-developers/helpers": "^2.0.0"
+    "@solana-developers/helpers": "^2.0.0",
+    "@solana/web3.js": "^1.95.2"
   },
   "license": "MIT",
   "devDependencies": {
+    "anchor-bankrun": "^0.4.0",
+    "solana-bankrun": "^0.3.0",
     "@types/bn.js": "^5.1.0",
     "@types/chai": "^4.3.0",
     "@types/mocha": "^9.0.0",

File diff suppressed because it is too large
+ 179 - 520
basics/favorites/anchor/pnpm-lock.yaml


+ 84 - 0
basics/favorites/anchor/tests/favorites-bankrun.test.ts

@@ -0,0 +1,84 @@
+import { describe, it } from 'node:test';
+import * as anchor from '@coral-xyz/anchor';
+import { getCustomErrorMessage } from '@solana-developers/helpers';
+import { PublicKey } from '@solana/web3.js';
+import { BankrunProvider } from 'anchor-bankrun';
+import { assert } from 'chai';
+import { startAnchor } from 'solana-bankrun';
+import type { Favorites } from '../target/types/favorites';
+import { systemProgramErrors } from './system-errors';
+
+const web3 = anchor.web3;
+const IDL = require('../target/idl/favorites.json');
+const PROGRAM_ID = new PublicKey(IDL.address);
+
+describe('Favorites Bankrun', async () => {
+  // Use the cluster and the keypair from Anchor.toml
+  // Load programs into anchor-bankrun
+  const context = await startAnchor('', [{ name: 'favorites', programId: PROGRAM_ID }], []);
+  const provider = new BankrunProvider(context);
+  anchor.setProvider(provider);
+  const user = (provider.wallet as anchor.Wallet).payer;
+  const someRandomGuy = anchor.web3.Keypair.generate();
+
+  const program = new anchor.Program<Favorites>(IDL, provider);
+
+  // Here's what we want to write to the blockchain
+  const favoriteNumber = new anchor.BN(23);
+  const favoriteColor = 'purple';
+  const favoriteHobbies = ['skiing', 'skydiving', 'biking'];
+
+  // We don't need to airdrop if we're using the local cluster
+  // because the local cluster gives us 1,000,000 SOL
+  const balance = await context.banksClient.getBalance(user.publicKey);
+  const balanceInSOL = balance / BigInt(web3.LAMPORTS_PER_SOL);
+  const formattedBalance = new Intl.NumberFormat().format(balanceInSOL);
+  console.log(`Balance: ${formattedBalance} SOL`);
+
+  it('Writes our favorites to the blockchain', async () => {
+    await program.methods
+      // set_favourites in Rust becomes setFavorites in TypeScript
+      .setFavorites(favoriteNumber, favoriteColor, favoriteHobbies)
+      // Sign the transaction
+      .signers([user])
+      // Send the transaction to the cluster or RPC
+      .rpc();
+
+    // Find the PDA for the user's favorites
+    const favoritesPdaAndBump = web3.PublicKey.findProgramAddressSync([Buffer.from('favorites'), user.publicKey.toBuffer()], program.programId);
+    const favoritesPda = favoritesPdaAndBump[0];
+    const dataFromPda = await program.account.favorites.fetch(favoritesPda);
+    // And make sure it matches!
+    assert.equal(dataFromPda.color, favoriteColor);
+    // A little extra work to make sure the BNs are equal
+    assert.equal(dataFromPda.number.toString(), favoriteNumber.toString());
+    // And check the hobbies too
+    assert.deepEqual(dataFromPda.hobbies, favoriteHobbies);
+  });
+
+  it('Updates the favorites', async () => {
+    const newFavoriteHobbies = ['skiing', 'skydiving', 'biking', 'swimming'];
+    try {
+      await program.methods.setFavorites(favoriteNumber, favoriteColor, newFavoriteHobbies).signers([user]).rpc();
+    } catch (error) {
+      console.error((error as Error).message);
+      const customErrorMessage = getCustomErrorMessage(systemProgramErrors, error);
+      throw new Error(customErrorMessage);
+    }
+  });
+
+  it('Rejects transactions from unauthorized signers', async () => {
+    try {
+      await program.methods
+        // set_favourites in Rust becomes setFavorites in TypeScript
+        .setFavorites(favoriteNumber, favoriteColor, favoriteHobbies)
+        // Sign the transaction
+        .signers([someRandomGuy])
+        // Send the transaction to the cluster or RPC
+        .rpc();
+    } catch (error) {
+      const errorMessage = (error as Error).message;
+      assert.isTrue(errorMessage.includes('unknown signer'));
+    }
+  });
+});

+ 0 - 0
basics/favorites/anchor/tests/favorites.ts → basics/favorites/anchor/tests/favorites.test.ts


+ 4 - 1
basics/hello-solana/anchor/package.json

@@ -1,8 +1,11 @@
 {
   "dependencies": {
-    "@coral-xyz/anchor": "^0.30.0"
+    "@coral-xyz/anchor": "^0.30.0",
+    "@solana/web3.js": "^1.95.2"
   },
   "devDependencies": {
+    "anchor-bankrun": "^0.4.0",
+    "solana-bankrun": "^0.3.0",
     "@types/bn.js": "^5.1.0",
     "@types/chai": "^4.3.0",
     "@types/mocha": "^9.0.0",

File diff suppressed because it is too large
+ 264 - 661
basics/hello-solana/anchor/pnpm-lock.yaml


+ 24 - 0
basics/hello-solana/anchor/tests/bankrun.test.ts

@@ -0,0 +1,24 @@
+import { describe, it } from 'node:test';
+import * as anchor from '@coral-xyz/anchor';
+import { PublicKey } from '@solana/web3.js';
+import { BankrunProvider } from 'anchor-bankrun';
+import { startAnchor } from 'solana-bankrun';
+import { HelloSolana } from '../target/types/hello_solana';
+
+const IDL = require('../target/idl/hello_solana.json');
+const PROGRAM_ID = new PublicKey(IDL.address);
+
+describe('hello-solana', async () => {
+  // Configure the Anchor provider & load the program IDL for anchor-bankrun
+  // The IDL gives you a typescript module
+  const context = await startAnchor('', [{ name: 'hello_solana', programId: PROGRAM_ID }], []);
+  const provider = new BankrunProvider(context);
+
+  const program = new anchor.Program<HelloSolana>(IDL, provider);
+
+  it('Say hello!', async () => {
+    // Just run Anchor's IDL method to build a transaction!
+    //
+    await program.methods.hello().accounts({}).rpc();
+  });
+});

+ 4 - 1
basics/pda-rent-payer/anchor/package.json

@@ -1,8 +1,11 @@
 {
   "dependencies": {
-    "@coral-xyz/anchor": "^0.30.0"
+    "@coral-xyz/anchor": "^0.30.0",
+    "@solana/web3.js": "^1.95.2"
   },
   "devDependencies": {
+    "anchor-bankrun": "^0.4.0",
+    "solana-bankrun": "^0.3.0",
     "@types/bn.js": "^5.1.0",
     "@types/chai": "^4.3.0",
     "@types/mocha": "^9.0.0",

File diff suppressed because it is too large
+ 264 - 661
basics/pda-rent-payer/anchor/pnpm-lock.yaml


+ 58 - 0
basics/pda-rent-payer/anchor/tests/bankrun.test.ts

@@ -0,0 +1,58 @@
+import { describe, it } from 'node:test';
+import * as anchor from '@coral-xyz/anchor';
+import { Keypair, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js';
+import { BankrunProvider } from 'anchor-bankrun';
+import { assert } from 'chai';
+import { startAnchor } from 'solana-bankrun';
+import type { PdaRentPayer } from '../target/types/pda_rent_payer';
+
+const IDL = require('../target/idl/pda_rent_payer.json');
+const PROGRAM_ID = new PublicKey(IDL.address);
+
+describe('PDA Rent-Payer', async () => {
+  const context = await startAnchor('', [{ name: 'pda_rent_payer', programId: PROGRAM_ID }], []);
+  const provider = new BankrunProvider(context);
+  const program = new anchor.Program<PdaRentPayer>(IDL, provider);
+
+  const wallet = provider.wallet as anchor.Wallet;
+  const connection = provider.connection;
+
+  // PDA for the Rent Vault
+  const [rentVaultPDA] = PublicKey.findProgramAddressSync([Buffer.from('rent_vault')], program.programId);
+
+  it('Initialize the Rent Vault', async () => {
+    // 1 SOL
+    const fundAmount = new anchor.BN(LAMPORTS_PER_SOL);
+
+    await program.methods
+      .initRentVault(fundAmount)
+      .accounts({
+        payer: wallet.publicKey,
+      })
+      .rpc();
+
+    // Check rent vault balance
+    const accountInfo = await program.provider.connection.getAccountInfo(rentVaultPDA);
+    assert(accountInfo.lamports === fundAmount.toNumber());
+  });
+
+  it('Create a new account using the Rent Vault', async () => {
+    // Generate a new keypair for the new account
+    const newAccount = new Keypair();
+
+    await program.methods
+      .createNewAccount()
+      .accounts({
+        newAccount: newAccount.publicKey,
+      })
+      .signers([newAccount])
+      .rpc();
+
+    // Minimum balance for rent exemption for new account
+    const lamports = await connection.getMinimumBalanceForRentExemption(0);
+
+    // Check that the account was created
+    const accountInfo = await connection.getAccountInfo(newAccount.publicKey);
+    assert(accountInfo.lamports === lamports);
+  });
+});

+ 0 - 2
basics/pda-rent-payer/anchor/tests/test.ts

@@ -21,7 +21,6 @@ describe('PDA Rent-Payer', () => {
       .initRentVault(fundAmount)
       .accounts({
         payer: wallet.publicKey,
-        rentVault: rentVaultPDA,
       })
       .rpc();
 
@@ -37,7 +36,6 @@ describe('PDA Rent-Payer', () => {
     await program.methods
       .createNewAccount()
       .accounts({
-        rentVault: rentVaultPDA,
         newAccount: newAccount.publicKey,
       })
       .signers([newAccount])

+ 4 - 1
basics/processing-instructions/anchor/package.json

@@ -1,8 +1,11 @@
 {
   "dependencies": {
-    "@coral-xyz/anchor": "^0.30.0"
+    "@coral-xyz/anchor": "^0.30.0",
+    "@solana/web3.js": "^1.95.2"
   },
   "devDependencies": {
+    "anchor-bankrun": "^0.4.0",
+    "solana-bankrun": "^0.3.0",
     "@types/bn.js": "^5.1.0",
     "@types/chai": "^4.3.0",
     "@types/mocha": "^9.0.0",

File diff suppressed because it is too large
+ 264 - 661
basics/processing-instructions/anchor/pnpm-lock.yaml


+ 23 - 0
basics/processing-instructions/anchor/tests/bankrun.test.ts

@@ -0,0 +1,23 @@
+import { describe, it } from 'node:test';
+import * as anchor from '@coral-xyz/anchor';
+import { PublicKey } from '@solana/web3.js';
+import { BankrunProvider } from 'anchor-bankrun';
+import { startAnchor } from 'solana-bankrun';
+import type { ProcessingInstructions } from '../target/types/processing_instructions';
+
+const IDL = require('../target/idl/processing_instructions.json');
+const PROGRAM_ID = new PublicKey(IDL.address);
+
+describe('custom-instruction-data', async () => {
+  const context = await startAnchor('', [{ name: 'processing_instructions', programId: PROGRAM_ID }], []);
+  const provider = new BankrunProvider(context);
+
+  const payer = provider.wallet as anchor.Wallet;
+  const program = new anchor.Program<ProcessingInstructions>(IDL, provider);
+
+  it('Go to the park!', async () => {
+    // Anchor makes it super simple.
+    await program.methods.goToPark('Jimmy', 3).accounts({}).rpc();
+    await program.methods.goToPark('Mary', 10).accounts({}).rpc();
+  });
+});

+ 4 - 1
basics/program-derived-addresses/anchor/package.json

@@ -1,8 +1,11 @@
 {
   "dependencies": {
-    "@coral-xyz/anchor": "^0.30.0"
+    "@coral-xyz/anchor": "^0.30.0",
+    "@solana/web3.js": "^1.95.2"
   },
   "devDependencies": {
+    "anchor-bankrun": "^0.4.0",
+    "solana-bankrun": "^0.3.0",
     "@types/bn.js": "^5.1.0",
     "@types/chai": "^4.3.0",
     "@types/mocha": "^9.0.0",

File diff suppressed because it is too large
+ 264 - 661
basics/program-derived-addresses/anchor/pnpm-lock.yaml


+ 53 - 0
basics/program-derived-addresses/anchor/tests/bankrun.test.ts

@@ -0,0 +1,53 @@
+import { describe, it } from 'node:test';
+import * as anchor from '@coral-xyz/anchor';
+import { PublicKey, Transaction } from '@solana/web3.js';
+import { BankrunProvider } from 'anchor-bankrun';
+import { startAnchor } from 'solana-bankrun';
+import type { AnchorProgramExample } from '../target/types/anchor_program_example';
+
+const IDL = require('../target/idl/anchor_program_example.json');
+const PROGRAM_ID = new PublicKey(IDL.address);
+
+describe('PDAs', async () => {
+  const context = await startAnchor('', [{ name: 'anchor_program_example', programId: PROGRAM_ID }], []);
+  const provider = new BankrunProvider(context);
+  const client = context.banksClient;
+
+  const payer = provider.wallet as anchor.Wallet;
+  const program = new anchor.Program<AnchorProgramExample>(IDL, provider);
+
+  // PDA for the page visits account
+  const [pageVisitPDA] = PublicKey.findProgramAddressSync([Buffer.from('page_visits'), payer.publicKey.toBuffer()], program.programId);
+
+  it('Create the page visits tracking PDA', async () => {
+    await program.methods
+      .createPageVisits()
+      .accounts({
+        payer: payer.publicKey,
+      })
+      .rpc();
+  });
+
+  it('Visit the page!', async () => {
+    await program.methods
+      .incrementPageVisits()
+      .accounts({
+        user: payer.publicKey,
+      })
+      .rpc();
+  });
+
+  it('Visit the page!', async () => {
+    await program.methods
+      .incrementPageVisits()
+      .accounts({
+        user: payer.publicKey,
+      })
+      .rpc();
+  });
+
+  it('View page visits', async () => {
+    const pageVisits = await program.account.pageVisits.fetch(pageVisitPDA);
+    console.log(`Number of page visits: ${pageVisits.pageVisits}`);
+  });
+});

+ 0 - 3
basics/program-derived-addresses/anchor/tests/test.ts

@@ -16,7 +16,6 @@ describe('PDAs', () => {
       .createPageVisits()
       .accounts({
         payer: payer.publicKey,
-        pageVisits: pageVisitPDA,
       })
       .rpc();
   });
@@ -26,7 +25,6 @@ describe('PDAs', () => {
       .incrementPageVisits()
       .accounts({
         user: payer.publicKey,
-        pageVisits: pageVisitPDA,
       })
       .rpc();
   });
@@ -36,7 +34,6 @@ describe('PDAs', () => {
       .incrementPageVisits()
       .accounts({
         user: payer.publicKey,
-        pageVisits: pageVisitPDA,
       })
       .rpc();
   });

+ 0 - 1
basics/program-derived-addresses/native/package.json

@@ -13,7 +13,6 @@
     "@types/bn.js": "^5.1.0",
     "@types/chai": "^4.3.1",
     "@types/mocha": "^9.1.1",
-    "chai": "^4.3.4",
     "mocha": "^9.0.3",
     "solana-bankrun": "^0.3.0",
     "ts-mocha": "^10.0.0",

File diff suppressed because it is too large
+ 182 - 607
basics/program-derived-addresses/native/pnpm-lock.yaml


+ 0 - 1
basics/program-derived-addresses/native/tests/test.ts

@@ -119,7 +119,6 @@ describe('PDAs', async () => {
     await client.processTransaction(tx);
   });
 
-  // commented because couldn't get different blockhash
   test('Visit the page!', async () => {
     const [pageVisitsPda, _] = derivePageVisitsPda(testUser.publicKey);
     const ix = new TransactionInstruction({

+ 1 - 1
basics/program-derived-addresses/native/tsconfig.json

@@ -1,6 +1,6 @@
 {
   "compilerOptions": {
-    "types": ["mocha", "chai"],
+    "types": ["mocha"],
     "typeRoots": ["./node_modules/@types"],
     "lib": ["es2015"],
     "module": "commonjs",

+ 9 - 6
basics/realloc/anchor/package.json

@@ -4,16 +4,19 @@
     "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
   },
   "dependencies": {
-    "@coral-xyz/anchor": "^0.30.0"
+    "@coral-xyz/anchor": "^0.30.0",
+    "@solana/web3.js": "^1.95.2"
   },
   "devDependencies": {
-    "chai": "^4.3.4",
-    "mocha": "^9.0.3",
-    "ts-mocha": "^10.0.0",
+    "anchor-bankrun": "^0.4.0",
+    "solana-bankrun": "^0.3.0",
     "@types/bn.js": "^5.1.0",
     "@types/chai": "^4.3.0",
     "@types/mocha": "^9.0.0",
-    "typescript": "^4.3.5",
-    "prettier": "^2.6.2"
+    "chai": "^4.4.1",
+    "mocha": "^9.0.3",
+    "prettier": "^2.6.2",
+    "ts-mocha": "^10.0.0",
+    "typescript": "^4.3.5"
   }
 }

File diff suppressed because it is too large
+ 268 - 666
basics/realloc/anchor/pnpm-lock.yaml


+ 79 - 0
basics/realloc/anchor/tests/bankrun.test.ts

@@ -0,0 +1,79 @@
+import { describe, it } from 'node:test';
+import * as anchor from '@coral-xyz/anchor';
+import type { Program } from '@coral-xyz/anchor';
+import { Keypair, PublicKey } from '@solana/web3.js';
+import { BankrunProvider } from 'anchor-bankrun';
+import { assert } from 'chai';
+import { startAnchor } from 'solana-bankrun';
+import type { AnchorRealloc } from '../target/types/anchor_realloc';
+
+const IDL = require('../target/idl/anchor_realloc.json');
+const PROGRAM_ID = new PublicKey(IDL.address);
+
+describe('anchor-realloc', async () => {
+  const context = await startAnchor('', [{ name: 'anchor_realloc', programId: PROGRAM_ID }], []);
+  const provider = new BankrunProvider(context);
+  const connection = provider.connection;
+  const payer = provider.wallet as anchor.Wallet;
+  const program = new anchor.Program<AnchorRealloc>(IDL, provider);
+
+  const messageAccount = new Keypair();
+
+  // helper function to check the account data and message
+  async function checkAccount(publicKey: PublicKey, expectedMessage: string) {
+    const accountInfo = await connection.getAccountInfo(publicKey);
+    const accountData = await program.account.message.fetch(publicKey);
+
+    // 8 bytes for the discriminator,
+    // 4 bytes for the length of the message,
+    // and the length of the message
+    assert.equal(accountInfo.data.length, 8 + 4 + expectedMessage.length);
+    assert.equal(accountData.message, expectedMessage);
+
+    console.log(`Account Data Length: ${accountInfo.data.length}`);
+    console.log(`Message: ${accountData.message}`);
+  }
+
+  it('Is initialized!', async () => {
+    const input = 'hello';
+
+    await program.methods
+      .initialize(input)
+      .accounts({
+        payer: payer.publicKey,
+        messageAccount: messageAccount.publicKey,
+      })
+      .signers([messageAccount])
+      .rpc();
+
+    await checkAccount(messageAccount.publicKey, input);
+  });
+
+  it('Update', async () => {
+    const input = 'hello world';
+
+    await program.methods
+      .update(input)
+      .accounts({
+        payer: payer.publicKey,
+        messageAccount: messageAccount.publicKey,
+      })
+      .rpc();
+
+    await checkAccount(messageAccount.publicKey, input);
+  });
+
+  it('Update', async () => {
+    const input = 'hi';
+
+    await program.methods
+      .update(input)
+      .accounts({
+        payer: payer.publicKey,
+        messageAccount: messageAccount.publicKey,
+      })
+      .rpc();
+
+    await checkAccount(messageAccount.publicKey, input);
+  });
+});

+ 4 - 1
basics/rent/anchor/package.json

@@ -1,8 +1,11 @@
 {
   "dependencies": {
-    "@coral-xyz/anchor": "^0.30.0"
+    "@coral-xyz/anchor": "^0.30.0",
+    "@solana/web3.js": "^1.95.2"
   },
   "devDependencies": {
+    "anchor-bankrun": "^0.4.0",
+    "solana-bankrun": "^0.3.0",
     "@types/bn.js": "^5.1.0",
     "@types/chai": "^4.3.0",
     "@types/mocha": "^9.0.0",

File diff suppressed because it is too large
+ 264 - 661
basics/rent/anchor/pnpm-lock.yaml


+ 42 - 0
basics/rent/anchor/tests/bankrun.test.ts

@@ -0,0 +1,42 @@
+import { describe, it } from 'node:test';
+import * as anchor from '@coral-xyz/anchor';
+import { PublicKey } from '@solana/web3.js';
+import { BankrunProvider } from 'anchor-bankrun';
+import { startAnchor } from 'solana-bankrun';
+import Idl from '../target/idl/rent_example.json';
+import type { RentExample } from '../target/types/rent_example';
+
+const IDL = require('../target/idl/rent_example.json');
+const PROGRAM_ID = new PublicKey(IDL.address);
+
+describe('Bankrun example', async () => {
+  const context = await startAnchor('', [{ name: 'rent_example', programId: PROGRAM_ID }], []);
+  const provider = new BankrunProvider(context);
+
+  const wallet = provider.wallet as anchor.Wallet;
+  const program = new anchor.Program<RentExample>(IDL, provider);
+
+  it('Create the account', async () => {
+    const newKeypair = anchor.web3.Keypair.generate();
+
+    const addressData: anchor.IdlTypes<RentExample>['addressData'] = {
+      name: 'Marcus',
+      address: '123 Main St. San Francisco, CA',
+    };
+
+    // We're just going to serialize our object here so we can check
+    //  the size on the client side against the program logs
+    //
+    const addressDataBuffer = new anchor.BorshCoder(Idl as anchor.Idl).types.encode('AddressData', addressData);
+    console.log(`Address data buffer length: ${addressDataBuffer.length}`);
+
+    await program.methods
+      .createSystemAccount(addressData)
+      .accounts({
+        payer: wallet.publicKey,
+        newAccount: newKeypair.publicKey,
+      })
+      .signers([wallet.payer, newKeypair])
+      .rpc();
+  });
+});

+ 4 - 1
basics/repository-layout/anchor/package.json

@@ -1,8 +1,11 @@
 {
   "dependencies": {
-    "@coral-xyz/anchor": "^0.30.0"
+    "@coral-xyz/anchor": "^0.30.0",
+    "@solana/web3.js": "^1.95.2"
   },
   "devDependencies": {
+    "anchor-bankrun": "^0.4.0",
+    "solana-bankrun": "^0.3.0",
     "@types/bn.js": "^5.1.0",
     "@types/chai": "^4.3.0",
     "@types/mocha": "^9.0.0",

File diff suppressed because it is too large
+ 264 - 661
basics/repository-layout/anchor/pnpm-lock.yaml


+ 51 - 0
basics/repository-layout/anchor/tests/bankrun.test.ts

@@ -0,0 +1,51 @@
+import { describe, it } from 'node:test';
+import * as anchor from '@coral-xyz/anchor';
+import { PublicKey, Transaction, sendAndConfirmTransaction } from '@solana/web3.js';
+import { BankrunProvider } from 'anchor-bankrun';
+import { startAnchor } from 'solana-bankrun';
+import type { Carnival } from '../target/types/carnival';
+
+const IDL = require('../target/idl/carnival.json');
+const PROGRAM_ID = new PublicKey(IDL.address);
+
+describe('Bankrun example', async () => {
+  const context = await startAnchor('', [{ name: 'carnival', programId: PROGRAM_ID }], []);
+  const provider = new BankrunProvider(context);
+  const wallet = provider.wallet as anchor.Wallet;
+  const program = new anchor.Program<Carnival>(IDL, provider);
+
+  async function sendCarnivalInstructions(instructionsList: anchor.web3.TransactionInstruction[]) {
+    const tx = new Transaction();
+    for (const ix of instructionsList) {
+      tx.add(ix);
+    }
+    await provider.sendAndConfirm(tx, [wallet.payer]);
+  }
+
+  it('Go on some rides!', async () => {
+    await sendCarnivalInstructions([
+      await program.methods.goOnRide('Jimmy', 36, 15, 'Scrambler').instruction(),
+      await program.methods.goOnRide('Mary', 52, 1, 'Ferris Wheel').instruction(),
+      await program.methods.goOnRide('Alice', 56, 15, 'Scrambler').instruction(),
+      await program.methods.goOnRide('Bob', 49, 6, 'Tilt-a-Whirl').instruction(),
+    ]);
+  });
+
+  it('Play some games!', async () => {
+    await sendCarnivalInstructions([
+      await program.methods.playGame('Jimmy', 15, 'I Got It!').instruction(),
+      await program.methods.playGame('Mary', 1, 'Ring Toss').instruction(),
+      await program.methods.playGame('Alice', 15, 'Ladder Climb').instruction(),
+      await program.methods.playGame('Bob', 6, 'Ring Toss').instruction(),
+    ]);
+  });
+
+  it('Eat some food!', async () => {
+    await sendCarnivalInstructions([
+      await program.methods.eatFood('Jimmy', 15, 'Taco Shack').instruction(),
+      await program.methods.eatFood('Mary', 1, "Larry's Pizza").instruction(),
+      await program.methods.eatFood('Alice', 15, "Dough Boy's").instruction(),
+      await program.methods.eatFood('Bob', 6, "Dough Boy's").instruction(),
+    ]);
+  });
+});

+ 4 - 1
basics/transfer-sol/anchor/package.json

@@ -1,8 +1,11 @@
 {
   "dependencies": {
-    "@coral-xyz/anchor": "^0.30.0"
+    "@coral-xyz/anchor": "^0.30.0",
+    "@solana/web3.js": "^1.95.2"
   },
   "devDependencies": {
+    "anchor-bankrun": "^0.4.0",
+    "solana-bankrun": "^0.3.0",
     "@types/bn.js": "^5.1.0",
     "@types/chai": "^4.3.0",
     "@types/mocha": "^9.0.0",

File diff suppressed because it is too large
+ 264 - 661
basics/transfer-sol/anchor/pnpm-lock.yaml


+ 75 - 0
basics/transfer-sol/anchor/tests/bankrun.test.ts

@@ -0,0 +1,75 @@
+import { describe, it } from 'node:test';
+import * as anchor from '@coral-xyz/anchor';
+import { Keypair, LAMPORTS_PER_SOL, PublicKey, SystemProgram, Transaction, sendAndConfirmTransaction } from '@solana/web3.js';
+import { BankrunProvider } from 'anchor-bankrun';
+import { startAnchor } from 'solana-bankrun';
+import type { TransferSol } from '../target/types/transfer_sol';
+
+const IDL = require('../target/idl/transfer_sol.json');
+const PROGRAM_ID = new PublicKey(IDL.address);
+
+describe('Bankrun example', async () => {
+  const context = await startAnchor('', [{ name: 'transfer_sol', programId: PROGRAM_ID }], []);
+  const provider = new BankrunProvider(context);
+  const payer = provider.wallet as anchor.Wallet;
+  const program = new anchor.Program<TransferSol>(IDL, provider);
+
+  // 1 SOL
+  const transferAmount = 1 * LAMPORTS_PER_SOL;
+
+  // Generate a new keypair for the recipient
+  const recipient = new Keypair();
+
+  // Generate a new keypair to create an account owned by our program
+  const programOwnedAccount = new Keypair();
+
+  it('Transfer SOL with CPI', async () => {
+    await getBalances(payer.publicKey, recipient.publicKey, 'Beginning');
+
+    await program.methods
+      .transferSolWithCpi(new anchor.BN(transferAmount))
+      .accounts({
+        payer: payer.publicKey,
+        recipient: recipient.publicKey,
+      })
+      .rpc();
+
+    await getBalances(payer.publicKey, recipient.publicKey, 'Resulting');
+  });
+
+  it('Create and fund account owned by our program', async () => {
+    const instruction = SystemProgram.createAccount({
+      fromPubkey: payer.publicKey,
+      newAccountPubkey: programOwnedAccount.publicKey,
+      space: 0,
+      lamports: 1 * LAMPORTS_PER_SOL, // 1 SOL
+      programId: program.programId, // Program Owner, our program's address
+    });
+
+    const transaction = new Transaction().add(instruction);
+
+    await sendAndConfirmTransaction(provider.connection, transaction, [payer.payer, programOwnedAccount]);
+  });
+
+  it('Transfer SOL with Program', async () => {
+    await getBalances(programOwnedAccount.publicKey, payer.publicKey, 'Beginning');
+
+    await program.methods
+      .transferSolWithProgram(new anchor.BN(transferAmount))
+      .accounts({
+        payer: programOwnedAccount.publicKey,
+        recipient: payer.publicKey,
+      })
+      .rpc();
+
+    await getBalances(programOwnedAccount.publicKey, payer.publicKey, 'Resulting');
+  });
+
+  async function getBalances(payerPubkey: PublicKey, recipientPubkey: PublicKey, timeframe: string) {
+    const payerBalance = await provider.connection.getBalance(payerPubkey);
+    const recipientBalance = await provider.connection.getBalance(recipientPubkey);
+    console.log(`${timeframe} balances:`);
+    console.log(`   Payer: ${payerBalance / LAMPORTS_PER_SOL}`);
+    console.log(`   Recipient: ${recipientBalance / LAMPORTS_PER_SOL}`);
+  }
+});

+ 1 - 0
basics/transfer-sol/anchor/tests/test.ts

@@ -1,6 +1,7 @@
 import * as anchor from '@coral-xyz/anchor';
 import { Keypair, LAMPORTS_PER_SOL, type PublicKey, SystemProgram, Transaction, sendAndConfirmTransaction } from '@solana/web3.js';
 import type { TransferSol } from '../target/types/transfer_sol';
+
 describe('transfer-sol', () => {
   const provider = anchor.AnchorProvider.env();
   anchor.setProvider(provider);

+ 1 - 1
biome.json

@@ -52,6 +52,6 @@
   },
   "files": {
     "ignore": ["node_modules"],
-    "include": ["**/*.ts", "**/*.js", "**/*.json"]
+    "include": ["**/*.ts", "**/*.js", "**/*.json", "**/*.mjs"]
   }
 }

+ 7 - 0
package.json

@@ -24,5 +24,12 @@
     "picocolors": "^1.0.0",
     "ts-node": "^10.9.1",
     "typescript": "^5.2.2"
+  },
+  "dependencies": {
+    "@coral-xyz/anchor": "^0.30.1",
+    "@solana/web3.js": "^1.95.2",
+    "anchor-bankrun": "^0.4.0",
+    "chai": "^5.1.1",
+    "solana-bankrun": "^0.3.0"
   }
 }

+ 675 - 0
pnpm-lock.yaml

@@ -7,6 +7,22 @@ settings:
 importers:
 
   .:
+    dependencies:
+      '@coral-xyz/anchor':
+        specifier: ^0.30.1
+        version: 0.30.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+      '@solana/web3.js':
+        specifier: ^1.95.2
+        version: 1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+      anchor-bankrun:
+        specifier: ^0.4.0
+        version: 0.4.0(@coral-xyz/anchor@0.30.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(solana-bankrun@0.3.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))
+      chai:
+        specifier: ^5.1.1
+        version: 5.1.1
+      solana-bankrun:
+        specifier: ^0.3.0
+        version: 0.3.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
     devDependencies:
       '@biomejs/biome':
         specifier: 1.8.1
@@ -29,6 +45,10 @@ importers:
 
 packages:
 
+  '@babel/runtime@7.25.0':
+    resolution: {integrity: sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==}
+    engines: {node: '>=6.9.0'}
+
   '@biomejs/biome@1.8.1':
     resolution: {integrity: sha512-fQXGfvq6DIXem12dGQCM2tNF+vsNHH1qs3C7WeOu75Pd0trduoTmoO7G4ntLJ2qDs5wuw981H+cxQhi1uHnAtA==}
     engines: {node: '>=14.21.3'}
@@ -82,6 +102,20 @@ packages:
     cpu: [x64]
     os: [win32]
 
+  '@coral-xyz/anchor-errors@0.30.1':
+    resolution: {integrity: sha512-9Mkradf5yS5xiLWrl9WrpjqOrAV+/W2RQHDlbnAZBivoGpOs1ECjoDCkVk4aRG8ZdiFiB8zQEVlxf+8fKkmSfQ==}
+    engines: {node: '>=10'}
+
+  '@coral-xyz/anchor@0.30.1':
+    resolution: {integrity: sha512-gDXFoF5oHgpriXAaLpxyWBHdCs8Awgf/gLHIo6crv7Aqm937CNdY+x+6hoj7QR5vaJV7MxWSQ0NGFzL3kPbWEQ==}
+    engines: {node: '>=11'}
+
+  '@coral-xyz/borsh@0.30.1':
+    resolution: {integrity: sha512-aaxswpPrCFKl8vZTbxLssA2RvwX2zmKLlRCIktJOwW+VpVwYtXRtlWiIP+c2pPRKneiTiWCN2GEMSH9j1zTlWQ==}
+    engines: {node: '>=10'}
+    peerDependencies:
+      '@solana/web3.js': ^1.68.0
+
   '@cspotcode/source-map-support@0.8.1':
     resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
     engines: {node: '>=12'}
@@ -96,6 +130,23 @@ packages:
   '@jridgewell/trace-mapping@0.3.9':
     resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
 
+  '@noble/curves@1.4.2':
+    resolution: {integrity: sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==}
+
+  '@noble/hashes@1.4.0':
+    resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==}
+    engines: {node: '>= 16'}
+
+  '@solana/buffer-layout@4.0.1':
+    resolution: {integrity: sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==}
+    engines: {node: '>=5.10'}
+
+  '@solana/web3.js@1.95.2':
+    resolution: {integrity: sha512-SjlHp0G4qhuhkQQc+YXdGkI8EerCqwxvgytMgBpzMUQTafrkNant3e7pgilBGgjy/iM40ICvWBLgASTPMrQU7w==}
+
+  '@swc/helpers@0.5.12':
+    resolution: {integrity: sha512-KMZNXiGibsW9kvZAO1Pam2JPTDBm+KSHMMHWdsyI/1DbIZjT2A6Gy3hblVXUMEDvUAKq+e0vL0X0o54owWji7g==}
+
   '@tsconfig/node10@1.0.11':
     resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
 
@@ -108,9 +159,28 @@ packages:
   '@tsconfig/node16@1.0.4':
     resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
 
+  '@types/connect@3.4.38':
+    resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
+
+  '@types/node@12.20.55':
+    resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==}
+
   '@types/node@20.14.2':
     resolution: {integrity: sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==}
 
+  '@types/uuid@8.3.4':
+    resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==}
+
+  '@types/ws@7.4.7':
+    resolution: {integrity: sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==}
+
+  '@types/ws@8.5.12':
+    resolution: {integrity: sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==}
+
+  JSONStream@1.3.5:
+    resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==}
+    hasBin: true
+
   acorn-walk@8.3.2:
     resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==}
     engines: {node: '>=0.4.0'}
@@ -120,27 +190,253 @@ packages:
     engines: {node: '>=0.4.0'}
     hasBin: true
 
+  agentkeepalive@4.5.0:
+    resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==}
+    engines: {node: '>= 8.0.0'}
+
+  anchor-bankrun@0.4.0:
+    resolution: {integrity: sha512-s+K7E0IGAlmkhuo8nbiqVsQf2yJ+3l9GjNQJSmkRDe25dQj4Yef9rJh77FH6EQ5H6yQYfzuhgm/5GD6JMjdTZg==}
+    engines: {node: '>= 10'}
+    peerDependencies:
+      '@coral-xyz/anchor': ^0.30.0
+      '@solana/web3.js': ^1.78.4
+      solana-bankrun: ^0.2.0
+
   arg@4.1.3:
     resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
 
+  assertion-error@2.0.1:
+    resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
+    engines: {node: '>=12'}
+
+  base-x@3.0.10:
+    resolution: {integrity: sha512-7d0s06rR9rYaIWHkpfLIFICM/tkSVdoPC9qYAQRpxn9DdKNWNsKC0uk++akckyLq16Tx2WIinnZ6WRriAt6njQ==}
+
+  base64-js@1.5.1:
+    resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
+
+  bigint-buffer@1.1.5:
+    resolution: {integrity: sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==}
+    engines: {node: '>= 10.0.0'}
+
+  bindings@1.5.0:
+    resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
+
+  bn.js@5.2.1:
+    resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==}
+
+  borsh@0.7.0:
+    resolution: {integrity: sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==}
+
+  bs58@4.0.1:
+    resolution: {integrity: sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==}
+
+  buffer-layout@1.2.2:
+    resolution: {integrity: sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA==}
+    engines: {node: '>=4.5'}
+
+  buffer@6.0.3:
+    resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
+
+  bufferutil@4.0.8:
+    resolution: {integrity: sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==}
+    engines: {node: '>=6.14.2'}
+
+  camelcase@6.3.0:
+    resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
+    engines: {node: '>=10'}
+
+  chai@5.1.1:
+    resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==}
+    engines: {node: '>=12'}
+
+  check-error@2.1.1:
+    resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
+    engines: {node: '>= 16'}
+
+  commander@2.20.3:
+    resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
+
   create-require@1.1.1:
     resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
 
+  cross-fetch@3.1.8:
+    resolution: {integrity: sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==}
+
+  crypto-hash@1.3.0:
+    resolution: {integrity: sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg==}
+    engines: {node: '>=8'}
+
+  deep-eql@5.0.2:
+    resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
+    engines: {node: '>=6'}
+
+  delay@5.0.0:
+    resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==}
+    engines: {node: '>=10'}
+
   diff@4.0.2:
     resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
     engines: {node: '>=0.3.1'}
 
+  dot-case@3.0.4:
+    resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
+
+  es6-promise@4.2.8:
+    resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==}
+
+  es6-promisify@5.0.0:
+    resolution: {integrity: sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==}
+
+  eventemitter3@4.0.7:
+    resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
+
+  eventemitter3@5.0.1:
+    resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
+
+  eyes@0.1.8:
+    resolution: {integrity: sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==}
+    engines: {node: '> 0.1.90'}
+
+  fast-stable-stringify@1.0.0:
+    resolution: {integrity: sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==}
+
+  file-uri-to-path@1.0.0:
+    resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
+
+  get-func-name@2.0.2:
+    resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
+
+  humanize-ms@1.2.1:
+    resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==}
+
   husky@9.0.11:
     resolution: {integrity: sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==}
     engines: {node: '>=18'}
     hasBin: true
 
+  ieee754@1.2.1:
+    resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
+
+  isomorphic-ws@4.0.1:
+    resolution: {integrity: sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==}
+    peerDependencies:
+      ws: '*'
+
+  jayson@4.1.1:
+    resolution: {integrity: sha512-5ZWm4Q/0DHPyeMfAsrwViwUS2DMVsQgWh8bEEIVTkfb3DzHZ2L3G5WUnF+AKmGjjM9r1uAv73SaqC1/U4RL45w==}
+    engines: {node: '>=8'}
+    hasBin: true
+
+  json-stringify-safe@5.0.1:
+    resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
+
+  jsonparse@1.3.1:
+    resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==}
+    engines: {'0': node >= 0.2.0}
+
+  loupe@3.1.1:
+    resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==}
+
+  lower-case@2.0.2:
+    resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
+
   make-error@1.3.6:
     resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
 
+  ms@2.1.3:
+    resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+  no-case@3.0.4:
+    resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
+
+  node-fetch@2.7.0:
+    resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
+    engines: {node: 4.x || >=6.0.0}
+    peerDependencies:
+      encoding: ^0.1.0
+    peerDependenciesMeta:
+      encoding:
+        optional: true
+
+  node-gyp-build@4.8.1:
+    resolution: {integrity: sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==}
+    hasBin: true
+
+  pako@2.1.0:
+    resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==}
+
+  pathval@2.0.0:
+    resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==}
+    engines: {node: '>= 14.16'}
+
   picocolors@1.0.1:
     resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
 
+  regenerator-runtime@0.14.1:
+    resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
+
+  rpc-websockets@9.0.2:
+    resolution: {integrity: sha512-YzggvfItxMY3Lwuax5rC18inhbjJv9Py7JXRHxTIi94JOLrqBsSsUUc5bbl5W6c11tXhdfpDPK0KzBhoGe8jjw==}
+
+  safe-buffer@5.2.1:
+    resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+
+  snake-case@3.0.4:
+    resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==}
+
+  solana-bankrun-darwin-arm64@0.3.0:
+    resolution: {integrity: sha512-+NbDncf0U6l3knuacRBiqpjZ2DSp+5lZaAU518gH7/x6qubbui/d000STaIBK+uNTPBS/AL/bCN+7PkXqmA3lA==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [darwin]
+
+  solana-bankrun-darwin-universal@0.3.0:
+    resolution: {integrity: sha512-1/F0xdMa4qvc5o6z16FCCbZ5jbdvKvxpx5kyPcMWRiRPwyvi+zltMxciPAYMlg3wslQqGz88uFhrBEzq2eTumQ==}
+    engines: {node: '>= 10'}
+    os: [darwin]
+
+  solana-bankrun-darwin-x64@0.3.0:
+    resolution: {integrity: sha512-U6CANjkmMl+lgNA7UH0GKs5V7LtVIUDzJBZefGGqLfqUNv3EjA/PrrToM0hAOWJgkxSwdz6zW+p5sw5FmnbXtg==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [darwin]
+
+  solana-bankrun-linux-x64-gnu@0.3.0:
+    resolution: {integrity: sha512-qJSkCFs0k2n4XtTnyxGMiZsuqO2TiqTYgWjQ+3mZhGNUAMys/Vq8bd7/SyBm6RR7EfVuRXRxZvh+F8oKZ77V4w==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [linux]
+
+  solana-bankrun-linux-x64-musl@0.3.0:
+    resolution: {integrity: sha512-xsS2CS2xb1Sw4ivNXM0gPz/qpW9BX0neSvt/pnok5L330Nu9xlTnKAY8FhzzqOP9P9sJlGRM787Y6d0yYwt6xQ==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [linux]
+
+  solana-bankrun@0.3.0:
+    resolution: {integrity: sha512-YkH7sa8TB/AoRPzG17CXJtYsRIQHEkEqGLz1Vwc13taXhDBkjO7z6NI5JYw7n0ybRymDHwMYTc7sd+5J40TyVQ==}
+    engines: {node: '>= 10'}
+
+  superstruct@0.15.5:
+    resolution: {integrity: sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ==}
+
+  superstruct@2.0.2:
+    resolution: {integrity: sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==}
+    engines: {node: '>=14.0.0'}
+
+  text-encoding-utf-8@1.0.2:
+    resolution: {integrity: sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==}
+
+  through@2.3.8:
+    resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
+
+  toml@3.0.0:
+    resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==}
+
+  tr46@0.0.3:
+    resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
+
   ts-node@10.9.2:
     resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
     hasBin: true
@@ -155,6 +451,9 @@ packages:
       '@swc/wasm':
         optional: true
 
+  tslib@2.6.3:
+    resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
+
   typescript@5.4.5:
     resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==}
     engines: {node: '>=14.17'}
@@ -163,15 +462,57 @@ packages:
   undici-types@5.26.5:
     resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
 
+  utf-8-validate@5.0.10:
+    resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==}
+    engines: {node: '>=6.14.2'}
+
+  uuid@8.3.2:
+    resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
+    hasBin: true
+
   v8-compile-cache-lib@3.0.1:
     resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
 
+  webidl-conversions@3.0.1:
+    resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
+
+  whatwg-url@5.0.0:
+    resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
+
+  ws@7.5.10:
+    resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==}
+    engines: {node: '>=8.3.0'}
+    peerDependencies:
+      bufferutil: ^4.0.1
+      utf-8-validate: ^5.0.2
+    peerDependenciesMeta:
+      bufferutil:
+        optional: true
+      utf-8-validate:
+        optional: true
+
+  ws@8.18.0:
+    resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==}
+    engines: {node: '>=10.0.0'}
+    peerDependencies:
+      bufferutil: ^4.0.1
+      utf-8-validate: '>=5.0.2'
+    peerDependenciesMeta:
+      bufferutil:
+        optional: true
+      utf-8-validate:
+        optional: true
+
   yn@3.1.1:
     resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
     engines: {node: '>=6'}
 
 snapshots:
 
+  '@babel/runtime@7.25.0':
+    dependencies:
+      regenerator-runtime: 0.14.1
+
   '@biomejs/biome@1.8.1':
     optionalDependencies:
       '@biomejs/cli-darwin-arm64': 1.8.1
@@ -207,6 +548,36 @@ snapshots:
   '@biomejs/cli-win32-x64@1.8.1':
     optional: true
 
+  '@coral-xyz/anchor-errors@0.30.1': {}
+
+  '@coral-xyz/anchor@0.30.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)':
+    dependencies:
+      '@coral-xyz/anchor-errors': 0.30.1
+      '@coral-xyz/borsh': 0.30.1(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))
+      '@noble/hashes': 1.4.0
+      '@solana/web3.js': 1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+      bn.js: 5.2.1
+      bs58: 4.0.1
+      buffer-layout: 1.2.2
+      camelcase: 6.3.0
+      cross-fetch: 3.1.8
+      crypto-hash: 1.3.0
+      eventemitter3: 4.0.7
+      pako: 2.1.0
+      snake-case: 3.0.4
+      superstruct: 0.15.5
+      toml: 3.0.0
+    transitivePeerDependencies:
+      - bufferutil
+      - encoding
+      - utf-8-validate
+
+  '@coral-xyz/borsh@0.30.1(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))':
+    dependencies:
+      '@solana/web3.js': 1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+      bn.js: 5.2.1
+      buffer-layout: 1.2.2
+
   '@cspotcode/source-map-support@0.8.1':
     dependencies:
       '@jridgewell/trace-mapping': 0.3.9
@@ -220,6 +591,42 @@ snapshots:
       '@jridgewell/resolve-uri': 3.1.2
       '@jridgewell/sourcemap-codec': 1.4.15
 
+  '@noble/curves@1.4.2':
+    dependencies:
+      '@noble/hashes': 1.4.0
+
+  '@noble/hashes@1.4.0': {}
+
+  '@solana/buffer-layout@4.0.1':
+    dependencies:
+      buffer: 6.0.3
+
+  '@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)':
+    dependencies:
+      '@babel/runtime': 7.25.0
+      '@noble/curves': 1.4.2
+      '@noble/hashes': 1.4.0
+      '@solana/buffer-layout': 4.0.1
+      agentkeepalive: 4.5.0
+      bigint-buffer: 1.1.5
+      bn.js: 5.2.1
+      borsh: 0.7.0
+      bs58: 4.0.1
+      buffer: 6.0.3
+      fast-stable-stringify: 1.0.0
+      jayson: 4.1.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+      node-fetch: 2.7.0
+      rpc-websockets: 9.0.2
+      superstruct: 2.0.2
+    transitivePeerDependencies:
+      - bufferutil
+      - encoding
+      - utf-8-validate
+
+  '@swc/helpers@0.5.12':
+    dependencies:
+      tslib: 2.6.3
+
   '@tsconfig/node10@1.0.11': {}
 
   '@tsconfig/node12@1.0.11': {}
@@ -228,26 +635,268 @@ snapshots:
 
   '@tsconfig/node16@1.0.4': {}
 
+  '@types/connect@3.4.38':
+    dependencies:
+      '@types/node': 20.14.2
+
+  '@types/node@12.20.55': {}
+
   '@types/node@20.14.2':
     dependencies:
       undici-types: 5.26.5
 
+  '@types/uuid@8.3.4': {}
+
+  '@types/ws@7.4.7':
+    dependencies:
+      '@types/node': 20.14.2
+
+  '@types/ws@8.5.12':
+    dependencies:
+      '@types/node': 20.14.2
+
+  JSONStream@1.3.5:
+    dependencies:
+      jsonparse: 1.3.1
+      through: 2.3.8
+
   acorn-walk@8.3.2: {}
 
   acorn@8.11.3: {}
 
+  agentkeepalive@4.5.0:
+    dependencies:
+      humanize-ms: 1.2.1
+
+  anchor-bankrun@0.4.0(@coral-xyz/anchor@0.30.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(solana-bankrun@0.3.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)):
+    dependencies:
+      '@coral-xyz/anchor': 0.30.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+      solana-bankrun: 0.3.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+
   arg@4.1.3: {}
 
+  assertion-error@2.0.1: {}
+
+  base-x@3.0.10:
+    dependencies:
+      safe-buffer: 5.2.1
+
+  base64-js@1.5.1: {}
+
+  bigint-buffer@1.1.5:
+    dependencies:
+      bindings: 1.5.0
+
+  bindings@1.5.0:
+    dependencies:
+      file-uri-to-path: 1.0.0
+
+  bn.js@5.2.1: {}
+
+  borsh@0.7.0:
+    dependencies:
+      bn.js: 5.2.1
+      bs58: 4.0.1
+      text-encoding-utf-8: 1.0.2
+
+  bs58@4.0.1:
+    dependencies:
+      base-x: 3.0.10
+
+  buffer-layout@1.2.2: {}
+
+  buffer@6.0.3:
+    dependencies:
+      base64-js: 1.5.1
+      ieee754: 1.2.1
+
+  bufferutil@4.0.8:
+    dependencies:
+      node-gyp-build: 4.8.1
+    optional: true
+
+  camelcase@6.3.0: {}
+
+  chai@5.1.1:
+    dependencies:
+      assertion-error: 2.0.1
+      check-error: 2.1.1
+      deep-eql: 5.0.2
+      loupe: 3.1.1
+      pathval: 2.0.0
+
+  check-error@2.1.1: {}
+
+  commander@2.20.3: {}
+
   create-require@1.1.1: {}
 
+  cross-fetch@3.1.8:
+    dependencies:
+      node-fetch: 2.7.0
+    transitivePeerDependencies:
+      - encoding
+
+  crypto-hash@1.3.0: {}
+
+  deep-eql@5.0.2: {}
+
+  delay@5.0.0: {}
+
   diff@4.0.2: {}
 
+  dot-case@3.0.4:
+    dependencies:
+      no-case: 3.0.4
+      tslib: 2.6.3
+
+  es6-promise@4.2.8: {}
+
+  es6-promisify@5.0.0:
+    dependencies:
+      es6-promise: 4.2.8
+
+  eventemitter3@4.0.7: {}
+
+  eventemitter3@5.0.1: {}
+
+  eyes@0.1.8: {}
+
+  fast-stable-stringify@1.0.0: {}
+
+  file-uri-to-path@1.0.0: {}
+
+  get-func-name@2.0.2: {}
+
+  humanize-ms@1.2.1:
+    dependencies:
+      ms: 2.1.3
+
   husky@9.0.11: {}
 
+  ieee754@1.2.1: {}
+
+  isomorphic-ws@4.0.1(ws@7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10)):
+    dependencies:
+      ws: 7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+
+  jayson@4.1.1(bufferutil@4.0.8)(utf-8-validate@5.0.10):
+    dependencies:
+      '@types/connect': 3.4.38
+      '@types/node': 12.20.55
+      '@types/ws': 7.4.7
+      JSONStream: 1.3.5
+      commander: 2.20.3
+      delay: 5.0.0
+      es6-promisify: 5.0.0
+      eyes: 0.1.8
+      isomorphic-ws: 4.0.1(ws@7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10))
+      json-stringify-safe: 5.0.1
+      uuid: 8.3.2
+      ws: 7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+    transitivePeerDependencies:
+      - bufferutil
+      - utf-8-validate
+
+  json-stringify-safe@5.0.1: {}
+
+  jsonparse@1.3.1: {}
+
+  loupe@3.1.1:
+    dependencies:
+      get-func-name: 2.0.2
+
+  lower-case@2.0.2:
+    dependencies:
+      tslib: 2.6.3
+
   make-error@1.3.6: {}
 
+  ms@2.1.3: {}
+
+  no-case@3.0.4:
+    dependencies:
+      lower-case: 2.0.2
+      tslib: 2.6.3
+
+  node-fetch@2.7.0:
+    dependencies:
+      whatwg-url: 5.0.0
+
+  node-gyp-build@4.8.1:
+    optional: true
+
+  pako@2.1.0: {}
+
+  pathval@2.0.0: {}
+
   picocolors@1.0.1: {}
 
+  regenerator-runtime@0.14.1: {}
+
+  rpc-websockets@9.0.2:
+    dependencies:
+      '@swc/helpers': 0.5.12
+      '@types/uuid': 8.3.4
+      '@types/ws': 8.5.12
+      buffer: 6.0.3
+      eventemitter3: 5.0.1
+      uuid: 8.3.2
+      ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+    optionalDependencies:
+      bufferutil: 4.0.8
+      utf-8-validate: 5.0.10
+
+  safe-buffer@5.2.1: {}
+
+  snake-case@3.0.4:
+    dependencies:
+      dot-case: 3.0.4
+      tslib: 2.6.3
+
+  solana-bankrun-darwin-arm64@0.3.0:
+    optional: true
+
+  solana-bankrun-darwin-universal@0.3.0:
+    optional: true
+
+  solana-bankrun-darwin-x64@0.3.0:
+    optional: true
+
+  solana-bankrun-linux-x64-gnu@0.3.0:
+    optional: true
+
+  solana-bankrun-linux-x64-musl@0.3.0:
+    optional: true
+
+  solana-bankrun@0.3.0(bufferutil@4.0.8)(utf-8-validate@5.0.10):
+    dependencies:
+      '@solana/web3.js': 1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+      bs58: 4.0.1
+    optionalDependencies:
+      solana-bankrun-darwin-arm64: 0.3.0
+      solana-bankrun-darwin-universal: 0.3.0
+      solana-bankrun-darwin-x64: 0.3.0
+      solana-bankrun-linux-x64-gnu: 0.3.0
+      solana-bankrun-linux-x64-musl: 0.3.0
+    transitivePeerDependencies:
+      - bufferutil
+      - encoding
+      - utf-8-validate
+
+  superstruct@0.15.5: {}
+
+  superstruct@2.0.2: {}
+
+  text-encoding-utf-8@1.0.2: {}
+
+  through@2.3.8: {}
+
+  toml@3.0.0: {}
+
+  tr46@0.0.3: {}
+
   ts-node@10.9.2(@types/node@20.14.2)(typescript@5.4.5):
     dependencies:
       '@cspotcode/source-map-support': 0.8.1
@@ -266,10 +915,36 @@ snapshots:
       v8-compile-cache-lib: 3.0.1
       yn: 3.1.1
 
+  tslib@2.6.3: {}
+
   typescript@5.4.5: {}
 
   undici-types@5.26.5: {}
 
+  utf-8-validate@5.0.10:
+    dependencies:
+      node-gyp-build: 4.8.1
+    optional: true
+
+  uuid@8.3.2: {}
+
   v8-compile-cache-lib@3.0.1: {}
 
+  webidl-conversions@3.0.1: {}
+
+  whatwg-url@5.0.0:
+    dependencies:
+      tr46: 0.0.3
+      webidl-conversions: 3.0.1
+
+  ws@7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10):
+    optionalDependencies:
+      bufferutil: 4.0.8
+      utf-8-validate: 5.0.10
+
+  ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10):
+    optionalDependencies:
+      bufferutil: 4.0.8
+      utf-8-validate: 5.0.10
+
   yn@3.1.1: {}

+ 1 - 1
tokens/create-token/anchor/Anchor.toml

@@ -11,7 +11,7 @@ create_token = "GwvQ53QTu1xz3XXYfG5m5jEqwhMBvVBudPS8TUuFYnhT"
 url = "https://api.apr.dev"
 
 [provider]
-cluster = "devnet"
+cluster = "localnet"
 wallet = "~/.config/solana/id.json"
 
 [scripts]

+ 7 - 1
tokens/create-token/anchor/package.json

@@ -2,13 +2,19 @@
   "dependencies": {
     "@coral-xyz/anchor": "^0.30.0"
   },
+  "scripts": {
+    "postinstall": "zx prepare.mjs"
+  },
   "devDependencies": {
     "@types/bn.js": "^5.1.0",
     "@types/chai": "^4.3.0",
     "@types/mocha": "^9.0.0",
+    "anchor-bankrun": "^0.4.0",
     "chai": "^4.3.4",
     "mocha": "^9.0.3",
+    "solana-bankrun": "^0.3.0",
     "ts-mocha": "^10.0.0",
-    "typescript": "^4.3.5"
+    "typescript": "^4.3.5",
+    "zx": "^8.1.4"
   }
 }

File diff suppressed because it is too large
+ 249 - 653
tokens/create-token/anchor/pnpm-lock.yaml


+ 34 - 0
tokens/create-token/anchor/prepare.mjs

@@ -0,0 +1,34 @@
+#!/usr/bin/env zx
+
+import { mkdir, rm } from 'node:fs/promises';
+import { join } from 'node:path';
+import { $ } from 'zx';
+
+const programs = [
+  {
+    id: 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s',
+    name: 'token_metadata.so',
+  },
+];
+
+const outputDir = 'tests/fixtures';
+const overwrite = true;
+
+try {
+  for (const program of programs) {
+    const { id, name } = program;
+    const outputFile = join(outputDir, name);
+    await $`solana config set -um`;
+
+    try {
+      await mkdir(outputDir, { recursive: true });
+      if (overwrite) await rm(outputFile, { force: true });
+      await $`solana program dump ${id} ${outputFile}`;
+      console.log(`Program ${id} dumped to ${outputFile}`);
+    } catch (error) {
+      console.error(`Error dumping ${id}: ${error.message}`);
+    }
+  }
+} catch (error) {
+  console.error(`Error preparing programs: ${error.message}`);
+}

+ 69 - 0
tokens/create-token/anchor/tests/bankrun.test.ts

@@ -0,0 +1,69 @@
+import { describe, it } from 'node:test';
+import * as anchor from '@coral-xyz/anchor';
+import { Keypair } from '@solana/web3.js';
+import { PublicKey } from '@solana/web3.js';
+import { BankrunProvider } from 'anchor-bankrun';
+import { startAnchor } from 'solana-bankrun';
+import type { CreateToken } from '../target/types/create_token';
+
+const IDL = require('../target/idl/create_token.json');
+const PROGRAM_ID = new PublicKey(IDL.address);
+const METADATA_PROGRAM_ID = new PublicKey('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s');
+
+describe('Bankrun example', async () => {
+  const context = await startAnchor(
+    '',
+    [
+      { name: 'create_token', programId: PROGRAM_ID },
+      { name: 'token_metadata', programId: METADATA_PROGRAM_ID },
+    ],
+    [],
+  );
+  const provider = new BankrunProvider(context);
+  const payer = provider.wallet as anchor.Wallet;
+  const program = new anchor.Program<CreateToken>(IDL, provider);
+
+  const metadata = {
+    name: 'Solana Gold',
+    symbol: 'GOLDSOL',
+    uri: 'https://raw.githubusercontent.com/solana-developers/program-examples/new-examples/tokens/tokens/.assets/spl-token.json',
+  };
+
+  it('Create an SPL Token!', async () => {
+    // Generate new keypair to use as address for mint account.
+    const mintKeypair = new Keypair();
+
+    // SPL Token default = 9 decimals
+    const transactionSignature = await program.methods
+      .createTokenMint(9, metadata.name, metadata.symbol, metadata.uri)
+      .accounts({
+        payer: payer.publicKey,
+        mintAccount: mintKeypair.publicKey,
+      })
+      .signers([mintKeypair])
+      .rpc();
+
+    console.log('Success!');
+    console.log(`   Mint Address: ${mintKeypair.publicKey}`);
+    console.log(`   Transaction Signature: ${transactionSignature}`);
+  });
+
+  it('Create an NFT!', async () => {
+    // Generate new keypair to use as address for mint account.
+    const mintKeypair = new Keypair();
+
+    // NFT default = 0 decimals
+    const transactionSignature = await program.methods
+      .createTokenMint(0, metadata.name, metadata.symbol, metadata.uri)
+      .accounts({
+        payer: payer.publicKey,
+        mintAccount: mintKeypair.publicKey,
+      })
+      .signers([mintKeypair])
+      .rpc();
+
+    console.log('Success!');
+    console.log(`   Mint Address: ${mintKeypair.publicKey}`);
+    console.log(`   Transaction Signature: ${transactionSignature}`);
+  });
+});

+ 1 - 1
tokens/escrow/anchor/Anchor.toml

@@ -15,4 +15,4 @@ cluster = "Localnet"
 wallet = "~/.config/solana/id.json"
 
 [scripts]
-test = "pnpm ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
+test = "pnpm ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.test.ts"

+ 5 - 2
tokens/escrow/anchor/package.json

@@ -5,17 +5,20 @@
   },
   "dependencies": {
     "@coral-xyz/anchor": "^0.30.0",
-    "@solana-developers/helpers": "^2.4.0",
-    "@solana/spl-token": "^0.4.6"
+    "@solana-developers/helpers": "^2.3.0",
+    "@solana/spl-token": "^0.4.6",
+    "@solana/web3.js": "^1.95.2"
   },
   "license": "MIT",
   "devDependencies": {
     "@types/bn.js": "^5.1.0",
     "@types/chai": "^4.3.0",
     "@types/mocha": "^9.0.0",
+    "anchor-bankrun": "^0.4.0",
     "chai": "^4.3.4",
     "mocha": "^9.0.3",
     "prettier": "^2.6.2",
+    "solana-bankrun": "^0.3.0",
     "ts-mocha": "^10.0.0",
     "typescript": "^4.3.5"
   }

+ 103 - 122
tokens/escrow/anchor/pnpm-lock.yaml

@@ -12,11 +12,14 @@ importers:
         specifier: ^0.30.0
         version: 0.30.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
       '@solana-developers/helpers':
-        specifier: ^2.4.0
-        version: 2.4.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@4.9.5)(utf-8-validate@5.0.10)
+        specifier: ^2.3.0
+        version: 2.5.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@4.9.5)(utf-8-validate@5.0.10)
       '@solana/spl-token':
         specifier: ^0.4.6
         version: 0.4.6(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10)
+      '@solana/web3.js':
+        specifier: ^1.95.2
+        version: 1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)
     devDependencies:
       '@types/bn.js':
         specifier: ^5.1.0
@@ -27,6 +30,9 @@ importers:
       '@types/mocha':
         specifier: ^9.0.0
         version: 9.1.1
+      anchor-bankrun:
+        specifier: ^0.4.0
+        version: 0.4.0(@coral-xyz/anchor@0.30.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(solana-bankrun@0.3.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))
       chai:
         specifier: ^4.3.4
         version: 4.4.1
@@ -36,6 +42,9 @@ importers:
       prettier:
         specifier: ^2.6.2
         version: 2.8.8
+      solana-bankrun:
+        specifier: ^0.3.0
+        version: 0.3.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
       ts-mocha:
         specifier: ^10.0.0
         version: 10.0.0(mocha@9.2.2)
@@ -45,10 +54,6 @@ importers:
 
 packages:
 
-  '@babel/runtime@7.24.7':
-    resolution: {integrity: sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==}
-    engines: {node: '>=6.9.0'}
-
   '@babel/runtime@7.25.0':
     resolution: {integrity: sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==}
     engines: {node: '>=6.9.0'}
@@ -63,18 +68,15 @@ packages:
     peerDependencies:
       '@solana/web3.js': ^1.68.0
 
-  '@noble/curves@1.4.0':
-    resolution: {integrity: sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==}
-
-  '@noble/curves@1.4.2':
-    resolution: {integrity: sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==}
+  '@noble/curves@1.5.0':
+    resolution: {integrity: sha512-J5EKamIHnKPyClwVrzmaf5wSdQXgdHcPZIZLu3bwnbeCx8/7NPK5q2ZBWF+5FvYGByjiQQsJYX6jfgB2wDPn3A==}
 
   '@noble/hashes@1.4.0':
     resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==}
     engines: {node: '>= 16'}
 
-  '@solana-developers/helpers@2.4.0':
-    resolution: {integrity: sha512-eiqHOluJlP9cYfAkgSLaM6o5etWfbBbjNnR3AL0Ds09CJdA3A61qimCPQFmjiLynAb6U7/NSWq0Q28/hnb02Bw==}
+  '@solana-developers/helpers@2.5.0':
+    resolution: {integrity: sha512-QKm22DUFcDseUJ+rjZtIJmw6k5KHe5hV6K3g1RVISitL1KoKPfLYMqAjBQlUcT1KG0GNCpmOKT1dFjJTBsm+1w==}
 
   '@solana/buffer-layout-utils@0.2.0':
     resolution: {integrity: sha512-szG4sxgJGktbuZYDg2FfNmkMi0DYQoVjN2h7ta1W1hPrwzarcFLBq9UpX1UjNXsNpT9dn+chgprtWGioUAr4/g==}
@@ -179,9 +181,6 @@ packages:
     resolution: {integrity: sha512-JBMGB0oR4lPttOZ5XiUGyvylwLQjt1CPJa6qQ5oM+MBCndfjz2TKKkw0eATlLLcYmq1jBVsNlJ2cD6ns2GR7lA==}
     engines: {node: '>=16'}
 
-  '@solana/web3.js@1.93.0':
-    resolution: {integrity: sha512-suf4VYwWxERz4tKoPpXCRHFRNst7jmcFUaD65kII+zg9urpy5PeeqgLV6G5eWGzcVzA9tZeXOju1A1Y+0ojEVw==}
-
   '@solana/web3.js@1.95.2':
     resolution: {integrity: sha512-SjlHp0G4qhuhkQQc+YXdGkI8EerCqwxvgytMgBpzMUQTafrkNant3e7pgilBGgjy/iM40ICvWBLgASTPMrQU7w==}
 
@@ -229,6 +228,14 @@ packages:
     resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==}
     engines: {node: '>= 8.0.0'}
 
+  anchor-bankrun@0.4.0:
+    resolution: {integrity: sha512-s+K7E0IGAlmkhuo8nbiqVsQf2yJ+3l9GjNQJSmkRDe25dQj4Yef9rJh77FH6EQ5H6yQYfzuhgm/5GD6JMjdTZg==}
+    engines: {node: '>= 10'}
+    peerDependencies:
+      '@coral-xyz/anchor': ^0.30.0
+      '@solana/web3.js': ^1.78.4
+      solana-bankrun: ^0.2.0
+
   ansi-colors@4.1.1:
     resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==}
     engines: {node: '>=6'}
@@ -535,11 +542,6 @@ packages:
     peerDependencies:
       ws: '*'
 
-  jayson@4.1.0:
-    resolution: {integrity: sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A==}
-    engines: {node: '>=8'}
-    hasBin: true
-
   jayson@4.1.1:
     resolution: {integrity: sha512-5ZWm4Q/0DHPyeMfAsrwViwUS2DMVsQgWh8bEEIVTkfb3DzHZ2L3G5WUnF+AKmGjjM9r1uAv73SaqC1/U4RL45w==}
     engines: {node: '>=8'}
@@ -675,9 +677,6 @@ packages:
     resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
     engines: {node: '>=0.10.0'}
 
-  rpc-websockets@9.0.1:
-    resolution: {integrity: sha512-JCkdc/TfJBGRfmjIFK7cmqX79nwPWUd9xCM0DAydRbdLShsW3j/GV2gmPlaFa8V1+2u4V/O47fm4ZR5+F6HyDw==}
-
   rpc-websockets@9.0.2:
     resolution: {integrity: sha512-YzggvfItxMY3Lwuax5rC18inhbjJv9Py7JXRHxTIi94JOLrqBsSsUUc5bbl5W6c11tXhdfpDPK0KzBhoGe8jjw==}
 
@@ -690,6 +689,39 @@ packages:
   snake-case@3.0.4:
     resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==}
 
+  solana-bankrun-darwin-arm64@0.3.0:
+    resolution: {integrity: sha512-+NbDncf0U6l3knuacRBiqpjZ2DSp+5lZaAU518gH7/x6qubbui/d000STaIBK+uNTPBS/AL/bCN+7PkXqmA3lA==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [darwin]
+
+  solana-bankrun-darwin-universal@0.3.0:
+    resolution: {integrity: sha512-1/F0xdMa4qvc5o6z16FCCbZ5jbdvKvxpx5kyPcMWRiRPwyvi+zltMxciPAYMlg3wslQqGz88uFhrBEzq2eTumQ==}
+    engines: {node: '>= 10'}
+    os: [darwin]
+
+  solana-bankrun-darwin-x64@0.3.0:
+    resolution: {integrity: sha512-U6CANjkmMl+lgNA7UH0GKs5V7LtVIUDzJBZefGGqLfqUNv3EjA/PrrToM0hAOWJgkxSwdz6zW+p5sw5FmnbXtg==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [darwin]
+
+  solana-bankrun-linux-x64-gnu@0.3.0:
+    resolution: {integrity: sha512-qJSkCFs0k2n4XtTnyxGMiZsuqO2TiqTYgWjQ+3mZhGNUAMys/Vq8bd7/SyBm6RR7EfVuRXRxZvh+F8oKZ77V4w==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [linux]
+
+  solana-bankrun-linux-x64-musl@0.3.0:
+    resolution: {integrity: sha512-xsS2CS2xb1Sw4ivNXM0gPz/qpW9BX0neSvt/pnok5L330Nu9xlTnKAY8FhzzqOP9P9sJlGRM787Y6d0yYwt6xQ==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [linux]
+
+  solana-bankrun@0.3.0:
+    resolution: {integrity: sha512-YkH7sa8TB/AoRPzG17CXJtYsRIQHEkEqGLz1Vwc13taXhDBkjO7z6NI5JYw7n0ybRymDHwMYTc7sd+5J40TyVQ==}
+    engines: {node: '>= 10'}
+
   source-map-support@0.5.21:
     resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
 
@@ -716,10 +748,6 @@ packages:
   superstruct@0.15.5:
     resolution: {integrity: sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ==}
 
-  superstruct@1.0.4:
-    resolution: {integrity: sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ==}
-    engines: {node: '>=14.0.0'}
-
   superstruct@2.0.2:
     resolution: {integrity: sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==}
     engines: {node: '>=14.0.0'}
@@ -819,18 +847,6 @@ packages:
       utf-8-validate:
         optional: true
 
-  ws@7.5.9:
-    resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==}
-    engines: {node: '>=8.3.0'}
-    peerDependencies:
-      bufferutil: ^4.0.1
-      utf-8-validate: ^5.0.2
-    peerDependenciesMeta:
-      bufferutil:
-        optional: true
-      utf-8-validate:
-        optional: true
-
   ws@8.17.0:
     resolution: {integrity: sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==}
     engines: {node: '>=10.0.0'}
@@ -869,19 +885,15 @@ packages:
 
 snapshots:
 
-  '@babel/runtime@7.24.7':
-    dependencies:
-      regenerator-runtime: 0.14.1
-
   '@babel/runtime@7.25.0':
     dependencies:
       regenerator-runtime: 0.14.1
 
   '@coral-xyz/anchor@0.30.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)':
     dependencies:
-      '@coral-xyz/borsh': 0.30.0(@solana/web3.js@1.93.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))
+      '@coral-xyz/borsh': 0.30.0(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))
       '@noble/hashes': 1.4.0
-      '@solana/web3.js': 1.93.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)
       bn.js: 5.2.1
       bs58: 4.0.1
       buffer-layout: 1.2.2
@@ -898,25 +910,22 @@ snapshots:
       - encoding
       - utf-8-validate
 
-  '@coral-xyz/borsh@0.30.0(@solana/web3.js@1.93.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))':
+  '@coral-xyz/borsh@0.30.0(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))':
     dependencies:
-      '@solana/web3.js': 1.93.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)
       bn.js: 5.2.1
       buffer-layout: 1.2.2
 
-  '@noble/curves@1.4.0':
-    dependencies:
-      '@noble/hashes': 1.4.0
-
-  '@noble/curves@1.4.2':
+  '@noble/curves@1.5.0':
     dependencies:
       '@noble/hashes': 1.4.0
 
   '@noble/hashes@1.4.0': {}
 
-  '@solana-developers/helpers@2.4.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@4.9.5)(utf-8-validate@5.0.10)':
+  '@solana-developers/helpers@2.5.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@4.9.5)(utf-8-validate@5.0.10)':
     dependencies:
       '@solana/spl-token': 0.4.8(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@4.9.5)(utf-8-validate@5.0.10)
+      '@solana/spl-token-metadata': 0.1.4(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)
       '@solana/web3.js': 1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)
       bs58: 6.0.0
       dotenv: 16.4.5
@@ -930,7 +939,7 @@ snapshots:
   '@solana/buffer-layout-utils@0.2.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)':
     dependencies:
       '@solana/buffer-layout': 4.0.1
-      '@solana/web3.js': 1.93.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)
       bigint-buffer: 1.1.5
       bignumber.js: 9.1.2
     transitivePeerDependencies:
@@ -1096,32 +1105,10 @@ snapshots:
     dependencies:
       buffer: 6.0.3
 
-  '@solana/web3.js@1.93.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)':
-    dependencies:
-      '@babel/runtime': 7.24.7
-      '@noble/curves': 1.4.0
-      '@noble/hashes': 1.4.0
-      '@solana/buffer-layout': 4.0.1
-      agentkeepalive: 4.5.0
-      bigint-buffer: 1.1.5
-      bn.js: 5.2.1
-      borsh: 0.7.0
-      bs58: 4.0.1
-      buffer: 6.0.3
-      fast-stable-stringify: 1.0.0
-      jayson: 4.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
-      node-fetch: 2.7.0
-      rpc-websockets: 9.0.1
-      superstruct: 1.0.4
-    transitivePeerDependencies:
-      - bufferutil
-      - encoding
-      - utf-8-validate
-
   '@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)':
     dependencies:
       '@babel/runtime': 7.25.0
-      '@noble/curves': 1.4.2
+      '@noble/curves': 1.5.0
       '@noble/hashes': 1.4.0
       '@solana/buffer-layout': 4.0.1
       agentkeepalive: 4.5.0
@@ -1152,7 +1139,7 @@ snapshots:
 
   '@types/connect@3.4.38':
     dependencies:
-      '@types/node': 12.20.55
+      '@types/node': 20.14.2
 
   '@types/json5@0.0.29':
     optional: true
@@ -1169,7 +1156,7 @@ snapshots:
 
   '@types/ws@7.4.7':
     dependencies:
-      '@types/node': 12.20.55
+      '@types/node': 20.14.2
 
   '@types/ws@8.5.10':
     dependencies:
@@ -1186,6 +1173,12 @@ snapshots:
     dependencies:
       humanize-ms: 1.2.1
 
+  anchor-bankrun@0.4.0(@coral-xyz/anchor@0.30.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(solana-bankrun@0.3.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)):
+    dependencies:
+      '@coral-xyz/anchor': 0.30.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+      solana-bankrun: 0.3.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+
   ansi-colors@4.1.1: {}
 
   ansi-regex@5.0.1: {}
@@ -1454,28 +1447,6 @@ snapshots:
     dependencies:
       ws: 7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10)
 
-  isomorphic-ws@4.0.1(ws@7.5.9(bufferutil@4.0.8)(utf-8-validate@5.0.10)):
-    dependencies:
-      ws: 7.5.9(bufferutil@4.0.8)(utf-8-validate@5.0.10)
-
-  jayson@4.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10):
-    dependencies:
-      '@types/connect': 3.4.38
-      '@types/node': 12.20.55
-      '@types/ws': 7.4.7
-      JSONStream: 1.3.5
-      commander: 2.20.3
-      delay: 5.0.0
-      es6-promisify: 5.0.0
-      eyes: 0.1.8
-      isomorphic-ws: 4.0.1(ws@7.5.9(bufferutil@4.0.8)(utf-8-validate@5.0.10))
-      json-stringify-safe: 5.0.1
-      uuid: 8.3.2
-      ws: 7.5.9(bufferutil@4.0.8)(utf-8-validate@5.0.10)
-    transitivePeerDependencies:
-      - bufferutil
-      - utf-8-validate
-
   jayson@4.1.1(bufferutil@4.0.8)(utf-8-validate@5.0.10):
     dependencies:
       '@types/connect': 3.4.38
@@ -1623,19 +1594,6 @@ snapshots:
 
   require-directory@2.1.1: {}
 
-  rpc-websockets@9.0.1:
-    dependencies:
-      '@swc/helpers': 0.5.11
-      '@types/uuid': 8.3.4
-      '@types/ws': 8.5.10
-      buffer: 6.0.3
-      eventemitter3: 5.0.1
-      uuid: 8.3.2
-      ws: 8.17.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
-    optionalDependencies:
-      bufferutil: 4.0.8
-      utf-8-validate: 5.0.10
-
   rpc-websockets@9.0.2:
     dependencies:
       '@swc/helpers': 0.5.11
@@ -1660,6 +1618,36 @@ snapshots:
       dot-case: 3.0.4
       tslib: 2.6.3
 
+  solana-bankrun-darwin-arm64@0.3.0:
+    optional: true
+
+  solana-bankrun-darwin-universal@0.3.0:
+    optional: true
+
+  solana-bankrun-darwin-x64@0.3.0:
+    optional: true
+
+  solana-bankrun-linux-x64-gnu@0.3.0:
+    optional: true
+
+  solana-bankrun-linux-x64-musl@0.3.0:
+    optional: true
+
+  solana-bankrun@0.3.0(bufferutil@4.0.8)(utf-8-validate@5.0.10):
+    dependencies:
+      '@solana/web3.js': 1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+      bs58: 4.0.1
+    optionalDependencies:
+      solana-bankrun-darwin-arm64: 0.3.0
+      solana-bankrun-darwin-universal: 0.3.0
+      solana-bankrun-darwin-x64: 0.3.0
+      solana-bankrun-linux-x64-gnu: 0.3.0
+      solana-bankrun-linux-x64-musl: 0.3.0
+    transitivePeerDependencies:
+      - bufferutil
+      - encoding
+      - utf-8-validate
+
   source-map-support@0.5.21:
     dependencies:
       buffer-from: 1.1.2
@@ -1684,8 +1672,6 @@ snapshots:
 
   superstruct@0.15.5: {}
 
-  superstruct@1.0.4: {}
-
   superstruct@2.0.2: {}
 
   supports-color@7.2.0:
@@ -1775,11 +1761,6 @@ snapshots:
       bufferutil: 4.0.8
       utf-8-validate: 5.0.10
 
-  ws@7.5.9(bufferutil@4.0.8)(utf-8-validate@5.0.10):
-    optionalDependencies:
-      bufferutil: 4.0.8
-      utf-8-validate: 5.0.10
-
   ws@8.17.0(bufferutil@4.0.8)(utf-8-validate@5.0.10):
     optionalDependencies:
       bufferutil: 4.0.8

+ 177 - 0
tokens/escrow/anchor/tests/bankrun.test.ts

@@ -0,0 +1,177 @@
+import { randomBytes } from 'node:crypto';
+import { describe, it } from 'node:test';
+import * as anchor from '@coral-xyz/anchor';
+import { BN, type Program } from '@coral-xyz/anchor';
+import {
+  MINT_SIZE,
+  TOKEN_2022_PROGRAM_ID,
+  type TOKEN_PROGRAM_ID,
+  createAssociatedTokenAccountIdempotentInstruction,
+  createInitializeMint2Instruction,
+  createMintToInstruction,
+  getAssociatedTokenAddressSync,
+  getMinimumBalanceForRentExemptMint,
+} from '@solana/spl-token';
+import { LAMPORTS_PER_SOL, PublicKey, SystemProgram, Transaction, type TransactionInstruction } from '@solana/web3.js';
+import { BankrunProvider } from 'anchor-bankrun';
+import { assert } from 'chai';
+import { startAnchor } from 'solana-bankrun';
+import type { Escrow } from '../target/types/escrow';
+
+import { confirmTransaction, makeKeypairs } from '@solana-developers/helpers';
+
+const TOKEN_PROGRAM: typeof TOKEN_2022_PROGRAM_ID | typeof TOKEN_PROGRAM_ID = TOKEN_2022_PROGRAM_ID;
+const IDL = require('../target/idl/escrow.json');
+const PROGRAM_ID = new PublicKey(IDL.address);
+
+const getRandomBigNumber = (size = 8) => {
+  return new BN(randomBytes(size));
+};
+
+describe('Escrow Bankrun example', async () => {
+  const context = await startAnchor('', [{ name: 'escrow', programId: PROGRAM_ID }], []);
+  const provider = new BankrunProvider(context);
+  const connection = provider.connection;
+  // const payer = provider.wallet as anchor.Wallet;
+  const program = new anchor.Program<Escrow>(IDL, provider);
+
+  // We're going to reuse these accounts across multiple tests
+  const accounts: Record<string, PublicKey> = {
+    tokenProgram: TOKEN_PROGRAM,
+  };
+
+  const [alice, bob, tokenMintA, tokenMintB] = makeKeypairs(4);
+
+  before('Creates Alice and Bob accounts, 2 token mints, and associated token accounts for both tokens for both users', async () => {
+    const [aliceTokenAccountA, aliceTokenAccountB, bobTokenAccountA, bobTokenAccountB] = [alice, bob].flatMap((keypair) =>
+      [tokenMintA, tokenMintB].map((mint) => getAssociatedTokenAddressSync(mint.publicKey, keypair.publicKey, false, TOKEN_PROGRAM)),
+    );
+
+    // Airdrops to users, and creates two tokens mints 'A' and 'B'"
+    const minimumLamports = await getMinimumBalanceForRentExemptMint(connection);
+
+    const sendSolInstructions: Array<TransactionInstruction> = [alice, bob].map((account) =>
+      SystemProgram.transfer({
+        fromPubkey: provider.publicKey,
+        toPubkey: account.publicKey,
+        lamports: 10 * LAMPORTS_PER_SOL,
+      }),
+    );
+
+    const createMintInstructions: Array<TransactionInstruction> = [tokenMintA, tokenMintB].map((mint) =>
+      SystemProgram.createAccount({
+        fromPubkey: provider.publicKey,
+        newAccountPubkey: mint.publicKey,
+        lamports: minimumLamports,
+        space: MINT_SIZE,
+        programId: TOKEN_PROGRAM,
+      }),
+    );
+
+    // Make tokenA and tokenB mints, mint tokens and create ATAs
+    const mintTokensInstructions: Array<TransactionInstruction> = [
+      {
+        mint: tokenMintA.publicKey,
+        authority: alice.publicKey,
+        ata: aliceTokenAccountA,
+      },
+      {
+        mint: tokenMintB.publicKey,
+        authority: bob.publicKey,
+        ata: bobTokenAccountB,
+      },
+    ].flatMap((mintDetails) => [
+      createInitializeMint2Instruction(mintDetails.mint, 6, mintDetails.authority, null, TOKEN_PROGRAM),
+      createAssociatedTokenAccountIdempotentInstruction(provider.publicKey, mintDetails.ata, mintDetails.authority, mintDetails.mint, TOKEN_PROGRAM),
+      createMintToInstruction(mintDetails.mint, mintDetails.ata, mintDetails.authority, 1_000_000_000, [], TOKEN_PROGRAM),
+    ]);
+
+    // Add all these instructions to our transaction
+    const tx = new Transaction();
+    tx.instructions = [...sendSolInstructions, ...createMintInstructions, ...mintTokensInstructions];
+
+    await provider.sendAndConfirm(tx, [tokenMintA, tokenMintB, alice, bob]);
+
+    // Save the accounts for later use
+    accounts.maker = alice.publicKey;
+    accounts.taker = bob.publicKey;
+    accounts.tokenMintA = tokenMintA.publicKey;
+    accounts.makerTokenAccountA = aliceTokenAccountA;
+    accounts.takerTokenAccountA = bobTokenAccountA;
+    accounts.tokenMintB = tokenMintB.publicKey;
+    accounts.makerTokenAccountB = aliceTokenAccountB;
+    accounts.takerTokenAccountB = bobTokenAccountB;
+  });
+
+  const tokenAOfferedAmount = new BN(1_000_000);
+  const tokenBWantedAmount = new BN(1_000_000);
+
+  // We'll call this function from multiple tests, so let's seperate it out
+  const make = async () => {
+    // Pick a random ID for the offer we'll make
+    const offerId = getRandomBigNumber();
+
+    // Then determine the account addresses we'll use for the offer and the vault
+    const offer = PublicKey.findProgramAddressSync(
+      [Buffer.from('offer'), accounts.maker.toBuffer(), offerId.toArrayLike(Buffer, 'le', 8)],
+      program.programId,
+    )[0];
+
+    const vault = getAssociatedTokenAddressSync(accounts.tokenMintA, offer, true, TOKEN_PROGRAM);
+
+    accounts.offer = offer;
+    accounts.vault = vault;
+
+    const transactionSignature = await program.methods
+      .makeOffer(offerId, tokenAOfferedAmount, tokenBWantedAmount)
+      .accounts({ ...accounts })
+      .signers([alice])
+      .rpc();
+
+    await confirmTransaction(connection, transactionSignature);
+
+    // Check our vault contains the tokens offered
+    const vaultBalanceResponse = await connection.getTokenAccountBalance(vault);
+    const vaultBalance = new BN(vaultBalanceResponse.value.amount);
+    assert(vaultBalance.eq(tokenAOfferedAmount));
+
+    // Check our Offer account contains the correct data
+    const offerAccount = await program.account.offer.fetch(offer);
+
+    assert(offerAccount.maker.equals(alice.publicKey));
+    assert(offerAccount.tokenMintA.equals(accounts.tokenMintA));
+    assert(offerAccount.tokenMintB.equals(accounts.tokenMintB));
+    assert(offerAccount.tokenBWantedAmount.eq(tokenBWantedAmount));
+  };
+
+  // We'll call this function from multiple tests, so let's seperate it out
+  const take = async () => {
+    const transactionSignature = await program.methods
+      .takeOffer()
+      .accounts({ ...accounts })
+      .signers([bob])
+      .rpc();
+
+    await confirmTransaction(connection, transactionSignature);
+
+    // Check the offered tokens are now in Bob's account
+    // (note: there is no before balance as Bob didn't have any offered tokens before the transaction)
+    const bobTokenAccountBalanceAfterResponse = await connection.getTokenAccountBalance(accounts.takerTokenAccountA);
+    const bobTokenAccountBalanceAfter = new BN(bobTokenAccountBalanceAfterResponse.value.amount);
+    assert(bobTokenAccountBalanceAfter.eq(tokenAOfferedAmount));
+
+    // Check the wanted tokens are now in Alice's account
+    // (note: there is no before balance as Alice didn't have any wanted tokens before the transaction)
+    const aliceTokenAccountBalanceAfterResponse = await connection.getTokenAccountBalance(accounts.makerTokenAccountB);
+    const aliceTokenAccountBalanceAfter = new BN(aliceTokenAccountBalanceAfterResponse.value.amount);
+    assert(aliceTokenAccountBalanceAfter.eq(tokenBWantedAmount));
+  };
+
+  it('Puts the tokens Alice offers into the vault when Alice makes an offer', async () => {
+    await make();
+  });
+
+  it("Puts the tokens from the vault into Bob's account, and gives Alice Bob's tokens, when Bob takes an offer", async () => {
+    await take();
+  });
+});

+ 0 - 0
tokens/escrow/anchor/tests/escrow.ts → tokens/escrow/anchor/tests/escrow.test.ts


+ 1 - 1
tokens/nft-minter/anchor/Anchor.toml

@@ -12,7 +12,7 @@ nft_minter = "52quezNUzc1Ej6Jh6L4bvtxPW8j6TEFHuLVAWiFvdnsc"
 url = "https://api.apr.dev"
 
 [provider]
-cluster = "devnet"
+cluster = "localnet"
 wallet = "~/.config/solana/id.json"
 
 [scripts]

+ 8 - 4
tokens/nft-minter/anchor/package.json

@@ -1,15 +1,19 @@
 {
-  "dependencies": {
-    "@coral-xyz/anchor": "^0.30.0",
-    "@solana/spl-token": "^0.3.8"
+  "scripts": {
+    "postinstall": "zx prepare.mjs"
   },
   "devDependencies": {
+    "@coral-xyz/anchor": "^0.30.0",
+    "@solana/spl-token": "^0.3.11",
     "@types/bn.js": "^5.1.0",
     "@types/chai": "^4.3.0",
     "@types/mocha": "^9.0.0",
+    "anchor-bankrun": "^0.4.0",
     "chai": "^4.3.4",
     "mocha": "^9.0.3",
+    "solana-bankrun": "^0.3.0",
     "ts-mocha": "^10.0.0",
-    "typescript": "^4.3.5"
+    "typescript": "^4.3.5",
+    "zx": "^8.1.4"
   }
 }

File diff suppressed because it is too large
+ 184 - 570
tokens/nft-minter/anchor/pnpm-lock.yaml


+ 34 - 0
tokens/nft-minter/anchor/prepare.mjs

@@ -0,0 +1,34 @@
+#!/usr/bin/env zx
+
+import { mkdir, rm } from 'node:fs/promises';
+import { join } from 'node:path';
+import { $ } from 'zx';
+
+const programs = [
+  {
+    id: 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s',
+    name: 'token_metadata.so',
+  },
+];
+
+const outputDir = 'tests/fixtures';
+const overwrite = true;
+
+try {
+  for (const program of programs) {
+    const { id, name } = program;
+    const outputFile = join(outputDir, name);
+    await $`solana config set -um`;
+
+    try {
+      await mkdir(outputDir, { recursive: true });
+      if (overwrite) await rm(outputFile, { force: true });
+      await $`solana program dump ${id} ${outputFile}`;
+      console.log(`Program ${id} dumped to ${outputFile}`);
+    } catch (error) {
+      console.error(`Error dumping ${id}: ${error.message}`);
+    }
+  }
+} catch (error) {
+  console.error(`Error preparing programs: ${error.message}`);
+}

+ 57 - 0
tokens/nft-minter/anchor/tests/bankrun.test.ts

@@ -0,0 +1,57 @@
+import fs from 'node:fs';
+import { describe, it } from 'node:test';
+import * as anchor from '@coral-xyz/anchor';
+import { getAssociatedTokenAddressSync } from '@solana/spl-token';
+import { Keypair } from '@solana/web3.js';
+import { PublicKey } from '@solana/web3.js';
+import { BankrunProvider } from 'anchor-bankrun';
+import { startAnchor } from 'solana-bankrun';
+import type { NftMinter } from '../target/types/nft_minter';
+
+const IDL = require('../target/idl/nft_minter.json');
+const PROGRAM_ID = new PublicKey(IDL.address);
+const METADATA_PROGRAM_ID = new PublicKey('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s');
+
+describe('NFT bankrun Minter', async () => {
+  const context = await startAnchor(
+    '',
+    [
+      { name: 'nft_minter', programId: PROGRAM_ID },
+      { name: 'token_metadata', programId: METADATA_PROGRAM_ID },
+    ],
+    [],
+  );
+  const provider = new BankrunProvider(context);
+  anchor.setProvider(provider);
+  const payer = provider.wallet as anchor.Wallet;
+  const program = new anchor.Program<NftMinter>(IDL, provider);
+
+  // The metadata for our NFT
+  const metadata = {
+    name: 'Homer NFT',
+    symbol: 'HOMR',
+    uri: 'https://raw.githubusercontent.com/solana-developers/program-examples/new-examples/tokens/tokens/.assets/nft.json',
+  };
+
+  it('Create an NFT!', async () => {
+    // Generate a keypair to use as the address of our mint account
+    const mintKeypair = new Keypair();
+
+    // Derive the associated token address account for the mint and payer.
+    const associatedTokenAccountAddress = getAssociatedTokenAddressSync(mintKeypair.publicKey, payer.publicKey);
+
+    const transactionSignature = await program.methods
+      .mintNft(metadata.name, metadata.symbol, metadata.uri)
+      .accounts({
+        payer: payer.publicKey,
+        mintAccount: mintKeypair.publicKey,
+        associatedTokenAccount: associatedTokenAccountAddress,
+      })
+      .signers([mintKeypair])
+      .rpc({ skipPreflight: true });
+
+    console.log('Success!');
+    console.log(`   Mint Address: ${mintKeypair.publicKey}`);
+    console.log(`   Transaction Signature: ${transactionSignature}`);
+  });
+});

+ 6 - 2
tokens/nft-operations/anchor/package.json

@@ -1,7 +1,8 @@
 {
   "scripts": {
     "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w",
-    "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
+    "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check",
+    "postinstall": "zx prepare.mjs"
   },
   "dependencies": {
     "@coral-xyz/anchor": "^0.30.1",
@@ -15,10 +16,13 @@
     "@types/bn.js": "^5.1.0",
     "@types/chai": "^4.3.0",
     "@types/mocha": "^9.0.0",
+    "anchor-bankrun": "^0.4.0",
     "chai": "^4.3.4",
     "mocha": "^9.0.3",
     "prettier": "^2.6.2",
+    "solana-bankrun": "^0.3.0",
     "ts-mocha": "^10.0.0",
-    "typescript": "^4.3.5"
+    "typescript": "^4.3.5",
+    "zx": "^8.1.4"
   }
 }

+ 113 - 0
tokens/nft-operations/anchor/pnpm-lock.yaml

@@ -36,6 +36,9 @@ importers:
       '@types/mocha':
         specifier: ^9.0.0
         version: 9.1.1
+      anchor-bankrun:
+        specifier: ^0.4.0
+        version: 0.4.0(@coral-xyz/anchor@0.30.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(@solana/web3.js@1.95.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(solana-bankrun@0.3.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))
       chai:
         specifier: ^4.3.4
         version: 4.4.1
@@ -45,12 +48,18 @@ importers:
       prettier:
         specifier: ^2.6.2
         version: 2.8.8
+      solana-bankrun:
+        specifier: ^0.3.0
+        version: 0.3.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
       ts-mocha:
         specifier: ^10.0.0
         version: 10.0.0(mocha@9.2.2)
       typescript:
         specifier: ^4.3.5
         version: 4.9.5
+      zx:
+        specifier: ^8.1.4
+        version: 8.1.4
 
 packages:
 
@@ -216,9 +225,15 @@ packages:
   '@types/connect@3.4.38':
     resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
 
+  '@types/fs-extra@11.0.4':
+    resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==}
+
   '@types/json5@0.0.29':
     resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
 
+  '@types/jsonfile@6.1.4':
+    resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==}
+
   '@types/mocha@9.1.1':
     resolution: {integrity: sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==}
 
@@ -248,6 +263,14 @@ packages:
     resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==}
     engines: {node: '>= 8.0.0'}
 
+  anchor-bankrun@0.4.0:
+    resolution: {integrity: sha512-s+K7E0IGAlmkhuo8nbiqVsQf2yJ+3l9GjNQJSmkRDe25dQj4Yef9rJh77FH6EQ5H6yQYfzuhgm/5GD6JMjdTZg==}
+    engines: {node: '>= 10'}
+    peerDependencies:
+      '@coral-xyz/anchor': ^0.30.0
+      '@solana/web3.js': ^1.78.4
+      solana-bankrun: ^0.2.0
+
   ansi-colors@4.1.1:
     resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==}
     engines: {node: '>=6'}
@@ -749,6 +772,39 @@ packages:
   snake-case@3.0.4:
     resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==}
 
+  solana-bankrun-darwin-arm64@0.3.0:
+    resolution: {integrity: sha512-+NbDncf0U6l3knuacRBiqpjZ2DSp+5lZaAU518gH7/x6qubbui/d000STaIBK+uNTPBS/AL/bCN+7PkXqmA3lA==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [darwin]
+
+  solana-bankrun-darwin-universal@0.3.0:
+    resolution: {integrity: sha512-1/F0xdMa4qvc5o6z16FCCbZ5jbdvKvxpx5kyPcMWRiRPwyvi+zltMxciPAYMlg3wslQqGz88uFhrBEzq2eTumQ==}
+    engines: {node: '>= 10'}
+    os: [darwin]
+
+  solana-bankrun-darwin-x64@0.3.0:
+    resolution: {integrity: sha512-U6CANjkmMl+lgNA7UH0GKs5V7LtVIUDzJBZefGGqLfqUNv3EjA/PrrToM0hAOWJgkxSwdz6zW+p5sw5FmnbXtg==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [darwin]
+
+  solana-bankrun-linux-x64-gnu@0.3.0:
+    resolution: {integrity: sha512-qJSkCFs0k2n4XtTnyxGMiZsuqO2TiqTYgWjQ+3mZhGNUAMys/Vq8bd7/SyBm6RR7EfVuRXRxZvh+F8oKZ77V4w==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [linux]
+
+  solana-bankrun-linux-x64-musl@0.3.0:
+    resolution: {integrity: sha512-xsS2CS2xb1Sw4ivNXM0gPz/qpW9BX0neSvt/pnok5L330Nu9xlTnKAY8FhzzqOP9P9sJlGRM787Y6d0yYwt6xQ==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [linux]
+
+  solana-bankrun@0.3.0:
+    resolution: {integrity: sha512-YkH7sa8TB/AoRPzG17CXJtYsRIQHEkEqGLz1Vwc13taXhDBkjO7z6NI5JYw7n0ybRymDHwMYTc7sd+5J40TyVQ==}
+    engines: {node: '>= 10'}
+
   source-map-support@0.5.21:
     resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
 
@@ -914,6 +970,11 @@ packages:
     resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
     engines: {node: '>=10'}
 
+  zx@8.1.4:
+    resolution: {integrity: sha512-QFDYYpnzdpRiJ3dL2102Cw26FpXpWshW4QLTGxiYfIcwdAqg084jRCkK/kuP/NOSkxOjydRwNFG81qzA5r1a6w==}
+    engines: {node: '>= 12.17.0'}
+    hasBin: true
+
 snapshots:
 
   '@babel/runtime@7.24.7':
@@ -1178,9 +1239,20 @@ snapshots:
     dependencies:
       '@types/node': 12.20.55
 
+  '@types/fs-extra@11.0.4':
+    dependencies:
+      '@types/jsonfile': 6.1.4
+      '@types/node': 20.14.10
+    optional: true
+
   '@types/json5@0.0.29':
     optional: true
 
+  '@types/jsonfile@6.1.4':
+    dependencies:
+      '@types/node': 20.14.10
+    optional: true
+
   '@types/mocha@9.1.1': {}
 
   '@types/node@12.20.55': {}
@@ -1210,6 +1282,12 @@ snapshots:
     dependencies:
       humanize-ms: 1.2.1
 
+  anchor-bankrun@0.4.0(@coral-xyz/anchor@0.30.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(@solana/web3.js@1.95.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(solana-bankrun@0.3.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)):
+    dependencies:
+      '@coral-xyz/anchor': 0.30.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.95.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+      solana-bankrun: 0.3.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+
   ansi-colors@4.1.1: {}
 
   ansi-regex@5.0.1: {}
@@ -1692,6 +1770,36 @@ snapshots:
       dot-case: 3.0.4
       tslib: 2.6.3
 
+  solana-bankrun-darwin-arm64@0.3.0:
+    optional: true
+
+  solana-bankrun-darwin-universal@0.3.0:
+    optional: true
+
+  solana-bankrun-darwin-x64@0.3.0:
+    optional: true
+
+  solana-bankrun-linux-x64-gnu@0.3.0:
+    optional: true
+
+  solana-bankrun-linux-x64-musl@0.3.0:
+    optional: true
+
+  solana-bankrun@0.3.0(bufferutil@4.0.8)(utf-8-validate@5.0.10):
+    dependencies:
+      '@solana/web3.js': 1.95.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+      bs58: 4.0.1
+    optionalDependencies:
+      solana-bankrun-darwin-arm64: 0.3.0
+      solana-bankrun-darwin-universal: 0.3.0
+      solana-bankrun-darwin-x64: 0.3.0
+      solana-bankrun-linux-x64-gnu: 0.3.0
+      solana-bankrun-linux-x64-musl: 0.3.0
+    transitivePeerDependencies:
+      - bufferutil
+      - encoding
+      - utf-8-validate
+
   source-map-support@0.5.21:
     dependencies:
       buffer-from: 1.1.2
@@ -1836,3 +1944,8 @@ snapshots:
   yn@2.0.0: {}
 
   yocto-queue@0.1.0: {}
+
+  zx@8.1.4:
+    optionalDependencies:
+      '@types/fs-extra': 11.0.4
+      '@types/node': 20.14.10

+ 34 - 0
tokens/nft-operations/anchor/prepare.mjs

@@ -0,0 +1,34 @@
+#!/usr/bin/env zx
+
+import { mkdir, rm } from 'node:fs/promises';
+import { join } from 'node:path';
+import { $ } from 'zx';
+
+const programs = [
+  {
+    id: 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s',
+    name: 'token_metadata.so',
+  },
+];
+
+const outputDir = 'tests/fixtures';
+const overwrite = true;
+
+try {
+  for (const program of programs) {
+    const { id, name } = program;
+    const outputFile = join(outputDir, name);
+    await $`solana config set -um`;
+
+    try {
+      await mkdir(outputDir, { recursive: true });
+      if (overwrite) await rm(outputFile, { force: true });
+      await $`solana program dump ${id} ${outputFile}`;
+      console.log(`Program ${id} dumped to ${outputFile}`);
+    } catch (error) {
+      console.error(`Error dumping ${id}: ${error.message}`);
+    }
+  }
+} catch (error) {
+  console.error(`Error preparing programs: ${error.message}`);
+}

+ 150 - 0
tokens/nft-operations/anchor/tests/bankrun.test.ts

@@ -0,0 +1,150 @@
+import { describe, it } from 'node:test';
+import * as anchor from '@coral-xyz/anchor';
+import type { Program } from '@coral-xyz/anchor';
+import type NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet';
+import { ASSOCIATED_PROGRAM_ID } from '@coral-xyz/anchor/dist/cjs/utils/token';
+import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync } from '@solana/spl-token';
+import { Keypair, PublicKey, SystemProgram } from '@solana/web3.js';
+import { BankrunProvider } from 'anchor-bankrun';
+import { startAnchor } from 'solana-bankrun';
+import type { MintNft } from '../target/types/mint_nft';
+
+const IDL = require('../target/idl/mint_nft.json');
+const PROGRAM_ID = new PublicKey(IDL.address);
+const METADATA_PROGRAM_ID = new PublicKey('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s');
+
+describe('mint-nft bankrun', async () => {
+  const context = await startAnchor(
+    '',
+    [
+      { name: 'mint_nft', programId: PROGRAM_ID },
+      { name: 'token_metadata', programId: METADATA_PROGRAM_ID },
+    ],
+    [],
+  );
+  const provider = new BankrunProvider(context);
+  anchor.setProvider(provider);
+  const wallet = provider.wallet as anchor.Wallet;
+  const program = new anchor.Program<MintNft>(IDL, provider);
+
+  const TOKEN_METADATA_PROGRAM_ID = new anchor.web3.PublicKey('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s');
+
+  const mintAuthority = anchor.web3.PublicKey.findProgramAddressSync([Buffer.from('authority')], program.programId)[0];
+
+  const collectionKeypair = Keypair.generate();
+  const collectionMint = collectionKeypair.publicKey;
+
+  const mintKeypair = Keypair.generate();
+  const mint = mintKeypair.publicKey;
+
+  const getMetadata = async (mint: anchor.web3.PublicKey): Promise<anchor.web3.PublicKey> => {
+    return anchor.web3.PublicKey.findProgramAddressSync(
+      [Buffer.from('metadata'), TOKEN_METADATA_PROGRAM_ID.toBuffer(), mint.toBuffer()],
+      TOKEN_METADATA_PROGRAM_ID,
+    )[0];
+  };
+
+  const getMasterEdition = async (mint: anchor.web3.PublicKey): Promise<anchor.web3.PublicKey> => {
+    return anchor.web3.PublicKey.findProgramAddressSync(
+      [Buffer.from('metadata'), TOKEN_METADATA_PROGRAM_ID.toBuffer(), mint.toBuffer(), Buffer.from('edition')],
+      TOKEN_METADATA_PROGRAM_ID,
+    )[0];
+  };
+
+  it('Create Collection NFT', async () => {
+    console.log('\nCollection Mint Key: ', collectionMint.toBase58());
+
+    const metadata = await getMetadata(collectionMint);
+    console.log('Collection Metadata Account: ', metadata.toBase58());
+
+    const masterEdition = await getMasterEdition(collectionMint);
+    console.log('Master Edition Account: ', masterEdition.toBase58());
+
+    const destination = getAssociatedTokenAddressSync(collectionMint, wallet.publicKey);
+    console.log('Destination ATA = ', destination.toBase58());
+
+    const tx = await program.methods
+      .createCollection()
+      .accountsPartial({
+        user: wallet.publicKey,
+        mint: collectionMint,
+        mintAuthority,
+        metadata,
+        masterEdition,
+        destination,
+        systemProgram: SystemProgram.programId,
+        tokenProgram: TOKEN_PROGRAM_ID,
+        associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
+        tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
+      })
+      .signers([collectionKeypair])
+      .rpc({
+        skipPreflight: true,
+      });
+    console.log('\nCollection NFT minted: TxID - ', tx);
+  });
+
+  it('Mint NFT', async () => {
+    console.log('\nMint', mint.toBase58());
+
+    const metadata = await getMetadata(mint);
+    console.log('Metadata', metadata.toBase58());
+
+    const masterEdition = await getMasterEdition(mint);
+    console.log('Master Edition', masterEdition.toBase58());
+
+    const destination = getAssociatedTokenAddressSync(mint, wallet.publicKey);
+    console.log('Destination', destination.toBase58());
+
+    const tx = await program.methods
+      .mintNft()
+      .accountsPartial({
+        owner: wallet.publicKey,
+        destination,
+        metadata,
+        masterEdition,
+        mint,
+        mintAuthority,
+        collectionMint,
+        systemProgram: SystemProgram.programId,
+        tokenProgram: TOKEN_PROGRAM_ID,
+        associatedTokenProgram: ASSOCIATED_PROGRAM_ID,
+        tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
+      })
+      .signers([mintKeypair])
+      .rpc({
+        skipPreflight: true,
+      });
+    console.log('\nNFT Minted! Your transaction signature', tx);
+  });
+
+  it('Verify Collection', async () => {
+    const mintMetadata = await getMetadata(mint);
+    console.log('\nMint Metadata', mintMetadata.toBase58());
+
+    const collectionMetadata = await getMetadata(collectionMint);
+    console.log('Collection Metadata', collectionMetadata.toBase58());
+
+    const collectionMasterEdition = await getMasterEdition(collectionMint);
+    console.log('Collection Master Edition', collectionMasterEdition.toBase58());
+
+    const tx = await program.methods
+      .verifyCollection()
+      .accountsPartial({
+        authority: wallet.publicKey,
+        metadata: mintMetadata,
+        mint,
+        mintAuthority,
+        collectionMint,
+        collectionMetadata,
+        collectionMasterEdition,
+        systemProgram: SystemProgram.programId,
+        sysvarInstruction: anchor.web3.SYSVAR_INSTRUCTIONS_PUBKEY,
+        tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
+      })
+      .rpc({
+        skipPreflight: true,
+      });
+    console.log('\nCollection Verified! Your transaction signature', tx);
+  });
+});

+ 1 - 1
tokens/pda-mint-authority/anchor/Anchor.toml

@@ -11,7 +11,7 @@ token_minter = "3LFrPHqwk5jMrmiz48BFj6NV2k4NjobgTe1jChzx3JGD"
 url = "https://api.apr.dev"
 
 [provider]
-cluster = "Devnet"
+cluster = "localnet"
 wallet = "~/.config/solana/id.json"
 
 [scripts]

+ 7 - 1
tokens/pda-mint-authority/anchor/package.json

@@ -3,13 +3,19 @@
     "@coral-xyz/anchor": "^0.30.0",
     "@solana/spl-token": "^0.3.8"
   },
+  "scripts": {
+    "postinstall": "zx prepare.mjs"
+  },
   "devDependencies": {
     "@types/bn.js": "^5.1.0",
     "@types/chai": "^4.3.0",
     "@types/mocha": "^9.0.0",
+    "anchor-bankrun": "^0.4.0",
     "chai": "^4.3.4",
     "mocha": "^9.0.3",
+    "solana-bankrun": "^0.3.0",
     "ts-mocha": "^10.0.0",
-    "typescript": "^4.3.5"
+    "typescript": "^4.3.5",
+    "zx": "^8.1.4"
   }
 }

File diff suppressed because it is too large
+ 185 - 570
tokens/pda-mint-authority/anchor/pnpm-lock.yaml


+ 34 - 0
tokens/pda-mint-authority/anchor/prepare.mjs

@@ -0,0 +1,34 @@
+#!/usr/bin/env zx
+
+import { mkdir, rm } from 'node:fs/promises';
+import { join } from 'node:path';
+import { $ } from 'zx';
+
+const programs = [
+  {
+    id: 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s',
+    name: 'token_metadata.so',
+  },
+];
+
+const outputDir = 'tests/fixtures';
+const overwrite = true;
+
+try {
+  for (const program of programs) {
+    const { id, name } = program;
+    const outputFile = join(outputDir, name);
+    await $`solana config set -um`;
+
+    try {
+      await mkdir(outputDir, { recursive: true });
+      if (overwrite) await rm(outputFile, { force: true });
+      await $`solana program dump ${id} ${outputFile}`;
+      console.log(`Program ${id} dumped to ${outputFile}`);
+    } catch (error) {
+      console.error(`Error dumping ${id}: ${error.message}`);
+    }
+  }
+} catch (error) {
+  console.error(`Error preparing programs: ${error.message}`);
+}

+ 69 - 0
tokens/pda-mint-authority/anchor/tests/bankrun.test.ts

@@ -0,0 +1,69 @@
+import { describe, it } from 'node:test';
+import * as anchor from '@coral-xyz/anchor';
+import { getAssociatedTokenAddressSync } from '@solana/spl-token';
+import { PublicKey } from '@solana/web3.js';
+import { BankrunProvider } from 'anchor-bankrun';
+import { startAnchor } from 'solana-bankrun';
+import type { TokenMinter } from '../target/types/token_minter';
+
+const IDL = require('../target/idl/token_minter.json');
+const PROGRAM_ID = new PublicKey(IDL.address);
+const METADATA_PROGRAM_ID = new PublicKey('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s');
+
+describe('NFT Minter', async () => {
+  const context = await startAnchor(
+    '',
+    [
+      { name: 'token_minter', programId: PROGRAM_ID },
+      { name: 'token_metadata', programId: METADATA_PROGRAM_ID },
+    ],
+    [],
+  );
+  const provider = new BankrunProvider(context);
+  anchor.setProvider(provider);
+  const payer = provider.wallet as anchor.Wallet;
+  const program = new anchor.Program<TokenMinter>(IDL, provider);
+
+  // Derive the PDA to use as mint account address.
+  // This same PDA is also used as the mint authority.
+  const [mintPDA] = PublicKey.findProgramAddressSync([Buffer.from('mint')], program.programId);
+
+  const metadata = {
+    name: 'Solana Gold',
+    symbol: 'GOLDSOL',
+    uri: 'https://raw.githubusercontent.com/solana-developers/program-examples/new-examples/tokens/tokens/.assets/spl-token.json',
+  };
+
+  it('Create a token!', async () => {
+    const transactionSignature = await program.methods
+      .createToken(metadata.name, metadata.symbol, metadata.uri)
+      .accounts({
+        payer: payer.publicKey,
+      })
+      .rpc();
+
+    console.log('Success!');
+    console.log(`   Mint Address: ${mintPDA}`);
+    console.log(`   Transaction Signature: ${transactionSignature}`);
+  });
+
+  it('Mint 1 Token!', async () => {
+    // Derive the associated token address account for the mint and payer.
+    const associatedTokenAccountAddress = getAssociatedTokenAddressSync(mintPDA, payer.publicKey);
+
+    // Amount of tokens to mint.
+    const amount = new anchor.BN(100);
+
+    const transactionSignature = await program.methods
+      .mintToken(amount)
+      .accounts({
+        payer: payer.publicKey,
+        associatedTokenAccount: associatedTokenAccountAddress,
+      })
+      .rpc();
+
+    console.log('Success!');
+    console.log(`   Associated Token Account Address: ${associatedTokenAccountAddress}`);
+    console.log(`   Transaction Signature: ${transactionSignature}`);
+  });
+});

+ 1 - 1
tokens/spl-token-minter/anchor/Anchor.toml

@@ -11,7 +11,7 @@ spl_token_minter = "3of89Z9jwek9zrFgpCWc9jZvQvitpVMxpZNsrAD2vQUD"
 url = "https://api.apr.dev"
 
 [provider]
-cluster = "devnet"
+cluster = "localnet"
 wallet = "~/.config/solana/id.json"
 
 [scripts]

+ 7 - 1
tokens/spl-token-minter/anchor/package.json

@@ -3,13 +3,19 @@
     "@coral-xyz/anchor": "^0.30.0",
     "@solana/spl-token": "^0.3.8"
   },
+  "scripts": {
+    "postinstall": "zx prepare.mjs"
+  },
   "devDependencies": {
     "@types/bn.js": "^5.1.0",
     "@types/chai": "^4.3.0",
     "@types/mocha": "^9.0.0",
+    "anchor-bankrun": "^0.4.0",
     "chai": "^4.3.4",
     "mocha": "^9.0.3",
+    "solana-bankrun": "^0.3.0",
     "ts-mocha": "^10.0.0",
-    "typescript": "^4.3.5"
+    "typescript": "^4.3.5",
+    "zx": "^8.1.4"
   }
 }

File diff suppressed because it is too large
+ 185 - 570
tokens/spl-token-minter/anchor/pnpm-lock.yaml


+ 34 - 0
tokens/spl-token-minter/anchor/prepare.mjs

@@ -0,0 +1,34 @@
+#!/usr/bin/env zx
+
+import { mkdir, rm } from 'node:fs/promises';
+import { join } from 'node:path';
+import { $ } from 'zx';
+
+const programs = [
+  {
+    id: 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s',
+    name: 'token_metadata.so',
+  },
+];
+
+const outputDir = 'tests/fixtures';
+const overwrite = true;
+
+try {
+  for (const program of programs) {
+    const { id, name } = program;
+    const outputFile = join(outputDir, name);
+    await $`solana config set -um`;
+
+    try {
+      await mkdir(outputDir, { recursive: true });
+      if (overwrite) await rm(outputFile, { force: true });
+      await $`solana program dump ${id} ${outputFile}`;
+      console.log(`Program ${id} dumped to ${outputFile}`);
+    } catch (error) {
+      console.error(`Error dumping ${id}: ${error.message}`);
+    }
+  }
+} catch (error) {
+  console.error(`Error preparing programs: ${error.message}`);
+}

+ 75 - 0
tokens/spl-token-minter/anchor/tests/bankrun.test.ts

@@ -0,0 +1,75 @@
+import { describe, it } from 'node:test';
+import * as anchor from '@coral-xyz/anchor';
+import { getAssociatedTokenAddressSync } from '@solana/spl-token';
+import { Keypair } from '@solana/web3.js';
+import { PublicKey } from '@solana/web3.js';
+import { BankrunProvider } from 'anchor-bankrun';
+import { startAnchor } from 'solana-bankrun';
+import type { SplTokenMinter } from '../target/types/spl_token_minter';
+
+const IDL = require('../target/idl/spl_token_minter.json');
+const PROGRAM_ID = new PublicKey(IDL.address);
+const METADATA_PROGRAM_ID = new PublicKey('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s');
+
+describe('SPL Token Minter', async () => {
+  const context = await startAnchor(
+    '',
+    [
+      { name: 'spl_token_minter', programId: PROGRAM_ID },
+      { name: 'token_metadata', programId: METADATA_PROGRAM_ID },
+    ],
+    [],
+  );
+
+  const provider = new BankrunProvider(context);
+  anchor.setProvider(provider);
+  const payer = provider.wallet as anchor.Wallet;
+  const program = new anchor.Program<SplTokenMinter>(IDL, provider);
+
+  const metadata = {
+    name: 'Solana Gold',
+    symbol: 'GOLDSOL',
+    uri: 'https://raw.githubusercontent.com/solana-developers/program-examples/new-examples/tokens/tokens/.assets/spl-token.json',
+  };
+
+  // Generate new keypair to use as address for mint account.
+  const mintKeypair = new Keypair();
+
+  it('Create an SPL Token!', async () => {
+    const transactionSignature = await program.methods
+      .createToken(metadata.name, metadata.symbol, metadata.uri)
+      .accounts({
+        payer: payer.publicKey,
+        mintAccount: mintKeypair.publicKey,
+      })
+      .signers([mintKeypair])
+      .rpc();
+
+    console.log('Success!');
+    console.log(`   Mint Address: ${mintKeypair.publicKey}`);
+    console.log(`   Transaction Signature: ${transactionSignature}`);
+  });
+
+  it('Mint some tokens to your wallet!', async () => {
+    // Derive the associated token address account for the mint and payer.
+    const associatedTokenAccountAddress = getAssociatedTokenAddressSync(mintKeypair.publicKey, payer.publicKey);
+
+    // Amount of tokens to mint.
+    const amount = new anchor.BN(100);
+
+    // Mint the tokens to the associated token account.
+    const transactionSignature = await program.methods
+      .mintToken(amount)
+      .accounts({
+        mintAuthority: payer.publicKey,
+        recipient: payer.publicKey,
+        mintAccount: mintKeypair.publicKey,
+        associatedTokenAccount: associatedTokenAccountAddress,
+      })
+      .rpc();
+
+    console.log('Success!');
+    console.log(`   Associated Token Account Address: ${associatedTokenAccountAddress}`);
+    console.log(`   Transaction Signature: ${transactionSignature}`);
+  });
+});

+ 4 - 1
tokens/token-2022/basics/anchor/package.json

@@ -4,9 +4,12 @@
     "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
   },
   "dependencies": {
-    "@coral-xyz/anchor": "^0.30.0"
+    "@coral-xyz/anchor": "^0.30.0",
+    "@solana/web3.js": "^1.95.2"
   },
   "devDependencies": {
+    "anchor-bankrun": "^0.4.0",
+    "solana-bankrun": "^0.3.0",
     "chai": "^4.3.4",
     "mocha": "^9.0.3",
     "ts-mocha": "^10.0.0",

File diff suppressed because it is too large
+ 268 - 666
tokens/token-2022/basics/anchor/pnpm-lock.yaml


+ 5 - 4
tokens/token-2022/basics/anchor/tests/anchor.ts

@@ -1,5 +1,6 @@
 import * as anchor from '@coral-xyz/anchor';
 import type { Program } from '@coral-xyz/anchor';
+import { sendAndConfirmTransaction } from '@solana/web3.js';
 import type { Anchor } from '../target/types/anchor';
 
 describe('anchor', () => {
@@ -45,7 +46,7 @@ describe('anchor', () => {
 
     tx.add(ix);
 
-    const sig = await anchor.web3.sendAndConfirmTransaction(program.provider.connection, tx, [wallet.payer]);
+    const sig = await sendAndConfirmTransaction(program.provider.connection, tx, [wallet.payer]);
     console.log('Your transaction signature', sig);
   });
 
@@ -64,7 +65,7 @@ describe('anchor', () => {
 
     tx.add(ix);
 
-    const sig = await anchor.web3.sendAndConfirmTransaction(program.provider.connection, tx, [wallet.payer]);
+    const sig = await sendAndConfirmTransaction(program.provider.connection, tx, [wallet.payer]);
     console.log('Your transaction signature', sig);
   });
 
@@ -108,7 +109,7 @@ describe('anchor', () => {
 
     tx.add(ix);
 
-    const sig = await anchor.web3.sendAndConfirmTransaction(program.provider.connection, tx, [wallet.payer]);
+    const sig = await sendAndConfirmTransaction(program.provider.connection, tx, [wallet.payer]);
     console.log('Your transaction signature', sig);
   });
 
@@ -130,7 +131,7 @@ describe('anchor', () => {
 
     tx.add(ix);
 
-    const sig = await anchor.web3.sendAndConfirmTransaction(program.provider.connection, tx, [wallet.payer]);
+    const sig = await sendAndConfirmTransaction(program.provider.connection, tx, [wallet.payer]);
     console.log('Your transaction signature', sig);
   });
 });

+ 149 - 0
tokens/token-2022/basics/anchor/tests/bankrun.test.ts

@@ -0,0 +1,149 @@
+import { describe, it } from 'node:test';
+import * as anchor from '@coral-xyz/anchor';
+import { PublicKey, sendAndConfirmTransaction } from '@solana/web3.js';
+import { BankrunProvider } from 'anchor-bankrun';
+import { startAnchor } from 'solana-bankrun';
+import type { Anchor } from '../target/types/anchor';
+
+const IDL = require('../target/idl/anchor.json');
+const PROGRAM_ID = new PublicKey(IDL.address);
+
+describe('anchor', async () => {
+  const context = await startAnchor('', [{ name: 'anchor', programId: PROGRAM_ID }], []);
+  const provider = new BankrunProvider(context);
+  anchor.setProvider(provider);
+  const program = new anchor.Program<Anchor>(IDL, provider);
+  const client = context.banksClient;
+  const TOKEN_2022_PROGRAM_ID = new anchor.web3.PublicKey('TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb');
+  const wallet = provider.wallet as anchor.Wallet;
+  const ATA_PROGRAM_ID = new anchor.web3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL');
+
+  const tokenName = 'TestToken';
+  const [mint] = anchor.web3.PublicKey.findProgramAddressSync(
+    [Buffer.from('token-2022-token'), wallet.publicKey.toBytes(), Buffer.from(tokenName)],
+    program.programId,
+  );
+  const [payerATA] = anchor.web3.PublicKey.findProgramAddressSync(
+    [wallet.publicKey.toBytes(), TOKEN_2022_PROGRAM_ID.toBytes(), mint.toBytes()],
+    ATA_PROGRAM_ID,
+  );
+
+  const receiver = anchor.web3.Keypair.generate();
+
+  const [receiverATA] = anchor.web3.PublicKey.findProgramAddressSync(
+    [receiver.publicKey.toBytes(), TOKEN_2022_PROGRAM_ID.toBytes(), mint.toBytes()],
+    ATA_PROGRAM_ID,
+  );
+
+  it('Create Token-2022 Token', async () => {
+    // await connection.requestAirdrop(receiver.publicKey, 1000000000);
+    // await connection.requestAirdrop(wallet.publicKey, 1000000000);
+    const tx = new anchor.web3.Transaction();
+    const [blockhash, _height] = await client.getLatestBlockhash();
+
+    const ix = await program.methods
+      .createToken(tokenName)
+      .accounts({
+        signer: wallet.publicKey,
+        tokenProgram: TOKEN_2022_PROGRAM_ID,
+      })
+      .instruction();
+
+    tx.recentBlockhash = blockhash;
+    tx.add(ix);
+    tx.sign(wallet.payer);
+    const sig = await client.processTransaction(tx);
+    console.log('Your transaction signature', sig);
+  });
+
+  it('Initialize payer ATA', async () => {
+    const tx = new anchor.web3.Transaction();
+    const [blockhash, _height] = await client.getLatestBlockhash();
+
+    const ix = await program.methods
+      .createAssociatedTokenAccount()
+      .accounts({
+        tokenAccount: payerATA,
+        mint: mint,
+        signer: wallet.publicKey,
+        tokenProgram: TOKEN_2022_PROGRAM_ID,
+      })
+      .instruction();
+
+    tx.recentBlockhash = blockhash;
+    tx.add(ix);
+    tx.sign(wallet.payer);
+    const sig = await client.processTransaction(tx);
+    console.log('Your transaction signature', sig);
+  });
+
+  /*
+  // This instruction is included only as a reference, but is not required to run this test, because we are using "init" in the program's transfer instruction. The create_associated_token_account instruction on the program is provided as a reference as well.
+  it("Initialize receiver ATA", async () => {
+    const tx = new anchor.web3.Transaction();
+    const ix = await program.methods
+      .createAssociatedTokenAccount()
+      .accounts({
+        tokenAccount: receiverATA,
+        mint: mint,
+        signer: receiver.publicKey,
+        tokenProgram: TOKEN_2022_PROGRAM_ID,
+        associatedTokenProgram: ATA_PROGRAM_ID,
+      })
+      .signers([receiver])
+      .instruction();
+    tx.add(ix);
+    const sig = await anchor.web3.sendAndConfirmTransaction(
+      program.provider.connection,
+      tx,
+      [receiver]
+    );
+    console.log("Your transaction signature", sig);
+  });
+*/
+
+  it('Mint Token to payer', async () => {
+    const tx = new anchor.web3.Transaction();
+    const [blockhash, _height] = await client.getLatestBlockhash();
+
+    const ix = await program.methods
+      .mintToken(new anchor.BN(200000000))
+      .accounts({
+        mint: mint,
+        signer: wallet.publicKey,
+        receiver: payerATA,
+        tokenProgram: TOKEN_2022_PROGRAM_ID,
+      })
+      .instruction();
+
+    tx.recentBlockhash = blockhash;
+    tx.add(ix);
+    tx.sign(wallet.payer);
+    const sig = await client.processTransaction(tx);
+    console.log('Your transaction signature', sig);
+  });
+
+  // Using init in the transfer instruction, as init if needed is bot working with Token 2022 yet.
+  it('Transfer Token', async () => {
+    const tx = new anchor.web3.Transaction();
+    const [blockhash, _height] = await client.getLatestBlockhash();
+
+    const ix = await program.methods
+      .transferToken(new anchor.BN(100))
+      .accounts({
+        mint: mint,
+        signer: wallet.publicKey,
+        from: payerATA,
+        to: receiver.publicKey,
+        tokenProgram: TOKEN_2022_PROGRAM_ID,
+        toAta: receiverATA,
+      })
+      .instruction();
+
+    tx.recentBlockhash = blockhash;
+    tx.add(ix);
+    tx.sign(wallet.payer);
+    const sig = await client.processTransaction(tx);
+    console.log('Your transaction signature', sig);
+  });
+});

+ 2 - 0
tokens/token-2022/cpi-guard/anchor/package.json

@@ -11,9 +11,11 @@
     "@types/bn.js": "^5.1.0",
     "@types/chai": "^4.3.0",
     "@types/mocha": "^9.0.0",
+    "anchor-bankrun": "^0.4.0",
     "chai": "^4.3.4",
     "mocha": "^9.0.3",
     "prettier": "^2.6.2",
+    "solana-bankrun": "^0.3.0",
     "ts-mocha": "^10.0.0",
     "typescript": "^4.3.5"
   }

File diff suppressed because it is too large
+ 180 - 582
tokens/token-2022/cpi-guard/anchor/pnpm-lock.yaml


File diff suppressed because it is too large
+ 803 - 236
tokens/token-2022/default-account-state/anchor/pnpm-lock.yaml


File diff suppressed because it is too large
+ 723 - 180
tokens/token-2022/group/anchor/pnpm-lock.yaml


+ 1 - 1
tokens/token-2022/group/anchor/programs/group/Cargo.toml

@@ -19,4 +19,4 @@ idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
 [dependencies]
 anchor-lang = "0.30.0"
 anchor-spl = "0.30.0"
-spl-token-group-interface = "=0.2.3"
+spl-token-group-interface = "0.2.5"

File diff suppressed because it is too large
+ 803 - 236
tokens/token-2022/immutable-owner/anchor/pnpm-lock.yaml


File diff suppressed because it is too large
+ 803 - 236
tokens/token-2022/interest-bearing/anchor/pnpm-lock.yaml


File diff suppressed because it is too large
+ 776 - 118
tokens/token-2022/memo-transfer/anchor/pnpm-lock.yaml


Some files were not shown because too many files changed in this diff