Browse Source

add shank example by joe and clockwork reference

Valentin Madrid 2 years ago
parent
commit
2caa491077
31 changed files with 1790 additions and 0 deletions
  1. 3 0
      tools/clockwork/README.md
  2. 2 0
      tools/native/shank-and-solita/.crates/.crates.toml
  3. 1 0
      tools/native/shank-and-solita/.crates/.crates2.json
  4. BIN
      tools/native/shank-and-solita/.crates/bin/shank
  5. 14 0
      tools/native/shank-and-solita/.solitarc.js
  6. 94 0
      tools/native/shank-and-solita/README.md
  7. 14 0
      tools/native/shank-and-solita/package.json
  8. 13 0
      tools/native/shank-and-solita/program/Cargo.toml
  9. 263 0
      tools/native/shank-and-solita/program/idl/car_rental_service.json
  10. 66 0
      tools/native/shank-and-solita/program/src/instructions/add_car.rs
  11. 74 0
      tools/native/shank-and-solita/program/src/instructions/book_rental.rs
  12. 55 0
      tools/native/shank-and-solita/program/src/instructions/mod.rs
  13. 42 0
      tools/native/shank-and-solita/program/src/instructions/pick_up_car.rs
  14. 42 0
      tools/native/shank-and-solita/program/src/instructions/return_car.rs
  15. 32 0
      tools/native/shank-and-solita/program/src/lib.rs
  16. 48 0
      tools/native/shank-and-solita/program/src/state/mod.rs
  17. 161 0
      tools/native/shank-and-solita/tests/generated/accounts/Car.ts
  18. 197 0
      tools/native/shank-and-solita/tests/generated/accounts/RentalOrder.ts
  19. 7 0
      tools/native/shank-and-solita/tests/generated/accounts/index.ts
  20. 20 0
      tools/native/shank-and-solita/tests/generated/index.ts
  21. 96 0
      tools/native/shank-and-solita/tests/generated/instructions/AddCar.ts
  22. 103 0
      tools/native/shank-and-solita/tests/generated/instructions/BookRental.ts
  23. 76 0
      tools/native/shank-and-solita/tests/generated/instructions/PickUpCar.ts
  24. 76 0
      tools/native/shank-and-solita/tests/generated/instructions/ReturnCar.ts
  25. 4 0
      tools/native/shank-and-solita/tests/generated/instructions/index.ts
  26. 26 0
      tools/native/shank-and-solita/tests/generated/types/AddCarArgs.ts
  27. 29 0
      tools/native/shank-and-solita/tests/generated/types/BookRentalArgs.ts
  28. 25 0
      tools/native/shank-and-solita/tests/generated/types/RentalOrderStatus.ts
  29. 3 0
      tools/native/shank-and-solita/tests/generated/types/index.ts
  30. 194 0
      tools/native/shank-and-solita/tests/test.ts
  31. 10 0
      tools/native/shank-and-solita/tests/tsconfig.test.json

+ 3 - 0
tools/clockwork/README.md

@@ -0,0 +1,3 @@
+Clockwork is an automation infrastructure for Solana. It allows you to schedule transactions and build automated, event driven programs.
+
+Here is a link to the Clockwork Program Examples repository: [Clockwork](https://github.com/clockwork-xyz/clockwork)

+ 2 - 0
tools/native/shank-and-solita/.crates/.crates.toml

@@ -0,0 +1,2 @@
+[v1]
+"shank-cli 0.0.12 (registry+https://github.com/rust-lang/crates.io-index)" = ["shank"]

+ 1 - 0
tools/native/shank-and-solita/.crates/.crates2.json

@@ -0,0 +1 @@
+{"installs":{"shank-cli 0.0.12 (registry+https://github.com/rust-lang/crates.io-index)":{"version_req":"0.0.12","bins":["shank"],"features":[],"all_features":false,"no_default_features":false,"profile":"release","target":"aarch64-apple-darwin","rustc":"rustc 1.66.1 (90743e729 2023-01-10)\nbinary: rustc\ncommit-hash: 90743e7298aca107ddaa0c202a4d3604e29bfeb6\ncommit-date: 2023-01-10\nhost: aarch64-apple-darwin\nrelease: 1.66.1\nLLVM version: 15.0.2\n"}}}

BIN
tools/native/shank-and-solita/.crates/bin/shank


+ 14 - 0
tools/native/shank-and-solita/.solitarc.js

@@ -0,0 +1,14 @@
+const path = require('path');
+const programDir = path.join(__dirname, 'program');
+const idlDir = path.join(programDir, 'idl');
+const sdkDir = path.join(__dirname, 'tests', 'generated');
+const binaryInstallDir = path.join(__dirname, '.crates');
+
+module.exports = {
+  idlGenerator: 'shank',
+  programName: 'car_rental_service',
+  idlDir,
+  sdkDir,
+  binaryInstallDir,
+  programDir,
+};

+ 94 - 0
tools/native/shank-and-solita/README.md

@@ -0,0 +1,94 @@
+# Shank & Solita
+
+The devs at Metaplex created Shank & Solita for native Solana programs to be able to take advantage of serialization & IDLs just like Anchor programs.
+
+### Shank
+
+[Shank](https://docs.metaplex.com/developer-tools/shank) is the Rust crate responsible for generating an IDL for your program.   
+   
+It's super easy to use in your Rust code:   
+   
+Add this annotation to any struct to mark it as an account:
+```rust
+#[derive(ShankAccount)]
+```
+ex:
+```rust
+#[derive(BorshDeserialize, BorshSerialize, Clone, ShankAccount)]
+pub struct Car {
+    pub year: u16,
+    pub make: String,
+    pub model: String,
+}
+```
+
+Add this annotation to any enum to mark it as an instruction enum:
+```rust
+#[derive(ShankInstruction)]
+```
+ex:
+```rust
+#[derive(BorshDeserialize, BorshSerialize, Clone, ShankInstruction)]
+pub enum CarRentalServiceInstruction {
+    AddCar(Car),
+    BookRental(RentalOrder),
+    PickUpCar,
+    ReturnCar,
+}
+```
+
+Then you just need to add the Shank CLI:
+```shell
+cargo install shank-cli
+```
+```shell
+USAGE:
+    shank <SUBCOMMAND>
+
+OPTIONS:
+    -h, --help    Print help information
+
+SUBCOMMANDS:
+    help    Print this message or the help of the given subcommand(s)
+    idl
+```
+
+> Note: You do have to make use of `declare_id` in order for Shank to work properly:
+```rust
+declare_id!("8avNGHVXDwsELJaWMSoUZ44CirQd4zyU9Ez4ZmP4jNjZ");
+```
+
+### Solita
+
+[Solita](https://docs.metaplex.com/developer-tools/solita/) is the JavaScript SDK responsible for building client-side SDK types from your program's IDL.
+
+> Note: Solita will work with an IDL from Shank or from Anchor!
+
+First add Solita to your project:
+```shell
+yarn add -D @metaplex-foundation/solita
+```
+Then add a Solita config `.solitarc.js`:
+```javascript
+const path = require('path');
+const programDir = path.join(__dirname, 'program');
+const idlDir = path.join(programDir, 'idl');
+const sdkDir = path.join(__dirname, 'tests', 'generated');
+const binaryInstallDir = path.join(__dirname, '.crates');
+
+module.exports = {
+  idlGenerator: 'shank',
+  programName: 'car_rental_service',
+  idlDir,
+  sdkDir,
+  binaryInstallDir,
+  programDir,
+};
+```
+
+Once you've got that file configured to match your repository layout, go ahead and run:
+```shell
+yarn solita
+```
+
+That should build all your types from your IDL! Check for a folder called `generated` to see them!

+ 14 - 0
tools/native/shank-and-solita/package.json

@@ -0,0 +1,14 @@
+{
+  "scripts": {
+    "test": "ts-mocha -p ./tests/tsconfig.test.json -t 1000000 ./tests/test.ts"
+  },
+  "devDependencies": {
+    "@metaplex-foundation/solita": "^0.19.3",
+    "@types/chai": "^4.3.4",
+    "@types/mocha": "^10.0.1",
+    "chai": "^4.3.7",
+    "mocha": "^10.2.0",
+    "ts-mocha": "^10.0.0",
+    "typescript": "^4.9.4"
+  }
+}

+ 13 - 0
tools/native/shank-and-solita/program/Cargo.toml

@@ -0,0 +1,13 @@
+[package]
+name = "car-rental-service"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+borsh = "0.9.3"
+borsh-derive = "0.9.3"
+shank = "0.0.12"
+solana-program = "1.14.13"
+
+[lib]
+crate-type = ["cdylib", "lib"]

+ 263 - 0
tools/native/shank-and-solita/program/idl/car_rental_service.json

@@ -0,0 +1,263 @@
+{
+  "version": "0.1.0",
+  "name": "car_rental_service",
+  "instructions": [
+    {
+      "name": "AddCar",
+      "accounts": [
+        {
+          "name": "carAccount",
+          "isMut": true,
+          "isSigner": false,
+          "desc": "The account that will represent the Car being created"
+        },
+        {
+          "name": "payer",
+          "isMut": true,
+          "isSigner": false,
+          "desc": "Fee payer"
+        },
+        {
+          "name": "systemProgram",
+          "isMut": false,
+          "isSigner": false,
+          "desc": "The System Program"
+        }
+      ],
+      "args": [
+        {
+          "name": "addCarArgs",
+          "type": {
+            "defined": "AddCarArgs"
+          }
+        }
+      ],
+      "discriminant": {
+        "type": "u8",
+        "value": 0
+      }
+    },
+    {
+      "name": "BookRental",
+      "accounts": [
+        {
+          "name": "rentalAccount",
+          "isMut": true,
+          "isSigner": false,
+          "desc": "The account that will represent the actual order for the rental"
+        },
+        {
+          "name": "carAccount",
+          "isMut": false,
+          "isSigner": false,
+          "desc": "The account representing the Car being rented in this order"
+        },
+        {
+          "name": "payer",
+          "isMut": true,
+          "isSigner": false,
+          "desc": "Fee payer"
+        },
+        {
+          "name": "systemProgram",
+          "isMut": false,
+          "isSigner": false,
+          "desc": "The System Program"
+        }
+      ],
+      "args": [
+        {
+          "name": "bookRentalArgs",
+          "type": {
+            "defined": "BookRentalArgs"
+          }
+        }
+      ],
+      "discriminant": {
+        "type": "u8",
+        "value": 1
+      }
+    },
+    {
+      "name": "PickUpCar",
+      "accounts": [
+        {
+          "name": "rentalAccount",
+          "isMut": true,
+          "isSigner": false,
+          "desc": "The account representing the active rental"
+        },
+        {
+          "name": "carAccount",
+          "isMut": false,
+          "isSigner": false,
+          "desc": "The account representing the Car being rented in this order"
+        },
+        {
+          "name": "payer",
+          "isMut": true,
+          "isSigner": false,
+          "desc": "Fee payer"
+        }
+      ],
+      "args": [],
+      "discriminant": {
+        "type": "u8",
+        "value": 2
+      }
+    },
+    {
+      "name": "ReturnCar",
+      "accounts": [
+        {
+          "name": "rentalAccount",
+          "isMut": true,
+          "isSigner": false,
+          "desc": "The account representing the active rental"
+        },
+        {
+          "name": "carAccount",
+          "isMut": false,
+          "isSigner": false,
+          "desc": "The account representing the Car being rented in this order"
+        },
+        {
+          "name": "payer",
+          "isMut": true,
+          "isSigner": false,
+          "desc": "Fee payer"
+        }
+      ],
+      "args": [],
+      "discriminant": {
+        "type": "u8",
+        "value": 3
+      }
+    }
+  ],
+  "accounts": [
+    {
+      "name": "Car",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "year",
+            "type": "u16"
+          },
+          {
+            "name": "make",
+            "type": "string"
+          },
+          {
+            "name": "model",
+            "type": "string"
+          }
+        ]
+      }
+    },
+    {
+      "name": "RentalOrder",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "car",
+            "type": "publicKey"
+          },
+          {
+            "name": "name",
+            "type": "string"
+          },
+          {
+            "name": "pickUpDate",
+            "type": "string"
+          },
+          {
+            "name": "returnDate",
+            "type": "string"
+          },
+          {
+            "name": "price",
+            "type": "u64"
+          },
+          {
+            "name": "status",
+            "type": {
+              "defined": "RentalOrderStatus"
+            }
+          }
+        ]
+      }
+    }
+  ],
+  "types": [
+    {
+      "name": "AddCarArgs",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "year",
+            "type": "u16"
+          },
+          {
+            "name": "make",
+            "type": "string"
+          },
+          {
+            "name": "model",
+            "type": "string"
+          }
+        ]
+      }
+    },
+    {
+      "name": "BookRentalArgs",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "name",
+            "type": "string"
+          },
+          {
+            "name": "pickUpDate",
+            "type": "string"
+          },
+          {
+            "name": "returnDate",
+            "type": "string"
+          },
+          {
+            "name": "price",
+            "type": "u64"
+          }
+        ]
+      }
+    },
+    {
+      "name": "RentalOrderStatus",
+      "type": {
+        "kind": "enum",
+        "variants": [
+          {
+            "name": "Created"
+          },
+          {
+            "name": "PickedUp"
+          },
+          {
+            "name": "Returned"
+          }
+        ]
+      }
+    }
+  ],
+  "metadata": {
+    "origin": "shank",
+    "address": "8avNGHVXDwsELJaWMSoUZ44CirQd4zyU9Ez4ZmP4jNjZ",
+    "binaryVersion": "0.0.12",
+    "libVersion": "0.0.12"
+  }
+}

+ 66 - 0
tools/native/shank-and-solita/program/src/instructions/add_car.rs

@@ -0,0 +1,66 @@
+use {
+    borsh::{
+        BorshDeserialize, 
+        BorshSerialize 
+    },
+    shank::ShankAccount,
+    solana_program::{
+        account_info::{AccountInfo, next_account_info}, 
+        entrypoint::ProgramResult, 
+        program::invoke_signed,
+        pubkey::Pubkey,
+        rent::Rent,
+        system_instruction,
+        sysvar::Sysvar,
+    },
+};
+use crate::state::Car;
+
+#[derive(BorshDeserialize, BorshSerialize, Clone, Debug)]
+pub struct AddCarArgs {
+    pub year: u16,
+    pub make: String,
+    pub model: String,
+}
+
+pub fn add_car(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    args: AddCarArgs,
+) -> ProgramResult {
+
+    let accounts_iter = &mut accounts.iter();
+    let car_account = next_account_info(accounts_iter)?;
+    let payer = next_account_info(accounts_iter)?;
+    let system_program = next_account_info(accounts_iter)?;
+
+    let (car_account_pda, car_account_bump) = Car::shank_pda(program_id, args.make, args.model);
+    assert!(&car_account_pda == car_account.key);
+
+    let car_data = Car {
+        year: args.year,
+        make: args.make,
+        model: args.model,
+    };
+
+    let account_span = (car_data.try_to_vec()?).len();
+    let lamports_required = (Rent::get()?).minimum_balance(account_span);
+
+    invoke_signed(
+        &system_instruction::create_account(
+            &payer.key,
+            &car_account.key,
+            lamports_required,
+            account_span as u64,
+            program_id,
+        ),
+        &[
+            payer.clone(), car_account.clone(), system_program.clone()
+        ],
+        Car::shank_seeds_with_bump(args.make, args.model, &[car_account_bump]),
+    )?;
+    
+    car_data.serialize(&mut &mut car_account.data.borrow_mut()[..])?;
+
+    Ok(())
+}

+ 74 - 0
tools/native/shank-and-solita/program/src/instructions/book_rental.rs

@@ -0,0 +1,74 @@
+use {
+    borsh::{
+        BorshDeserialize, 
+        BorshSerialize 
+    },
+    shank::ShankAccount,
+    solana_program::{
+        account_info::{AccountInfo, next_account_info}, 
+        entrypoint::ProgramResult, 
+        program::invoke_signed,
+        pubkey::Pubkey,
+        rent::Rent,
+        system_instruction,
+        sysvar::Sysvar,
+    },
+};
+use crate::state::{
+    RentalOrder,
+    RentalOrderStatus,
+};
+
+#[derive(BorshDeserialize, BorshSerialize, Clone, Debug)]
+pub struct BookRentalArgs {
+    pub name: String,
+    pub pick_up_date: String,
+    pub return_date: String,
+    pub price: u64,
+}
+
+pub fn book_rental(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    args: BookRentalArgs,
+) -> ProgramResult {
+
+    let accounts_iter = &mut accounts.iter();
+    let rental_order_account = next_account_info(accounts_iter)?;
+    let car_account = next_account_info(accounts_iter)?;
+    let payer = next_account_info(accounts_iter)?;
+    let system_program = next_account_info(accounts_iter)?;
+
+    let (rental_order_account_pda, rental_order_account_bump) = RentalOrder::shank_pda(program_id, car_account.key, payer.key);
+    assert!(&rental_order_account_pda == rental_order_account.key);
+
+    let rental_order_data = RentalOrder {
+        car: *car_account.key,
+        name: args.name,
+        pick_up_date: args.pick_up_date,
+        return_date: args.return_date,
+        price: args.price,
+        status: RentalOrderStatus::Created,
+    };
+
+    let account_span = (rental_order_data.try_to_vec()?).len();
+    let lamports_required = (Rent::get()?).minimum_balance(account_span);
+
+    invoke_signed(
+        &system_instruction::create_account(
+            &payer.key,
+            &rental_order_account.key,
+            lamports_required,
+            account_span as u64,
+            program_id,
+        ),
+        &[
+            payer.clone(), rental_order_account.clone(), system_program.clone()
+        ],
+        RentalOrder::shank_seeds_with_bump(car_account.key, payer.key, &[rental_order_account_bump]),
+    )?;
+    
+    rental_order_data.serialize(&mut &mut rental_order_account.data.borrow_mut()[..])?;
+
+    Ok(())
+}

+ 55 - 0
tools/native/shank-and-solita/program/src/instructions/mod.rs

@@ -0,0 +1,55 @@
+pub mod add_car;
+pub mod book_rental;
+pub mod pick_up_car;
+pub mod return_car;
+
+pub use add_car::*;
+pub use book_rental::*;
+pub use pick_up_car::*;
+pub use return_car::*;
+
+use {
+    borsh::{
+        BorshDeserialize, 
+        BorshSerialize,
+    },
+    shank::ShankInstruction,
+};
+
+#[derive(BorshDeserialize, BorshSerialize, Clone, Debug, ShankInstruction)]
+pub enum CarRentalServiceInstruction {
+    
+    #[account(0, writable, name="car_account",
+              desc="The account that will represent the Car being created")]
+    #[account(1, writable, name="payer",
+            desc = "Fee payer")]
+    #[account(2, name="system_program",
+            desc = "The System Program")]
+    AddCar(AddCarArgs),
+
+    #[account(0, writable, name="rental_account",
+              desc="The account that will represent the actual order for the rental")]
+    #[account(1, name="car_account",
+              desc="The account representing the Car being rented in this order")]
+    #[account(2, writable, name="payer",
+            desc = "Fee payer")]
+    #[account(3, name="system_program",
+            desc = "The System Program")]
+    BookRental(BookRentalArgs),
+
+    #[account(0, writable, name="rental_account",
+              desc="The account representing the active rental")]
+    #[account(1, name="car_account",
+              desc="The account representing the Car being rented in this order")]
+    #[account(2, writable, name="payer",
+            desc = "Fee payer")]
+    PickUpCar,
+
+    #[account(0, writable, name="rental_account",
+              desc="The account representing the active rental")]
+    #[account(1, name="car_account",
+              desc="The account representing the Car being rented in this order")]
+    #[account(2, writable, name="payer",
+            desc = "Fee payer")]
+    ReturnCar,
+}

+ 42 - 0
tools/native/shank-and-solita/program/src/instructions/pick_up_car.rs

@@ -0,0 +1,42 @@
+use {
+    borsh::{
+        BorshDeserialize,
+        BorshSerialize,
+    },
+    solana_program::{
+        account_info::{AccountInfo, next_account_info}, 
+        entrypoint::ProgramResult, 
+        pubkey::Pubkey,
+    },
+};
+use crate::state::{
+    RentalOrder,
+    RentalOrderStatus,
+};
+
+pub fn pick_up_car(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+) -> ProgramResult {
+
+    let accounts_iter = &mut accounts.iter();
+    let rental_order_account = next_account_info(accounts_iter)?;
+    let car_account = next_account_info(accounts_iter)?;
+    let payer = next_account_info(accounts_iter)?;
+
+    let (rental_order_account_pda, _) = Pubkey::find_program_address(
+        &[
+            RentalOrder::SEED_PREFIX.as_bytes().as_ref(),
+            car_account.key.as_ref(),
+            payer.key.as_ref(),
+        ],
+        program_id,
+    );
+    assert!(&rental_order_account_pda == rental_order_account.key);
+
+    let rental_order = &mut RentalOrder::try_from_slice(&rental_order_account.data.borrow())?;
+    rental_order.status = RentalOrderStatus::PickedUp;
+    rental_order.serialize(&mut &mut rental_order_account.data.borrow_mut()[..])?;
+
+    Ok(())
+}

+ 42 - 0
tools/native/shank-and-solita/program/src/instructions/return_car.rs

@@ -0,0 +1,42 @@
+use {
+    borsh::{
+        BorshDeserialize,
+        BorshSerialize,
+    },
+    solana_program::{
+        account_info::{AccountInfo, next_account_info}, 
+        entrypoint::ProgramResult, 
+        pubkey::Pubkey,
+    },
+};
+use crate::state::{
+    RentalOrder,
+    RentalOrderStatus,
+};
+
+pub fn return_car(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+) -> ProgramResult {
+
+    let accounts_iter = &mut accounts.iter();
+    let rental_order_account = next_account_info(accounts_iter)?;
+    let car_account = next_account_info(accounts_iter)?;
+    let payer = next_account_info(accounts_iter)?;
+
+    let (rental_order_account_pda, _) = Pubkey::find_program_address(
+        &[
+            RentalOrder::SEED_PREFIX.as_bytes().as_ref(),
+            car_account.key.as_ref(),
+            payer.key.as_ref(),
+        ],
+        program_id,
+    );
+    assert!(&rental_order_account_pda == rental_order_account.key);
+
+    let rental_order = &mut RentalOrder::try_from_slice(&rental_order_account.data.borrow())?;
+    rental_order.status = RentalOrderStatus::Returned;
+    rental_order.serialize(&mut &mut rental_order_account.data.borrow_mut()[..])?;
+
+    Ok(())
+}

+ 32 - 0
tools/native/shank-and-solita/program/src/lib.rs

@@ -0,0 +1,32 @@
+mod instructions;
+mod state;
+
+use {
+    borsh::BorshDeserialize,
+    solana_program::{
+        account_info::AccountInfo, 
+        declare_id,
+        entrypoint, 
+        entrypoint::ProgramResult, 
+        pubkey::Pubkey,
+    },
+};
+use crate::instructions::*;
+
+declare_id!("8avNGHVXDwsELJaWMSoUZ44CirQd4zyU9Ez4ZmP4jNjZ");
+entrypoint!(process_instruction);
+
+pub fn process_instruction(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    instruction_data: &[u8],
+) -> ProgramResult {
+
+    let instruction = CarRentalServiceInstruction::try_from_slice(instruction_data)?;
+    match instruction {
+        CarRentalServiceInstruction::AddCar(car) => add_car(program_id, accounts, car),
+        CarRentalServiceInstruction::BookRental(order) => book_rental(program_id, accounts, order),
+        CarRentalServiceInstruction::PickUpCar => pick_up_car(program_id, accounts),
+        CarRentalServiceInstruction::ReturnCar => return_car(program_id, accounts),
+    }
+}

+ 48 - 0
tools/native/shank-and-solita/program/src/state/mod.rs

@@ -0,0 +1,48 @@
+use {
+    borsh::{
+        BorshDeserialize, 
+        BorshSerialize 
+    },
+    shank::ShankAccount,
+    solana_program::pubkey::Pubkey,
+};
+
+#[derive(BorshDeserialize, BorshSerialize, Clone, Debug, ShankAccount)]
+#[seeds(
+    "car",
+    program_id,
+    make("The car's make", String),
+    model("The car's model", String),
+)]
+pub struct Car {
+    pub year: u16,
+    pub make: String,
+    pub model: String,
+}
+
+#[derive(BorshDeserialize, BorshSerialize, Clone, Debug)]
+pub enum RentalOrderStatus {
+    Created,
+    PickedUp,
+    Returned,
+}
+
+#[derive(BorshDeserialize, BorshSerialize, Clone, Debug, ShankAccount)]
+#[seeds(
+    "rental_order",
+    program_id,
+    car_public_key("The car's public key", Pubkey),
+    payer_public_key("The payer's public key", Pubkey),
+)]
+pub struct RentalOrder {
+    pub car: Pubkey,
+    pub name: String,
+    pub pick_up_date: String,
+    pub return_date: String,
+    pub price: u64,
+    pub status: RentalOrderStatus,
+}
+
+impl RentalOrder {
+    pub const SEED_PREFIX: &'static str = "rental_order";
+}

+ 161 - 0
tools/native/shank-and-solita/tests/generated/accounts/Car.ts

@@ -0,0 +1,161 @@
+/**
+ * This code was GENERATED using the solita package.
+ * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality.
+ *
+ * See: https://github.com/metaplex-foundation/solita
+ */
+
+import * as beet from '@metaplex-foundation/beet'
+import * as web3 from '@solana/web3.js'
+import * as beetSolana from '@metaplex-foundation/beet-solana'
+
+/**
+ * Arguments used to create {@link Car}
+ * @category Accounts
+ * @category generated
+ */
+export type CarArgs = {
+  year: number
+  make: string
+  model: string
+}
+/**
+ * Holds the data for the {@link Car} Account and provides de/serialization
+ * functionality for that data
+ *
+ * @category Accounts
+ * @category generated
+ */
+export class Car implements CarArgs {
+  private constructor(
+    readonly year: number,
+    readonly make: string,
+    readonly model: string
+  ) {}
+
+  /**
+   * Creates a {@link Car} instance from the provided args.
+   */
+  static fromArgs(args: CarArgs) {
+    return new Car(args.year, args.make, args.model)
+  }
+
+  /**
+   * Deserializes the {@link Car} from the data of the provided {@link web3.AccountInfo}.
+   * @returns a tuple of the account data and the offset up to which the buffer was read to obtain it.
+   */
+  static fromAccountInfo(
+    accountInfo: web3.AccountInfo<Buffer>,
+    offset = 0
+  ): [Car, number] {
+    return Car.deserialize(accountInfo.data, offset)
+  }
+
+  /**
+   * Retrieves the account info from the provided address and deserializes
+   * the {@link Car} from its data.
+   *
+   * @throws Error if no account info is found at the address or if deserialization fails
+   */
+  static async fromAccountAddress(
+    connection: web3.Connection,
+    address: web3.PublicKey,
+    commitmentOrConfig?: web3.Commitment | web3.GetAccountInfoConfig
+  ): Promise<Car> {
+    const accountInfo = await connection.getAccountInfo(
+      address,
+      commitmentOrConfig
+    )
+    if (accountInfo == null) {
+      throw new Error(`Unable to find Car account at ${address}`)
+    }
+    return Car.fromAccountInfo(accountInfo, 0)[0]
+  }
+
+  /**
+   * Provides a {@link web3.Connection.getProgramAccounts} config builder,
+   * to fetch accounts matching filters that can be specified via that builder.
+   *
+   * @param programId - the program that owns the accounts we are filtering
+   */
+  static gpaBuilder(
+    programId: web3.PublicKey = new web3.PublicKey(
+      '8avNGHVXDwsELJaWMSoUZ44CirQd4zyU9Ez4ZmP4jNjZ'
+    )
+  ) {
+    return beetSolana.GpaBuilder.fromStruct(programId, carBeet)
+  }
+
+  /**
+   * Deserializes the {@link Car} from the provided data Buffer.
+   * @returns a tuple of the account data and the offset up to which the buffer was read to obtain it.
+   */
+  static deserialize(buf: Buffer, offset = 0): [Car, number] {
+    return carBeet.deserialize(buf, offset)
+  }
+
+  /**
+   * Serializes the {@link Car} into a Buffer.
+   * @returns a tuple of the created Buffer and the offset up to which the buffer was written to store it.
+   */
+  serialize(): [Buffer, number] {
+    return carBeet.serialize(this)
+  }
+
+  /**
+   * Returns the byteSize of a {@link Buffer} holding the serialized data of
+   * {@link Car} for the provided args.
+   *
+   * @param args need to be provided since the byte size for this account
+   * depends on them
+   */
+  static byteSize(args: CarArgs) {
+    const instance = Car.fromArgs(args)
+    return carBeet.toFixedFromValue(instance).byteSize
+  }
+
+  /**
+   * Fetches the minimum balance needed to exempt an account holding
+   * {@link Car} data from rent
+   *
+   * @param args need to be provided since the byte size for this account
+   * depends on them
+   * @param connection used to retrieve the rent exemption information
+   */
+  static async getMinimumBalanceForRentExemption(
+    args: CarArgs,
+    connection: web3.Connection,
+    commitment?: web3.Commitment
+  ): Promise<number> {
+    return connection.getMinimumBalanceForRentExemption(
+      Car.byteSize(args),
+      commitment
+    )
+  }
+
+  /**
+   * Returns a readable version of {@link Car} properties
+   * and can be used to convert to JSON and/or logging
+   */
+  pretty() {
+    return {
+      year: this.year,
+      make: this.make,
+      model: this.model,
+    }
+  }
+}
+
+/**
+ * @category Accounts
+ * @category generated
+ */
+export const carBeet = new beet.FixableBeetStruct<Car, CarArgs>(
+  [
+    ['year', beet.u16],
+    ['make', beet.utf8String],
+    ['model', beet.utf8String],
+  ],
+  Car.fromArgs,
+  'Car'
+)

+ 197 - 0
tools/native/shank-and-solita/tests/generated/accounts/RentalOrder.ts

@@ -0,0 +1,197 @@
+/**
+ * This code was GENERATED using the solita package.
+ * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality.
+ *
+ * See: https://github.com/metaplex-foundation/solita
+ */
+
+import * as web3 from '@solana/web3.js'
+import * as beet from '@metaplex-foundation/beet'
+import * as beetSolana from '@metaplex-foundation/beet-solana'
+import {
+  RentalOrderStatus,
+  rentalOrderStatusBeet,
+} from '../types/RentalOrderStatus'
+
+/**
+ * Arguments used to create {@link RentalOrder}
+ * @category Accounts
+ * @category generated
+ */
+export type RentalOrderArgs = {
+  car: web3.PublicKey
+  name: string
+  pickUpDate: string
+  returnDate: string
+  price: beet.bignum
+  status: RentalOrderStatus
+}
+/**
+ * Holds the data for the {@link RentalOrder} Account and provides de/serialization
+ * functionality for that data
+ *
+ * @category Accounts
+ * @category generated
+ */
+export class RentalOrder implements RentalOrderArgs {
+  private constructor(
+    readonly car: web3.PublicKey,
+    readonly name: string,
+    readonly pickUpDate: string,
+    readonly returnDate: string,
+    readonly price: beet.bignum,
+    readonly status: RentalOrderStatus
+  ) {}
+
+  /**
+   * Creates a {@link RentalOrder} instance from the provided args.
+   */
+  static fromArgs(args: RentalOrderArgs) {
+    return new RentalOrder(
+      args.car,
+      args.name,
+      args.pickUpDate,
+      args.returnDate,
+      args.price,
+      args.status
+    )
+  }
+
+  /**
+   * Deserializes the {@link RentalOrder} from the data of the provided {@link web3.AccountInfo}.
+   * @returns a tuple of the account data and the offset up to which the buffer was read to obtain it.
+   */
+  static fromAccountInfo(
+    accountInfo: web3.AccountInfo<Buffer>,
+    offset = 0
+  ): [RentalOrder, number] {
+    return RentalOrder.deserialize(accountInfo.data, offset)
+  }
+
+  /**
+   * Retrieves the account info from the provided address and deserializes
+   * the {@link RentalOrder} from its data.
+   *
+   * @throws Error if no account info is found at the address or if deserialization fails
+   */
+  static async fromAccountAddress(
+    connection: web3.Connection,
+    address: web3.PublicKey,
+    commitmentOrConfig?: web3.Commitment | web3.GetAccountInfoConfig
+  ): Promise<RentalOrder> {
+    const accountInfo = await connection.getAccountInfo(
+      address,
+      commitmentOrConfig
+    )
+    if (accountInfo == null) {
+      throw new Error(`Unable to find RentalOrder account at ${address}`)
+    }
+    return RentalOrder.fromAccountInfo(accountInfo, 0)[0]
+  }
+
+  /**
+   * Provides a {@link web3.Connection.getProgramAccounts} config builder,
+   * to fetch accounts matching filters that can be specified via that builder.
+   *
+   * @param programId - the program that owns the accounts we are filtering
+   */
+  static gpaBuilder(
+    programId: web3.PublicKey = new web3.PublicKey(
+      '8avNGHVXDwsELJaWMSoUZ44CirQd4zyU9Ez4ZmP4jNjZ'
+    )
+  ) {
+    return beetSolana.GpaBuilder.fromStruct(programId, rentalOrderBeet)
+  }
+
+  /**
+   * Deserializes the {@link RentalOrder} from the provided data Buffer.
+   * @returns a tuple of the account data and the offset up to which the buffer was read to obtain it.
+   */
+  static deserialize(buf: Buffer, offset = 0): [RentalOrder, number] {
+    return rentalOrderBeet.deserialize(buf, offset)
+  }
+
+  /**
+   * Serializes the {@link RentalOrder} into a Buffer.
+   * @returns a tuple of the created Buffer and the offset up to which the buffer was written to store it.
+   */
+  serialize(): [Buffer, number] {
+    return rentalOrderBeet.serialize(this)
+  }
+
+  /**
+   * Returns the byteSize of a {@link Buffer} holding the serialized data of
+   * {@link RentalOrder} for the provided args.
+   *
+   * @param args need to be provided since the byte size for this account
+   * depends on them
+   */
+  static byteSize(args: RentalOrderArgs) {
+    const instance = RentalOrder.fromArgs(args)
+    return rentalOrderBeet.toFixedFromValue(instance).byteSize
+  }
+
+  /**
+   * Fetches the minimum balance needed to exempt an account holding
+   * {@link RentalOrder} data from rent
+   *
+   * @param args need to be provided since the byte size for this account
+   * depends on them
+   * @param connection used to retrieve the rent exemption information
+   */
+  static async getMinimumBalanceForRentExemption(
+    args: RentalOrderArgs,
+    connection: web3.Connection,
+    commitment?: web3.Commitment
+  ): Promise<number> {
+    return connection.getMinimumBalanceForRentExemption(
+      RentalOrder.byteSize(args),
+      commitment
+    )
+  }
+
+  /**
+   * Returns a readable version of {@link RentalOrder} properties
+   * and can be used to convert to JSON and/or logging
+   */
+  pretty() {
+    return {
+      car: this.car.toBase58(),
+      name: this.name,
+      pickUpDate: this.pickUpDate,
+      returnDate: this.returnDate,
+      price: (() => {
+        const x = <{ toNumber: () => number }>this.price
+        if (typeof x.toNumber === 'function') {
+          try {
+            return x.toNumber()
+          } catch (_) {
+            return x
+          }
+        }
+        return x
+      })(),
+      status: 'RentalOrderStatus.' + RentalOrderStatus[this.status],
+    }
+  }
+}
+
+/**
+ * @category Accounts
+ * @category generated
+ */
+export const rentalOrderBeet = new beet.FixableBeetStruct<
+  RentalOrder,
+  RentalOrderArgs
+>(
+  [
+    ['car', beetSolana.publicKey],
+    ['name', beet.utf8String],
+    ['pickUpDate', beet.utf8String],
+    ['returnDate', beet.utf8String],
+    ['price', beet.u64],
+    ['status', rentalOrderStatusBeet],
+  ],
+  RentalOrder.fromArgs,
+  'RentalOrder'
+)

+ 7 - 0
tools/native/shank-and-solita/tests/generated/accounts/index.ts

@@ -0,0 +1,7 @@
+export * from './Car'
+export * from './RentalOrder'
+
+import { Car } from './Car'
+import { RentalOrder } from './RentalOrder'
+
+export const accountProviders = { Car, RentalOrder }

+ 20 - 0
tools/native/shank-and-solita/tests/generated/index.ts

@@ -0,0 +1,20 @@
+import { PublicKey } from '@solana/web3.js'
+export * from './accounts'
+export * from './instructions'
+export * from './types'
+
+/**
+ * Program address
+ *
+ * @category constants
+ * @category generated
+ */
+export const PROGRAM_ADDRESS = '8avNGHVXDwsELJaWMSoUZ44CirQd4zyU9Ez4ZmP4jNjZ'
+
+/**
+ * Program public key
+ *
+ * @category constants
+ * @category generated
+ */
+export const PROGRAM_ID = new PublicKey(PROGRAM_ADDRESS)

+ 96 - 0
tools/native/shank-and-solita/tests/generated/instructions/AddCar.ts

@@ -0,0 +1,96 @@
+/**
+ * This code was GENERATED using the solita package.
+ * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality.
+ *
+ * See: https://github.com/metaplex-foundation/solita
+ */
+
+import * as beet from '@metaplex-foundation/beet'
+import * as web3 from '@solana/web3.js'
+import { AddCarArgs, addCarArgsBeet } from '../types/AddCarArgs'
+
+/**
+ * @category Instructions
+ * @category AddCar
+ * @category generated
+ */
+export type AddCarInstructionArgs = {
+  addCarArgs: AddCarArgs
+}
+/**
+ * @category Instructions
+ * @category AddCar
+ * @category generated
+ */
+export const AddCarStruct = new beet.FixableBeetArgsStruct<
+  AddCarInstructionArgs & {
+    instructionDiscriminator: number
+  }
+>(
+  [
+    ['instructionDiscriminator', beet.u8],
+    ['addCarArgs', addCarArgsBeet],
+  ],
+  'AddCarInstructionArgs'
+)
+/**
+ * Accounts required by the _AddCar_ instruction
+ *
+ * @property [_writable_] carAccount The account that will represent the Car being created
+ * @property [_writable_] payer Fee payer
+ * @category Instructions
+ * @category AddCar
+ * @category generated
+ */
+export type AddCarInstructionAccounts = {
+  carAccount: web3.PublicKey
+  payer: web3.PublicKey
+  systemProgram?: web3.PublicKey
+}
+
+export const addCarInstructionDiscriminator = 0
+
+/**
+ * Creates a _AddCar_ instruction.
+ *
+ * @param accounts that will be accessed while the instruction is processed
+ * @param args to provide as instruction data to the program
+ *
+ * @category Instructions
+ * @category AddCar
+ * @category generated
+ */
+export function createAddCarInstruction(
+  accounts: AddCarInstructionAccounts,
+  args: AddCarInstructionArgs,
+  programId = new web3.PublicKey('8avNGHVXDwsELJaWMSoUZ44CirQd4zyU9Ez4ZmP4jNjZ')
+) {
+  const [data] = AddCarStruct.serialize({
+    instructionDiscriminator: addCarInstructionDiscriminator,
+    ...args,
+  })
+  const keys: web3.AccountMeta[] = [
+    {
+      pubkey: accounts.carAccount,
+      isWritable: true,
+      isSigner: false,
+    },
+    {
+      pubkey: accounts.payer,
+      isWritable: true,
+      isSigner: false,
+    },
+    {
+      pubkey: accounts.systemProgram ?? web3.SystemProgram.programId,
+      isWritable: false,
+      isSigner: false,
+    },
+  ]
+
+  const ix = new web3.TransactionInstruction({
+    programId,
+    keys,
+    data,
+  })
+  return ix
+}

+ 103 - 0
tools/native/shank-and-solita/tests/generated/instructions/BookRental.ts

@@ -0,0 +1,103 @@
+/**
+ * This code was GENERATED using the solita package.
+ * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality.
+ *
+ * See: https://github.com/metaplex-foundation/solita
+ */
+
+import * as beet from '@metaplex-foundation/beet'
+import * as web3 from '@solana/web3.js'
+import { BookRentalArgs, bookRentalArgsBeet } from '../types/BookRentalArgs'
+
+/**
+ * @category Instructions
+ * @category BookRental
+ * @category generated
+ */
+export type BookRentalInstructionArgs = {
+  bookRentalArgs: BookRentalArgs
+}
+/**
+ * @category Instructions
+ * @category BookRental
+ * @category generated
+ */
+export const BookRentalStruct = new beet.FixableBeetArgsStruct<
+  BookRentalInstructionArgs & {
+    instructionDiscriminator: number
+  }
+>(
+  [
+    ['instructionDiscriminator', beet.u8],
+    ['bookRentalArgs', bookRentalArgsBeet],
+  ],
+  'BookRentalInstructionArgs'
+)
+/**
+ * Accounts required by the _BookRental_ instruction
+ *
+ * @property [_writable_] rentalAccount The account that will represent the actual order for the rental
+ * @property [] carAccount The account representing the Car being rented in this order
+ * @property [_writable_] payer Fee payer
+ * @category Instructions
+ * @category BookRental
+ * @category generated
+ */
+export type BookRentalInstructionAccounts = {
+  rentalAccount: web3.PublicKey
+  carAccount: web3.PublicKey
+  payer: web3.PublicKey
+  systemProgram?: web3.PublicKey
+}
+
+export const bookRentalInstructionDiscriminator = 1
+
+/**
+ * Creates a _BookRental_ instruction.
+ *
+ * @param accounts that will be accessed while the instruction is processed
+ * @param args to provide as instruction data to the program
+ *
+ * @category Instructions
+ * @category BookRental
+ * @category generated
+ */
+export function createBookRentalInstruction(
+  accounts: BookRentalInstructionAccounts,
+  args: BookRentalInstructionArgs,
+  programId = new web3.PublicKey('8avNGHVXDwsELJaWMSoUZ44CirQd4zyU9Ez4ZmP4jNjZ')
+) {
+  const [data] = BookRentalStruct.serialize({
+    instructionDiscriminator: bookRentalInstructionDiscriminator,
+    ...args,
+  })
+  const keys: web3.AccountMeta[] = [
+    {
+      pubkey: accounts.rentalAccount,
+      isWritable: true,
+      isSigner: false,
+    },
+    {
+      pubkey: accounts.carAccount,
+      isWritable: false,
+      isSigner: false,
+    },
+    {
+      pubkey: accounts.payer,
+      isWritable: true,
+      isSigner: false,
+    },
+    {
+      pubkey: accounts.systemProgram ?? web3.SystemProgram.programId,
+      isWritable: false,
+      isSigner: false,
+    },
+  ]
+
+  const ix = new web3.TransactionInstruction({
+    programId,
+    keys,
+    data,
+  })
+  return ix
+}

+ 76 - 0
tools/native/shank-and-solita/tests/generated/instructions/PickUpCar.ts

@@ -0,0 +1,76 @@
+/**
+ * This code was GENERATED using the solita package.
+ * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality.
+ *
+ * See: https://github.com/metaplex-foundation/solita
+ */
+
+import * as beet from '@metaplex-foundation/beet'
+import * as web3 from '@solana/web3.js'
+
+/**
+ * @category Instructions
+ * @category PickUpCar
+ * @category generated
+ */
+export const PickUpCarStruct = new beet.BeetArgsStruct<{
+  instructionDiscriminator: number
+}>([['instructionDiscriminator', beet.u8]], 'PickUpCarInstructionArgs')
+/**
+ * Accounts required by the _PickUpCar_ instruction
+ *
+ * @property [_writable_] rentalAccount The account representing the active rental
+ * @property [] carAccount The account representing the Car being rented in this order
+ * @property [_writable_] payer Fee payer
+ * @category Instructions
+ * @category PickUpCar
+ * @category generated
+ */
+export type PickUpCarInstructionAccounts = {
+  rentalAccount: web3.PublicKey
+  carAccount: web3.PublicKey
+  payer: web3.PublicKey
+}
+
+export const pickUpCarInstructionDiscriminator = 2
+
+/**
+ * Creates a _PickUpCar_ instruction.
+ *
+ * @param accounts that will be accessed while the instruction is processed
+ * @category Instructions
+ * @category PickUpCar
+ * @category generated
+ */
+export function createPickUpCarInstruction(
+  accounts: PickUpCarInstructionAccounts,
+  programId = new web3.PublicKey('8avNGHVXDwsELJaWMSoUZ44CirQd4zyU9Ez4ZmP4jNjZ')
+) {
+  const [data] = PickUpCarStruct.serialize({
+    instructionDiscriminator: pickUpCarInstructionDiscriminator,
+  })
+  const keys: web3.AccountMeta[] = [
+    {
+      pubkey: accounts.rentalAccount,
+      isWritable: true,
+      isSigner: false,
+    },
+    {
+      pubkey: accounts.carAccount,
+      isWritable: false,
+      isSigner: false,
+    },
+    {
+      pubkey: accounts.payer,
+      isWritable: true,
+      isSigner: false,
+    },
+  ]
+
+  const ix = new web3.TransactionInstruction({
+    programId,
+    keys,
+    data,
+  })
+  return ix
+}

+ 76 - 0
tools/native/shank-and-solita/tests/generated/instructions/ReturnCar.ts

@@ -0,0 +1,76 @@
+/**
+ * This code was GENERATED using the solita package.
+ * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality.
+ *
+ * See: https://github.com/metaplex-foundation/solita
+ */
+
+import * as beet from '@metaplex-foundation/beet'
+import * as web3 from '@solana/web3.js'
+
+/**
+ * @category Instructions
+ * @category ReturnCar
+ * @category generated
+ */
+export const ReturnCarStruct = new beet.BeetArgsStruct<{
+  instructionDiscriminator: number
+}>([['instructionDiscriminator', beet.u8]], 'ReturnCarInstructionArgs')
+/**
+ * Accounts required by the _ReturnCar_ instruction
+ *
+ * @property [_writable_] rentalAccount The account representing the active rental
+ * @property [] carAccount The account representing the Car being rented in this order
+ * @property [_writable_] payer Fee payer
+ * @category Instructions
+ * @category ReturnCar
+ * @category generated
+ */
+export type ReturnCarInstructionAccounts = {
+  rentalAccount: web3.PublicKey
+  carAccount: web3.PublicKey
+  payer: web3.PublicKey
+}
+
+export const returnCarInstructionDiscriminator = 3
+
+/**
+ * Creates a _ReturnCar_ instruction.
+ *
+ * @param accounts that will be accessed while the instruction is processed
+ * @category Instructions
+ * @category ReturnCar
+ * @category generated
+ */
+export function createReturnCarInstruction(
+  accounts: ReturnCarInstructionAccounts,
+  programId = new web3.PublicKey('8avNGHVXDwsELJaWMSoUZ44CirQd4zyU9Ez4ZmP4jNjZ')
+) {
+  const [data] = ReturnCarStruct.serialize({
+    instructionDiscriminator: returnCarInstructionDiscriminator,
+  })
+  const keys: web3.AccountMeta[] = [
+    {
+      pubkey: accounts.rentalAccount,
+      isWritable: true,
+      isSigner: false,
+    },
+    {
+      pubkey: accounts.carAccount,
+      isWritable: false,
+      isSigner: false,
+    },
+    {
+      pubkey: accounts.payer,
+      isWritable: true,
+      isSigner: false,
+    },
+  ]
+
+  const ix = new web3.TransactionInstruction({
+    programId,
+    keys,
+    data,
+  })
+  return ix
+}

+ 4 - 0
tools/native/shank-and-solita/tests/generated/instructions/index.ts

@@ -0,0 +1,4 @@
+export * from './AddCar'
+export * from './BookRental'
+export * from './PickUpCar'
+export * from './ReturnCar'

+ 26 - 0
tools/native/shank-and-solita/tests/generated/types/AddCarArgs.ts

@@ -0,0 +1,26 @@
+/**
+ * This code was GENERATED using the solita package.
+ * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality.
+ *
+ * See: https://github.com/metaplex-foundation/solita
+ */
+
+import * as beet from '@metaplex-foundation/beet'
+export type AddCarArgs = {
+  year: number
+  make: string
+  model: string
+}
+
+/**
+ * @category userTypes
+ * @category generated
+ */
+export const addCarArgsBeet = new beet.FixableBeetArgsStruct<AddCarArgs>(
+  [
+    ['year', beet.u16],
+    ['make', beet.utf8String],
+    ['model', beet.utf8String],
+  ],
+  'AddCarArgs'
+)

+ 29 - 0
tools/native/shank-and-solita/tests/generated/types/BookRentalArgs.ts

@@ -0,0 +1,29 @@
+/**
+ * This code was GENERATED using the solita package.
+ * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality.
+ *
+ * See: https://github.com/metaplex-foundation/solita
+ */
+
+import * as beet from '@metaplex-foundation/beet'
+export type BookRentalArgs = {
+  name: string
+  pickUpDate: string
+  returnDate: string
+  price: beet.bignum
+}
+
+/**
+ * @category userTypes
+ * @category generated
+ */
+export const bookRentalArgsBeet =
+  new beet.FixableBeetArgsStruct<BookRentalArgs>(
+    [
+      ['name', beet.utf8String],
+      ['pickUpDate', beet.utf8String],
+      ['returnDate', beet.utf8String],
+      ['price', beet.u64],
+    ],
+    'BookRentalArgs'
+  )

+ 25 - 0
tools/native/shank-and-solita/tests/generated/types/RentalOrderStatus.ts

@@ -0,0 +1,25 @@
+/**
+ * This code was GENERATED using the solita package.
+ * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality.
+ *
+ * See: https://github.com/metaplex-foundation/solita
+ */
+
+import * as beet from '@metaplex-foundation/beet'
+/**
+ * @category enums
+ * @category generated
+ */
+export enum RentalOrderStatus {
+  Created,
+  PickedUp,
+  Returned,
+}
+
+/**
+ * @category userTypes
+ * @category generated
+ */
+export const rentalOrderStatusBeet = beet.fixedScalarEnum(
+  RentalOrderStatus
+) as beet.FixedSizeBeet<RentalOrderStatus, RentalOrderStatus>

+ 3 - 0
tools/native/shank-and-solita/tests/generated/types/index.ts

@@ -0,0 +1,3 @@
+export * from './AddCarArgs'
+export * from './BookRentalArgs'
+export * from './RentalOrderStatus'

+ 194 - 0
tools/native/shank-and-solita/tests/test.ts

@@ -0,0 +1,194 @@
+import {
+    it,
+    describe,
+} from 'mocha'
+import {
+    Connection,
+    Keypair,
+    PublicKey,
+    sendAndConfirmTransaction,
+    SystemProgram,
+    Transaction,
+} from '@solana/web3.js'
+import {
+    AddCarArgs,
+    Car,
+    RentalOrder,
+    RentalOrderStatus,
+    createAddCarInstruction,
+    createBookRentalInstruction,
+    createPickUpCarInstruction,
+    createReturnCarInstruction,
+} from './generated'
+
+
+function loadKeypairFromFile(path: string): Keypair {
+    return Keypair.fromSecretKey(
+        Buffer.from(JSON.parse(require('fs').readFileSync(path, "utf-8")))
+    )
+}
+
+const carBmw: AddCarArgs = {
+    year: 2020,
+    make: 'BMW',
+    model: 'iX1',
+}
+
+const carMercedes: AddCarArgs = {
+    year: 2019,
+    make: 'Mercedes-Benz',
+    model: 'EQS',
+}
+
+const rentalInfo = {
+    name: "Fred Flinstone",
+    pickUpDate: "01/28/2023 8:00 AM",
+    returnDate: "01/28/2023 10:00 PM",
+    price: 300,
+}
+
+
+describe("Car Rental Service", () => {
+
+    const connection = new Connection(`https://api.devnet.solana.com`, 'confirmed')
+    const payer = loadKeypairFromFile(require('os').homedir() + '/.config/solana/id.json')
+    const program = loadKeypairFromFile('./program/target/deploy/car_rental_service-keypair.json')
+
+    let bmwPublicKey: PublicKey
+    let mercedesPublicKey: PublicKey
+
+    async function createCar(car: AddCarArgs): Promise<PublicKey> {
+        const carAccountPublicKey = PublicKey.findProgramAddressSync(
+            [
+                Buffer.from('car'),
+                Buffer.from(car.make),
+                Buffer.from(car.model),
+            ],
+            program.publicKey,
+        )[0]
+        const ix = createAddCarInstruction(
+            {
+                carAccount: carAccountPublicKey,
+                payer: payer.publicKey,
+                systemProgram: SystemProgram.programId,
+            },
+            { addCarArgs: { ...car } },
+        )
+        const sx = await sendAndConfirmTransaction(
+            connection,
+            new Transaction().add(ix),
+            [payer],
+            { skipPreflight: true }
+        )
+        await connection.confirmTransaction(sx)
+        const carData = await Car.fromAccountAddress(connection, carAccountPublicKey)
+        console.log('New car created:')
+        console.log(`   Year    : ${carData.year}`)
+        console.log(`   Make    : ${carData.make}`)
+        console.log(`   Model   : ${carData.model}`)
+        return carAccountPublicKey
+    }
+    it("Create a car that can be rented", async () => { bmwPublicKey = await createCar(carBmw) })
+    it("Create another car that can be rented", async () => { mercedesPublicKey = await createCar(carMercedes) })
+
+    const evaluateStatus = (status: RentalOrderStatus): string => {
+        if (status === RentalOrderStatus.Created) return "Created"
+        if (status === RentalOrderStatus.PickedUp) return "Picked Up"
+        return "Returned"
+    }
+
+    async function printRentalDetails(rentalPublicKey: PublicKey, carPublicKey: PublicKey) {
+        const rentalData = await RentalOrder.fromAccountAddress(connection, rentalPublicKey)
+        const carData = await Car.fromAccountAddress(connection, carPublicKey)
+        console.log('Rental booked:')
+        console.log('   Vehicle details:')
+        console.log(`       Year    : ${carData.year}`)
+        console.log(`       Make    : ${carData.make}`)
+        console.log(`       Model   : ${carData.model}`)
+        console.log(`   Name    : ${rentalData.name}`)
+        console.log(`   Pick Up : ${rentalData.pickUpDate}`)
+        console.log(`   Return  : ${rentalData.returnDate}`)
+        console.log(`   Price   : ${rentalData.price}`)
+        console.log(`   Status  : ${evaluateStatus(rentalData.status)}`)
+    }
+
+    it("Book a new rental", async () => {
+        const rentalAccountPublicKey = PublicKey.findProgramAddressSync(
+            [
+                Buffer.from('rental_order'),
+                bmwPublicKey.toBuffer(),
+                payer.publicKey.toBuffer(),
+            ],
+            program.publicKey,
+        )[0]
+        const ix = createBookRentalInstruction(
+            {
+                rentalAccount: rentalAccountPublicKey,
+                carAccount: bmwPublicKey,
+                payer: payer.publicKey,
+                systemProgram: SystemProgram.programId,
+            },
+            {
+                bookRentalArgs: { ...rentalInfo }
+            },
+        )
+        const sx = await sendAndConfirmTransaction(
+            connection,
+            new Transaction().add(ix),
+            [payer]
+        )
+        await connection.confirmTransaction(sx)
+        await printRentalDetails(rentalAccountPublicKey, bmwPublicKey)
+    })
+
+    it("Pick up your rental car", async () => {
+        const rentalAccountPublicKey = PublicKey.findProgramAddressSync(
+            [
+                Buffer.from('rental_order'),
+                bmwPublicKey.toBuffer(),
+                payer.publicKey.toBuffer(),
+            ],
+            program.publicKey,
+        )[0]
+        const ix = createPickUpCarInstruction(
+            {
+                rentalAccount: rentalAccountPublicKey,
+                carAccount: bmwPublicKey,
+                payer: payer.publicKey,
+            }
+        )
+        const sx = await sendAndConfirmTransaction(
+            connection,
+            new Transaction().add(ix),
+            [payer]
+        )
+        await connection.confirmTransaction(sx)
+        await printRentalDetails(rentalAccountPublicKey, bmwPublicKey)
+    })
+
+    it("Return your rental car", async () => {
+        const rentalAccountPublicKey = PublicKey.findProgramAddressSync(
+            [
+                Buffer.from('rental_order'),
+                bmwPublicKey.toBuffer(),
+                payer.publicKey.toBuffer(),
+            ],
+            program.publicKey,
+        )[0]
+        const ix = createReturnCarInstruction(
+            {
+                rentalAccount: rentalAccountPublicKey,
+                carAccount: bmwPublicKey,
+                payer: payer.publicKey,
+            }
+        )
+        const sx = await sendAndConfirmTransaction(
+            connection,
+            new Transaction().add(ix),
+            [payer]
+        )
+        await connection.confirmTransaction(sx)
+        await printRentalDetails(rentalAccountPublicKey, bmwPublicKey)
+    })
+  })
+  

+ 10 - 0
tools/native/shank-and-solita/tests/tsconfig.test.json

@@ -0,0 +1,10 @@
+{
+    "compilerOptions": {
+      "types": ["mocha", "chai"],
+      "typeRoots": ["./node_modules/@types"],
+      "lib": ["es2015"],
+      "module": "commonjs",
+      "target": "es6",
+      "esModuleInterop": true
+    }
+  }