Browse Source

Merge pull request #6 from ngundotra/counter

Add Counter Program example implementations
Joe Caulfield 3 years ago
parent
commit
3ed97dad8b
56 changed files with 1671 additions and 0 deletions
  1. 14 0
      basics/counter/README.md
  2. 7 0
      basics/counter/anchor/.gitignore
  3. 8 0
      basics/counter/anchor/.prettierignore
  4. 15 0
      basics/counter/anchor/Anchor.toml
  5. 13 0
      basics/counter/anchor/Cargo.toml
  6. 5 0
      basics/counter/anchor/README.md
  7. 12 0
      basics/counter/anchor/migrations/deploy.ts
  8. 19 0
      basics/counter/anchor/package.json
  9. 19 0
      basics/counter/anchor/programs/counter_anchor/Cargo.toml
  10. 2 0
      basics/counter/anchor/programs/counter_anchor/Xargo.toml
  11. 37 0
      basics/counter/anchor/programs/counter_anchor/src/lib.rs
  12. 44 0
      basics/counter/anchor/tests/counter_anchor.ts
  13. 10 0
      basics/counter/anchor/tsconfig.json
  14. 16 0
      basics/counter/mpl-stack/.solitarc.js
  15. 17 0
      basics/counter/mpl-stack/Cargo.toml
  16. 13 0
      basics/counter/mpl-stack/README.md
  17. 42 0
      basics/counter/mpl-stack/idl/counter_mpl_stack.json
  18. 33 0
      basics/counter/mpl-stack/idl/counter_solana_native.json
  19. 6 0
      basics/counter/mpl-stack/jest.config.js
  20. 30 0
      basics/counter/mpl-stack/package.json
  21. 67 0
      basics/counter/mpl-stack/src/lib.rs
  22. 7 0
      basics/counter/mpl-stack/src/state.rs
  23. 119 0
      basics/counter/mpl-stack/tests/counter.test.ts
  24. 156 0
      basics/counter/mpl-stack/ts/generated/accounts/Counter.ts
  25. 5 0
      basics/counter/mpl-stack/ts/generated/accounts/index.ts
  26. 19 0
      basics/counter/mpl-stack/ts/generated/index.ts
  27. 62 0
      basics/counter/mpl-stack/ts/generated/instructions/Increment.ts
  28. 1 0
      basics/counter/mpl-stack/ts/generated/instructions/index.ts
  29. 1 0
      basics/counter/mpl-stack/ts/index.ts
  30. 16 0
      basics/counter/native/Cargo.toml
  31. 14 0
      basics/counter/native/README.md
  32. 6 0
      basics/counter/native/jest.config.js
  33. 28 0
      basics/counter/native/package.json
  34. 60 0
      basics/counter/native/src/lib.rs
  35. 6 0
      basics/counter/native/src/state.rs
  36. 123 0
      basics/counter/native/tests/counter.test.ts
  37. 17 0
      basics/counter/native/ts/accounts/counter.ts
  38. 1 0
      basics/counter/native/ts/accounts/index.ts
  39. 5 0
      basics/counter/native/ts/index.ts
  40. 25 0
      basics/counter/native/ts/instructions/createIncrementInstruction.ts
  41. 1 0
      basics/counter/native/ts/instructions/index.ts
  42. 7 0
      basics/counter/seahorse/.gitignore
  43. 8 0
      basics/counter/seahorse/.prettierignore
  44. 15 0
      basics/counter/seahorse/Anchor.toml
  45. 13 0
      basics/counter/seahorse/Cargo.toml
  46. 5 0
      basics/counter/seahorse/README.md
  47. 12 0
      basics/counter/seahorse/migrations/deploy.ts
  48. 19 0
      basics/counter/seahorse/package.json
  49. 20 0
      basics/counter/seahorse/programs/counter_seahorse/Cargo.toml
  50. 2 0
      basics/counter/seahorse/programs/counter_seahorse/Xargo.toml
  51. 63 0
      basics/counter/seahorse/programs/counter_seahorse/src/lib.rs
  52. 23 0
      basics/counter/seahorse/programs_py/counter_seahorse.py
  53. 0 0
      basics/counter/seahorse/programs_py/seahorse/__init__.py
  54. 330 0
      basics/counter/seahorse/programs_py/seahorse/prelude.py
  55. 43 0
      basics/counter/seahorse/tests/counter_seahorse.ts
  56. 10 0
      basics/counter/seahorse/tsconfig.json

+ 14 - 0
basics/counter/README.md

@@ -0,0 +1,14 @@
+# Counter
+
+This example program allows anyone to create a counter and increment it.
+
+Any counter can be incremented by any key.
+
+## Note: Seahorse
+
+Seahorse currently does not allow the program to initialize anchor 
+accounts unless they are PDAs. 
+
+Seahorse example only allows users to increment the counter that corresponds to their public key.
+
+

+ 7 - 0
basics/counter/anchor/.gitignore

@@ -0,0 +1,7 @@
+
+.anchor
+.DS_Store
+target
+**/*.rs.bk
+node_modules
+test-ledger

+ 8 - 0
basics/counter/anchor/.prettierignore

@@ -0,0 +1,8 @@
+
+.anchor
+.DS_Store
+target
+node_modules
+dist
+build
+test-ledger

+ 15 - 0
basics/counter/anchor/Anchor.toml

@@ -0,0 +1,15 @@
+[features]
+seeds = false
+skip-lint = false
+[programs.localnet]
+counter_anchor = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
+
+[registry]
+url = "https://api.apr.dev"
+
+[provider]
+cluster = "localnet"
+wallet = "/Users/noahgundotra/.config/solana/id.json"
+
+[scripts]
+test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"

+ 13 - 0
basics/counter/anchor/Cargo.toml

@@ -0,0 +1,13 @@
+[workspace]
+members = [
+    "programs/*"
+]
+
+[profile.release]
+overflow-checks = true
+lto = "fat"
+codegen-units = 1
+[profile.release.build-override]
+opt-level = 3
+incremental = false
+codegen-units = 1

+ 5 - 0
basics/counter/anchor/README.md

@@ -0,0 +1,5 @@
+# Anchor Counter
+
+Anchor enforces init constraints that enforces good programming paradigms.
+
+This means this program has an additional initialization instruction for `Counter`s that the Solana native program does not.

+ 12 - 0
basics/counter/anchor/migrations/deploy.ts

@@ -0,0 +1,12 @@
+// Migrations are an early feature. Currently, they're nothing more than this
+// single deploy script that's invoked from the CLI, injecting a provider
+// configured from the workspace's Anchor.toml.
+
+const anchor = require("@project-serum/anchor");
+
+module.exports = async function (provider) {
+  // Configure client to use the provider.
+  anchor.setProvider(provider);
+
+  // Add your deploy script here.
+};

+ 19 - 0
basics/counter/anchor/package.json

@@ -0,0 +1,19 @@
+{
+    "scripts": {
+        "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w",
+        "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
+    },
+    "dependencies": {
+        "@project-serum/anchor": "^0.25.0"
+    },
+    "devDependencies": {
+        "chai": "^4.3.4",
+        "mocha": "^9.0.3",
+        "ts-mocha": "^10.0.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"
+    }
+}

+ 19 - 0
basics/counter/anchor/programs/counter_anchor/Cargo.toml

@@ -0,0 +1,19 @@
+[package]
+name = "counter_anchor"
+version = "0.1.0"
+description = "Created with Anchor"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "counter_anchor"
+
+[features]
+no-entrypoint = []
+no-idl = []
+no-log-ix-name = []
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+anchor-lang =  "0.25.0"

+ 2 - 0
basics/counter/anchor/programs/counter_anchor/Xargo.toml

@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []

+ 37 - 0
basics/counter/anchor/programs/counter_anchor/src/lib.rs

@@ -0,0 +1,37 @@
+use anchor_lang::prelude::*;
+
+declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
+
+#[program]
+pub mod counter_anchor {
+    use super::*;
+
+    pub fn initialize_counter(ctx: Context<InitializeCounter>) -> Result<()> {
+        Ok(())
+    }
+
+    pub fn increment(ctx: Context<Increment>) -> Result<()> {
+        ctx.accounts.counter.count += 1;
+        Ok(())
+    }
+}
+
+#[account]
+pub struct Counter {
+    count: u64,
+}
+
+#[derive(Accounts)]
+pub struct InitializeCounter<'info> {
+    #[account(init, space=8+8, payer=payer)]
+    pub counter: Account<'info, Counter>,
+    #[account(mut)]
+    pub payer: Signer<'info>,
+    pub system_program: Program<'info, System>,
+}
+
+#[derive(Accounts)]
+pub struct Increment<'info> {
+    #[account(mut)]
+    pub counter: Account<'info, Counter>,
+}

+ 44 - 0
basics/counter/anchor/tests/counter_anchor.ts

@@ -0,0 +1,44 @@
+import * as anchor from "@project-serum/anchor";
+import { Program } from "@project-serum/anchor";
+import {
+  Keypair
+} from '@solana/web3.js'
+import { assert } from "chai";
+import { CounterAnchor } from "../target/types/counter_anchor";
+
+describe("counter_anchor", () => {
+  // Configure the client to use the local cluster.
+  anchor.setProvider(anchor.AnchorProvider.env());
+
+  const program = anchor.workspace.CounterAnchor as Program<CounterAnchor>;
+
+  it("Test increment", async () => {
+    const counterKeypair = Keypair.generate();
+    const counter = counterKeypair.publicKey;
+
+    // Initialize counter
+    await program.methods
+      .initializeCounter()
+      .accounts({ counter, payer: program.provider.publicKey })
+      .signers([counterKeypair])
+      .rpc({ skipPreflight: true, commitment: "confirmed" });
+    let currentCount = (await program.account.counter.fetch(counter, "confirmed")).count.toNumber();
+    assert(currentCount === 0, "Expected initialized count to be 0");
+
+    // Increment counter
+    await program.methods
+      .increment()
+      .accounts({ counter })
+      .rpc({ skipPreflight: true, commitment: "confirmed" });
+    currentCount = (await program.account.counter.fetch(counter, "confirmed")).count.toNumber();
+    assert(currentCount === 1, "Expected count to be 1");
+
+    // Increment counter
+    await program.methods
+      .increment()
+      .accounts({ counter })
+      .rpc({ skipPreflight: true, commitment: "confirmed" });
+    currentCount = (await program.account.counter.fetch(counter, "confirmed")).count.toNumber();
+    assert(currentCount === 2, "Expected count to be 2");
+  });
+});

+ 10 - 0
basics/counter/anchor/tsconfig.json

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

+ 16 - 0
basics/counter/mpl-stack/.solitarc.js

@@ -0,0 +1,16 @@
+// @ts-check
+const path = require('path');
+const programDir = path.join(__dirname);
+const idlDir = path.join(__dirname, 'idl');
+const sdkDir = path.join(__dirname, 'ts', 'generated');
+const binaryInstallDir = path.join(__dirname, 'target', 'solita');
+
+module.exports = {
+    idlGenerator: 'shank',
+    programName: 'counter_mpl_stack',
+    programId: 'Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS',
+    idlDir,
+    sdkDir,
+    binaryInstallDir,
+    programDir,
+};

+ 17 - 0
basics/counter/mpl-stack/Cargo.toml

@@ -0,0 +1,17 @@
+[package]
+name = "counter-mpl-stack"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+
+[features]
+no-entrypoint = []
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+borsh = "0.9"
+shank = "0.0.8"
+solana-program = "1.10.38"

+ 13 - 0
basics/counter/mpl-stack/README.md

@@ -0,0 +1,13 @@
+# Counter: MPL Stack
+
+This example program is written using Solana native using MPL stack.
+
+
+## Setup
+
+1. Build the program with `cargo build-bpf`
+2. Compile the idl with `shank build`
+3. Build the typescript SDK with `yarn solita`
+ - Temporarily, we have to modify line 58 in ts/generated/accounts/Counter.ts 
+ to `const accountInfo = await connection.getAccountInfo(address, { commitment: "confirmed" });` in order to allow the tests to pass. In the future versions of Solita, this will be fixed.
+4. Run tests with `yarn test`

+ 42 - 0
basics/counter/mpl-stack/idl/counter_mpl_stack.json

@@ -0,0 +1,42 @@
+{
+  "version": "0.1.0",
+  "name": "counter_mpl_stack",
+  "instructions": [
+    {
+      "name": "Increment",
+      "accounts": [
+        {
+          "name": "counter",
+          "isMut": true,
+          "isSigner": false,
+          "desc": "Counter account to increment"
+        }
+      ],
+      "args": [],
+      "discriminant": {
+        "type": "u8",
+        "value": 0
+      }
+    }
+  ],
+  "accounts": [
+    {
+      "name": "Counter",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "count",
+            "type": "u64"
+          }
+        ]
+      }
+    }
+  ],
+  "metadata": {
+    "origin": "shank",
+    "address": "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS",
+    "binaryVersion": "0.0.8",
+    "libVersion": "0.0.8"
+  }
+}

+ 33 - 0
basics/counter/mpl-stack/idl/counter_solana_native.json

@@ -0,0 +1,33 @@
+{
+  "version": "0.1.0",
+  "name": "counter_solana_native",
+  "instructions": [
+    {
+      "name": "Increment",
+      "accounts": [],
+      "args": [],
+      "discriminant": {
+        "type": "u8",
+        "value": 0
+      }
+    }
+  ],
+  "accounts": [
+    {
+      "name": "Counter",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "count",
+            "type": "u64"
+          }
+        ]
+      }
+    }
+  ],
+  "metadata": {
+    "origin": "shank",
+    "address": "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
+  }
+}

+ 6 - 0
basics/counter/mpl-stack/jest.config.js

@@ -0,0 +1,6 @@
+module.exports = {
+    preset: 'ts-jest/presets/default',
+    testEnvironment: 'node',
+    testTimeout: 100000,
+    resolver: "ts-jest-resolver",
+};

+ 30 - 0
basics/counter/mpl-stack/package.json

@@ -0,0 +1,30 @@
+{
+  "name": "counter-mpl-stack",
+  "version": "0.1.0",
+  "description": "Counter program written using MPL tooling",
+  "main": "index.js",
+  "author": "ngundotra",
+  "license": "Apache-2.0",
+  "private": false,
+  "scripts": {
+    "start-validator": "solana-test-validator --reset --quiet --bpf-program Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS ./target/deploy/counter_solana_native.so",
+    "run-tests": "jest tests --detectOpenHandles",
+    "test": "start-server-and-test start-validator http://localhost:8899/health run-tests"
+  },
+  "devDependencies": {
+    "@types/bn.js": "^5.1.1",
+    "@types/jest": "^29.0.0",
+    "chai": "^4.3.6",
+    "jest": "^29.0.2",
+    "start-server-and-test": "^1.14.0",
+    "ts-jest": "^28.0.8",
+    "ts-jest-resolver": "^2.0.0",
+    "ts-node": "^10.9.1",
+    "typescript": "^4.8.2"
+  },
+  "dependencies": {
+    "@metaplex-foundation/beet": "^0.6.1",
+    "@metaplex-foundation/solita": "^0.15.2",
+    "@solana/web3.js": "^1.56.2"
+  }
+}

+ 67 - 0
basics/counter/mpl-stack/src/lib.rs

@@ -0,0 +1,67 @@
+use borsh::{BorshDeserialize, BorshSerialize};
+use shank::ShankInstruction;
+use solana_program::{
+    account_info::{next_account_info, AccountInfo},
+    declare_id,
+    entrypoint::ProgramResult,
+    msg,
+    program::{invoke, invoke_signed},
+    program_error::ProgramError,
+    pubkey::Pubkey,
+    system_instruction,
+};
+
+mod state;
+use state::*;
+
+declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
+
+#[cfg(not(feature = "no-entrypoint"))]
+use solana_program::entrypoint;
+
+#[cfg(not(feature = "no-entrypoint"))]
+entrypoint!(process_instruction);
+
+#[derive(ShankInstruction, BorshDeserialize, BorshSerialize)]
+pub enum Instruction {
+    #[account(0, writable, name = "counter", desc = "Counter account to increment")]
+    Increment,
+}
+
+pub fn process_instruction(
+    _program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    instruction_data: &[u8],
+) -> ProgramResult {
+    let (instruction_discriminant, instruction_data_inner) = instruction_data.split_at(1);
+    match instruction_discriminant[0] {
+        0 => {
+            msg!("Instruction: Increment");
+            process_increment_counter(accounts, instruction_data_inner)?;
+        }
+        _ => {
+            msg!("Error: unknown instruction")
+        }
+    }
+    Ok(())
+}
+
+pub fn process_increment_counter(
+    accounts: &[AccountInfo],
+    instruction_data: &[u8],
+) -> Result<(), ProgramError> {
+    let account_info_iter = &mut accounts.iter();
+
+    let counter_account = next_account_info(account_info_iter)?;
+    assert!(
+        counter_account.is_writable,
+        "Counter account must be writable"
+    );
+
+    let mut counter = Counter::try_from_slice(&counter_account.try_borrow_mut_data()?)?;
+    counter.count += 1;
+    counter.serialize(&mut *counter_account.data.borrow_mut())?;
+
+    msg!("Counter state incremented to {:?}", counter.count);
+    Ok(())
+}

+ 7 - 0
basics/counter/mpl-stack/src/state.rs

@@ -0,0 +1,7 @@
+use borsh::{BorshDeserialize, BorshSerialize};
+use shank::ShankAccount;
+
+#[derive(ShankAccount, BorshSerialize, BorshDeserialize, Debug, Clone)]
+pub struct Counter {
+    pub count: u64,
+}

+ 119 - 0
basics/counter/mpl-stack/tests/counter.test.ts

@@ -0,0 +1,119 @@
+import {
+    Connection,
+    Keypair,
+    LAMPORTS_PER_SOL,
+    Transaction,
+    TransactionInstruction,
+    sendAndConfirmTransaction,
+    SystemProgram
+} from '@solana/web3.js';
+import {
+    bignum
+} from '@metaplex-foundation/beet';
+import { assert } from 'chai';
+import { BN } from 'bn.js';
+
+import {
+    createIncrementInstruction,
+    Counter,
+    PROGRAM_ID,
+} from '../ts';
+
+function convertBignumToNumber(bignum: bignum): number {
+    return new BN(bignum).toNumber();
+}
+
+describe("Counter Solana Native", () => {
+    const connection = new Connection("http://localhost:8899");
+
+    it("Test allocate counter + increment tx", async () => {
+        // Randomly generate our wallet
+        const payerKeypair = Keypair.generate();
+        const payer = payerKeypair.publicKey;
+
+        // Randomly generate the account key 
+        // to sign for setting up the Counter state
+        const counterKeypair = Keypair.generate();
+        const counter = counterKeypair.publicKey;
+
+        // Airdrop our wallet 1 Sol
+        await connection.requestAirdrop(payer, LAMPORTS_PER_SOL);
+
+        // Create a TransactionInstruction to interact with our counter program
+        const allocIx: TransactionInstruction = SystemProgram.createAccount({
+            fromPubkey: payer,
+            newAccountPubkey: counter,
+            lamports: await connection.getMinimumBalanceForRentExemption(Counter.byteSize),
+            space: Counter.byteSize,
+            programId: PROGRAM_ID
+        })
+        const incrementIx: TransactionInstruction = createIncrementInstruction({ counter });
+        let tx = new Transaction().add(allocIx).add(incrementIx);
+
+        // Explicitly set the feePayer to be our wallet (this is set to first signer by default)
+        tx.feePayer = payer;
+
+        // Fetch a "timestamp" so validators know this is a recent transaction
+        tx.recentBlockhash = (await connection.getLatestBlockhash('confirmed')).blockhash;
+
+        // Send transaction to network (local network)
+        await sendAndConfirmTransaction(
+            connection,
+            tx,
+            [payerKeypair, counterKeypair],
+            { skipPreflight: true, commitment: 'confirmed' }
+        );
+
+        // Get the counter account info from network
+        let count = (await Counter.fromAccountAddress(connection, counter)).count;
+        assert((new BN(count)).toNumber() === 1, "Expected count to have been 1");
+        console.log(`[alloc+increment] count is: ${count}`);
+    });
+    it("Test allocate tx and increment tx", async () => {
+        const payerKeypair = Keypair.generate();
+        const payer = payerKeypair.publicKey;
+
+        const counterKeypair = Keypair.generate();
+        const counter = counterKeypair.publicKey;
+
+        await connection.requestAirdrop(payer, LAMPORTS_PER_SOL);
+
+        // Check allocate tx
+        const allocIx: TransactionInstruction = SystemProgram.createAccount({
+            fromPubkey: payer,
+            newAccountPubkey: counter,
+            lamports: await connection.getMinimumBalanceForRentExemption(Counter.byteSize),
+            space: Counter.byteSize,
+            programId: PROGRAM_ID
+        })
+        let tx = new Transaction().add(allocIx);
+        tx.feePayer = payer;
+        tx.recentBlockhash = (await connection.getLatestBlockhash('confirmed')).blockhash;
+        await sendAndConfirmTransaction(
+            connection,
+            tx,
+            [payerKeypair, counterKeypair],
+            { skipPreflight: true, commitment: 'confirmed' }
+        );
+
+        let count = (await Counter.fromAccountAddress(connection, counter)).count;
+        assert(convertBignumToNumber(count) === 0, "Expected count to have been 0");
+        console.log(`[allocate] count is: ${count}`);
+
+        // Check increment tx
+        const incrementIx: TransactionInstruction = createIncrementInstruction({ counter });
+        tx = new Transaction().add(incrementIx);
+        tx.feePayer = payer;
+        tx.recentBlockhash = (await connection.getLatestBlockhash('confirmed')).blockhash;
+        await sendAndConfirmTransaction(
+            connection,
+            tx,
+            [payerKeypair],
+            { skipPreflight: true, commitment: 'confirmed' }
+        );
+
+        count = (await Counter.fromAccountAddress(connection, counter)).count;
+        assert(convertBignumToNumber(count) === 1, "Expected count to have been 1");
+        console.log(`[increment] count is: ${count}`);
+    })
+})

+ 156 - 0
basics/counter/mpl-stack/ts/generated/accounts/Counter.ts

@@ -0,0 +1,156 @@
+/**
+ * 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 Counter}
+ * @category Accounts
+ * @category generated
+ */
+export type CounterArgs = {
+  count: beet.bignum
+}
+/**
+ * Holds the data for the {@link Counter} Account and provides de/serialization
+ * functionality for that data
+ *
+ * @category Accounts
+ * @category generated
+ */
+export class Counter implements CounterArgs {
+  private constructor(readonly count: beet.bignum) { }
+
+  /**
+   * Creates a {@link Counter} instance from the provided args.
+   */
+  static fromArgs(args: CounterArgs) {
+    return new Counter(args.count)
+  }
+
+  /**
+   * Deserializes the {@link Counter} 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
+  ): [Counter, number] {
+    return Counter.deserialize(accountInfo.data, offset)
+  }
+
+  /**
+   * Retrieves the account info from the provided address and deserializes
+   * the {@link Counter} 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
+  ): Promise<Counter> {
+    const accountInfo = await connection.getAccountInfo(address, { commitment: "confirmed" });
+    if (accountInfo == null) {
+      throw new Error(`Unable to find Counter account at ${address}`)
+    }
+    return Counter.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(
+      'Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS'
+    )
+  ) {
+    return beetSolana.GpaBuilder.fromStruct(programId, counterBeet)
+  }
+
+  /**
+   * Deserializes the {@link Counter} 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): [Counter, number] {
+    return counterBeet.deserialize(buf, offset)
+  }
+
+  /**
+   * Serializes the {@link Counter} 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 counterBeet.serialize(this)
+  }
+
+  /**
+   * Returns the byteSize of a {@link Buffer} holding the serialized data of
+   * {@link Counter}
+   */
+  static get byteSize() {
+    return counterBeet.byteSize
+  }
+
+  /**
+   * Fetches the minimum balance needed to exempt an account holding
+   * {@link Counter} data from rent
+   *
+   * @param connection used to retrieve the rent exemption information
+   */
+  static async getMinimumBalanceForRentExemption(
+    connection: web3.Connection,
+    commitment?: web3.Commitment
+  ): Promise<number> {
+    return connection.getMinimumBalanceForRentExemption(
+      Counter.byteSize,
+      commitment
+    )
+  }
+
+  /**
+   * Determines if the provided {@link Buffer} has the correct byte size to
+   * hold {@link Counter} data.
+   */
+  static hasCorrectByteSize(buf: Buffer, offset = 0) {
+    return buf.byteLength - offset === Counter.byteSize
+  }
+
+  /**
+   * Returns a readable version of {@link Counter} properties
+   * and can be used to convert to JSON and/or logging
+   */
+  pretty() {
+    return {
+      count: (() => {
+        const x = <{ toNumber: () => number }>this.count
+        if (typeof x.toNumber === 'function') {
+          try {
+            return x.toNumber()
+          } catch (_) {
+            return x
+          }
+        }
+        return x
+      })(),
+    }
+  }
+}
+
+/**
+ * @category Accounts
+ * @category generated
+ */
+export const counterBeet = new beet.BeetStruct<Counter, CounterArgs>(
+  [['count', beet.u64]],
+  Counter.fromArgs,
+  'Counter'
+)

+ 5 - 0
basics/counter/mpl-stack/ts/generated/accounts/index.ts

@@ -0,0 +1,5 @@
+export * from './Counter'
+
+import { Counter } from './Counter'
+
+export const accountProviders = { Counter }

+ 19 - 0
basics/counter/mpl-stack/ts/generated/index.ts

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

+ 62 - 0
basics/counter/mpl-stack/ts/generated/instructions/Increment.ts

@@ -0,0 +1,62 @@
+/**
+ * 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 Increment
+ * @category generated
+ */
+export const IncrementStruct = new beet.BeetArgsStruct<{
+  instructionDiscriminator: number
+}>([['instructionDiscriminator', beet.u8]], 'IncrementInstructionArgs')
+/**
+ * Accounts required by the _Increment_ instruction
+ *
+ * @property [_writable_] counter Counter account to increment
+ * @category Instructions
+ * @category Increment
+ * @category generated
+ */
+export type IncrementInstructionAccounts = {
+  counter: web3.PublicKey
+}
+
+export const incrementInstructionDiscriminator = 0
+
+/**
+ * Creates a _Increment_ instruction.
+ *
+ * @param accounts that will be accessed while the instruction is processed
+ * @category Instructions
+ * @category Increment
+ * @category generated
+ */
+export function createIncrementInstruction(
+  accounts: IncrementInstructionAccounts,
+  programId = new web3.PublicKey('Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS')
+) {
+  const [data] = IncrementStruct.serialize({
+    instructionDiscriminator: incrementInstructionDiscriminator,
+  })
+  const keys: web3.AccountMeta[] = [
+    {
+      pubkey: accounts.counter,
+      isWritable: true,
+      isSigner: false,
+    },
+  ]
+
+  const ix = new web3.TransactionInstruction({
+    programId,
+    keys,
+    data,
+  })
+  return ix
+}

+ 1 - 0
basics/counter/mpl-stack/ts/generated/instructions/index.ts

@@ -0,0 +1 @@
+export * from './Increment'

+ 1 - 0
basics/counter/mpl-stack/ts/index.ts

@@ -0,0 +1 @@
+export * from './generated';

+ 16 - 0
basics/counter/native/Cargo.toml

@@ -0,0 +1,16 @@
+[package]
+name = "counter-solana-native"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+
+[features]
+no-entrypoint = []
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+borsh = "0.9"
+solana-program = "1.10.38"

+ 14 - 0
basics/counter/native/README.md

@@ -0,0 +1,14 @@
+# Counter: Solana Native
+
+This example program is written in Solana using only the Solana toolsuite.
+
+## Setup
+
+1. Build the program with `cargo build-bpf`
+2. Run tests + local validator with `yarn test`
+
+## Debugging
+
+1. Start test validator with `yarn start-validator`
+2. Start listening to program logs with `solana config set -ul && solana logs`
+3. Run tests with `yarn run-tests`

+ 6 - 0
basics/counter/native/jest.config.js

@@ -0,0 +1,6 @@
+module.exports = {
+    preset: 'ts-jest/presets/default',
+    testEnvironment: 'node',
+    testTimeout: 100000,
+    resolver: "ts-jest-resolver",
+};

+ 28 - 0
basics/counter/native/package.json

@@ -0,0 +1,28 @@
+{
+  "name": "counter-solana-native",
+  "version": "0.1.0",
+  "description": "Counter program written using only Solana tooling",
+  "main": "index.js",
+  "author": "ngundotra",
+  "license": "Apache-2.0",
+  "private": false,
+  "scripts": {
+    "start-validator": "solana-test-validator --reset --quiet --bpf-program Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS ./target/deploy/counter_solana_native.so",
+    "run-tests": "jest tests --detectOpenHandles",
+    "test": "start-server-and-test start-validator http://localhost:8899/health run-tests"
+  },
+  "devDependencies": {
+    "@types/bn.js": "^5.1.1",
+    "@types/jest": "^29.0.0",
+    "chai": "^4.3.6",
+    "jest": "^29.0.2",
+    "start-server-and-test": "^1.14.0",
+    "ts-jest": "^28.0.8",
+    "ts-jest-resolver": "^2.0.0",
+    "ts-node": "^10.9.1",
+    "typescript": "^4.8.2"
+  },
+  "dependencies": {
+    "@solana/web3.js": "^1.56.2"
+  }
+}

+ 60 - 0
basics/counter/native/src/lib.rs

@@ -0,0 +1,60 @@
+use borsh::{BorshDeserialize, BorshSerialize};
+use solana_program::{
+    account_info::{next_account_info, AccountInfo},
+    declare_id,
+    entrypoint::ProgramResult,
+    msg,
+    program::{invoke, invoke_signed},
+    program_error::ProgramError,
+    pubkey::Pubkey,
+    system_instruction,
+};
+
+mod state;
+use state::*;
+
+declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
+
+#[cfg(not(feature = "no-entrypoint"))]
+use solana_program::entrypoint;
+
+#[cfg(not(feature = "no-entrypoint"))]
+entrypoint!(process_instruction);
+
+pub fn process_instruction(
+    _program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    instruction_data: &[u8],
+) -> ProgramResult {
+    let (instruction_discriminant, instruction_data_inner) = instruction_data.split_at(1);
+    match instruction_discriminant[0] {
+        0 => {
+            msg!("Instruction: Increment");
+            process_increment_counter(accounts, instruction_data_inner)?;
+        }
+        _ => {
+            msg!("Error: unknown instruction")
+        }
+    }
+    Ok(())
+}
+
+pub fn process_increment_counter(
+    accounts: &[AccountInfo],
+    instruction_data: &[u8],
+) -> Result<(), ProgramError> {
+    let account_info_iter = &mut accounts.iter();
+
+    let counter_account = next_account_info(account_info_iter)?;
+    assert!(
+        counter_account.is_writable,
+        "Counter account must be writable"
+    );
+
+    let mut counter = Counter::try_from_slice(&counter_account.try_borrow_mut_data()?)?;
+    counter.count += 1;
+    counter.serialize(&mut *counter_account.data.borrow_mut())?;
+
+    msg!("Counter state incremented to {:?}", counter.count);
+    Ok(())
+}

+ 6 - 0
basics/counter/native/src/state.rs

@@ -0,0 +1,6 @@
+use borsh::{BorshDeserialize, BorshSerialize};
+
+#[derive(BorshSerialize, BorshDeserialize, Debug, Clone)]
+pub struct Counter {
+    pub count: u64,
+}

+ 123 - 0
basics/counter/native/tests/counter.test.ts

@@ -0,0 +1,123 @@
+import {
+    Connection,
+    Keypair,
+    LAMPORTS_PER_SOL,
+    Transaction,
+    TransactionInstruction,
+    sendAndConfirmTransaction,
+    SystemProgram
+} from '@solana/web3.js';
+import { assert } from 'chai';
+
+import {
+    createIncrementInstruction,
+    deserializeCounterAccount,
+    Counter,
+    COUNTER_ACCOUNT_SIZE,
+    PROGRAM_ID,
+} from '../ts';
+
+describe("Counter Solana Native", () => {
+    const connection = new Connection("http://localhost:8899");
+
+    it("Test allocate counter + increment tx", async () => {
+        // Randomly generate our wallet
+        const payerKeypair = Keypair.generate();
+        const payer = payerKeypair.publicKey;
+
+        // Randomly generate the account key 
+        // to sign for setting up the Counter state
+        const counterKeypair = Keypair.generate();
+        const counter = counterKeypair.publicKey;
+
+        // Airdrop our wallet 1 Sol
+        await connection.requestAirdrop(payer, LAMPORTS_PER_SOL);
+
+        // Create a TransactionInstruction to interact with our counter program
+        const allocIx: TransactionInstruction = SystemProgram.createAccount({
+            fromPubkey: payer,
+            newAccountPubkey: counter,
+            lamports: await connection.getMinimumBalanceForRentExemption(COUNTER_ACCOUNT_SIZE),
+            space: COUNTER_ACCOUNT_SIZE,
+            programId: PROGRAM_ID
+        })
+        const incrementIx: TransactionInstruction = createIncrementInstruction({ counter }, {});
+        let tx = new Transaction().add(allocIx).add(incrementIx);
+
+        // Explicitly set the feePayer to be our wallet (this is set to first signer by default)
+        tx.feePayer = payer;
+
+        // Fetch a "timestamp" so validators know this is a recent transaction
+        tx.recentBlockhash = (await connection.getLatestBlockhash('confirmed')).blockhash;
+
+        // Send transaction to network (local network)
+        await sendAndConfirmTransaction(
+            connection,
+            tx,
+            [payerKeypair, counterKeypair],
+            { skipPreflight: true, commitment: 'confirmed' }
+        );
+
+        // Get the counter account info from network
+        const counterAccountInfo = await connection.getAccountInfo(counter, { commitment: "confirmed" });
+        assert(counterAccountInfo, "Expected counter account to have been created");
+
+        // Deserialize the counter & check count has been incremented
+        const counterAccount = deserializeCounterAccount(counterAccountInfo.data);
+        assert(counterAccount.count.toNumber() === 1, "Expected count to have been 1");
+        console.log(`[alloc+increment] count is: ${counterAccount.count.toNumber()}`);
+    });
+    it("Test allocate tx and increment tx", async () => {
+        const payerKeypair = Keypair.generate();
+        const payer = payerKeypair.publicKey;
+
+        const counterKeypair = Keypair.generate();
+        const counter = counterKeypair.publicKey;
+
+        await connection.requestAirdrop(payer, LAMPORTS_PER_SOL);
+
+        // Check allocate tx
+        const allocIx: TransactionInstruction = SystemProgram.createAccount({
+            fromPubkey: payer,
+            newAccountPubkey: counter,
+            lamports: await connection.getMinimumBalanceForRentExemption(COUNTER_ACCOUNT_SIZE),
+            space: COUNTER_ACCOUNT_SIZE,
+            programId: PROGRAM_ID
+        })
+        let tx = new Transaction().add(allocIx);
+        tx.feePayer = payer;
+        tx.recentBlockhash = (await connection.getLatestBlockhash('confirmed')).blockhash;
+        await sendAndConfirmTransaction(
+            connection,
+            tx,
+            [payerKeypair, counterKeypair],
+            { skipPreflight: true, commitment: 'confirmed' }
+        );
+
+        let counterAccountInfo = await connection.getAccountInfo(counter, { commitment: "confirmed" });
+        assert(counterAccountInfo, "Expected counter account to have been created");
+
+        let counterAccount = deserializeCounterAccount(counterAccountInfo.data);
+        assert(counterAccount.count.toNumber() === 0, "Expected count to have been 0");
+        console.log(`[allocate] count is: ${counterAccount.count.toNumber()}`);
+
+        // Check increment tx
+        const incrementIx: TransactionInstruction = createIncrementInstruction({ counter }, {});
+        tx = new Transaction().add(incrementIx);
+        tx.feePayer = payer;
+        tx.recentBlockhash = (await connection.getLatestBlockhash('confirmed')).blockhash;
+        await sendAndConfirmTransaction(
+            connection,
+            tx,
+            [payerKeypair],
+            { skipPreflight: true, commitment: 'confirmed' }
+        );
+
+        counterAccountInfo = await connection.getAccountInfo(counter, { commitment: "confirmed" });
+        assert(counterAccountInfo, "Expected counter account to have been created");
+
+        counterAccount = deserializeCounterAccount(counterAccountInfo.data);
+        assert(counterAccount.count.toNumber() === 1, "Expected count to have been 1");
+        console.log(`[increment] count is: ${counterAccount.count.toNumber()}`);
+    })
+})

+ 17 - 0
basics/counter/native/ts/accounts/counter.ts

@@ -0,0 +1,17 @@
+import * as BN from 'bn.js';
+
+export type Counter = {
+    count: BN
+}
+
+export const COUNTER_ACCOUNT_SIZE = 8;
+
+export function deserializeCounterAccount(data: Buffer): Counter {
+    if (data.byteLength !== 8) {
+        throw Error("Need exactly 8 bytes to deserialize counter")
+    }
+
+    return {
+        count: new BN(data, 'le')
+    }
+}

+ 1 - 0
basics/counter/native/ts/accounts/index.ts

@@ -0,0 +1 @@
+export * from './counter';

+ 5 - 0
basics/counter/native/ts/index.ts

@@ -0,0 +1,5 @@
+import { PublicKey } from '@solana/web3.js';
+export * from './instructions';
+export * from './accounts';
+
+export const PROGRAM_ID = new PublicKey("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

+ 25 - 0
basics/counter/native/ts/instructions/createIncrementInstruction.ts

@@ -0,0 +1,25 @@
+import { PublicKey, TransactionInstruction } from '@solana/web3.js';
+import { PROGRAM_ID } from '../';
+
+export type IncrementInstructionAccounts = {
+    counter: PublicKey,
+}
+export type IncrementInstructionArgs = {
+}
+
+export function createIncrementInstruction(
+    accounts: IncrementInstructionAccounts,
+    args: IncrementInstructionArgs
+): TransactionInstruction {
+    return new TransactionInstruction({
+        programId: PROGRAM_ID,
+        keys: [
+            {
+                pubkey: accounts.counter,
+                isSigner: false,
+                isWritable: true
+            }
+        ],
+        data: Buffer.from([0x0])
+    })
+}

+ 1 - 0
basics/counter/native/ts/instructions/index.ts

@@ -0,0 +1 @@
+export * from './createIncrementInstruction';

+ 7 - 0
basics/counter/seahorse/.gitignore

@@ -0,0 +1,7 @@
+
+.anchor
+.DS_Store
+target
+**/*.rs.bk
+node_modules
+test-ledger

+ 8 - 0
basics/counter/seahorse/.prettierignore

@@ -0,0 +1,8 @@
+
+.anchor
+.DS_Store
+target
+node_modules
+dist
+build
+test-ledger

+ 15 - 0
basics/counter/seahorse/Anchor.toml

@@ -0,0 +1,15 @@
+[features]
+seeds = true
+skip-lint = false
+[programs.localnet]
+counter_seahorse = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
+
+[registry]
+url = "https://api.apr.dev"
+
+[provider]
+cluster = "localnet"
+wallet = "/Users/noahgundotra/.config/solana/id.json"
+
+[scripts]
+test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"

+ 13 - 0
basics/counter/seahorse/Cargo.toml

@@ -0,0 +1,13 @@
+[workspace]
+members = [
+    "programs/*"
+]
+
+[profile.release]
+overflow-checks = true
+lto = "fat"
+codegen-units = 1
+[profile.release.build-override]
+opt-level = 3
+incremental = false
+codegen-units = 1

+ 5 - 0
basics/counter/seahorse/README.md

@@ -0,0 +1,5 @@
+# counter_seahorse
+
+This project was created by Seahorse 0.1.6.
+
+To get started, just add your code to **programs_py/counter_seahorse.py** and run `seahorse build`.

+ 12 - 0
basics/counter/seahorse/migrations/deploy.ts

@@ -0,0 +1,12 @@
+// Migrations are an early feature. Currently, they're nothing more than this
+// single deploy script that's invoked from the CLI, injecting a provider
+// configured from the workspace's Anchor.toml.
+
+const anchor = require("@project-serum/anchor");
+
+module.exports = async function (provider) {
+  // Configure client to use the provider.
+  anchor.setProvider(provider);
+
+  // Add your deploy script here.
+};

+ 19 - 0
basics/counter/seahorse/package.json

@@ -0,0 +1,19 @@
+{
+    "scripts": {
+        "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w",
+        "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
+    },
+    "dependencies": {
+        "@project-serum/anchor": "^0.25.0"
+    },
+    "devDependencies": {
+        "chai": "^4.3.4",
+        "mocha": "^9.0.3",
+        "ts-mocha": "^10.0.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"
+    }
+}

+ 20 - 0
basics/counter/seahorse/programs/counter_seahorse/Cargo.toml

@@ -0,0 +1,20 @@
+[package]
+name = "counter_seahorse"
+version = "0.1.0"
+description = "Created with Anchor"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "counter_seahorse"
+
+[features]
+no-entrypoint = []
+no-idl = []
+no-log-ix-name = []
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+anchor-lang = "0.25.0"
+anchor-spl = "0.25.0"

+ 2 - 0
basics/counter/seahorse/programs/counter_seahorse/Xargo.toml

@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []

+ 63 - 0
basics/counter/seahorse/programs/counter_seahorse/src/lib.rs

@@ -0,0 +1,63 @@
+use anchor_lang::prelude::*;
+use anchor_lang::solana_program;
+use anchor_spl::associated_token;
+use anchor_spl::token;
+use std::convert::TryFrom;
+
+declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
+
+#[derive(Debug)]
+#[account]
+pub struct Counter {
+    count: u64,
+}
+
+pub fn initialize_counter_handler(mut ctx: Context<InitializeCounter>, mut seed: u8) -> Result<()> {
+    let mut counter = &mut ctx.accounts.counter;
+    let mut payer = &mut ctx.accounts.payer;
+
+    Ok(())
+}
+
+pub fn increment_handler(mut ctx: Context<Increment>) -> Result<()> {
+    let mut counter = &mut ctx.accounts.counter;
+
+    counter.count += 1;
+
+    Ok(())
+}
+
+#[derive(Accounts)]
+# [instruction (seed : u8)]
+pub struct InitializeCounter<'info> {
+    #[account(
+        init,
+        payer = payer,
+        seeds = [seed.to_le_bytes().as_ref()],
+        bump,
+        space = 8 + std::mem::size_of::<Counter>()
+    )]
+    pub counter: Box<Account<'info, Counter>>,
+    #[account(mut)]
+    pub payer: Signer<'info>,
+    pub system_program: Program<'info, System>,
+}
+
+#[derive(Accounts)]
+pub struct Increment<'info> {
+    #[account(mut)]
+    pub counter: Box<Account<'info, Counter>>,
+}
+
+#[program]
+pub mod counter_seahorse {
+    use super::*;
+
+    pub fn initialize_counter(ctx: Context<InitializeCounter>, seed: u8) -> Result<()> {
+        initialize_counter_handler(ctx, seed)
+    }
+
+    pub fn increment(ctx: Context<Increment>) -> Result<()> {
+        increment_handler(ctx)
+    }
+}

+ 23 - 0
basics/counter/seahorse/programs_py/counter_seahorse.py

@@ -0,0 +1,23 @@
+# counter_seahorse
+# Built with Seahorse v0.1.6
+
+from seahorse.prelude import *
+
+declare_id('Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS')
+
+
+class Counter(Account):
+    count: u64
+
+
+@instruction
+def initialize_counter(counter: Empty[Counter], payer: Signer, seed: u8):
+    counter.init(
+        payer=payer,
+        seeds=[seed]
+    )
+
+
+@instruction
+def increment(counter: Counter):
+    counter.count += 1

+ 0 - 0
basics/counter/seahorse/programs_py/seahorse/__init__.py


+ 330 - 0
basics/counter/seahorse/programs_py/seahorse/prelude.py

@@ -0,0 +1,330 @@
+# seahorse.prelude: the basis for writing Seahorse programs.
+#
+# NOTE: this file just contains types and documentation for your editor. This
+# is NOT executable code, and you won't be able to change the behavior of your
+# Seahorse programs by editing this file.
+
+from typing import *
+from math import floor, ceil
+
+T = TypeVar('T')
+N = TypeVar('N')
+
+
+# ===========================================================
+# Internal types - here for completeness, but not really used
+# ===========================================================
+    
+class ProgramResult:
+    """Result from executing an instruction - either a success, or a failure with an error message."""
+
+
+# ==========
+# Rust types
+# ==========
+
+class u8:
+    """Single-byte unsigned integer."""
+
+    def __init__(self, _: Any):
+        return self
+
+    def __add__(self, _: Any):
+        return self
+
+    def __radd__(self, _: Any):
+        return self
+
+    def __iadd__(self, _: Any):
+        return self
+
+    def __sub__(self, _: Any):
+        return self
+
+    def __rsub__(self, _: Any):
+        return self
+
+    def __isub__(self, _: Any):
+        return self
+
+    def __mul__(self, _: Any):
+        return self
+
+    def __rmul__(self, _: Any):
+        return self
+
+    def __imul__(self, _: Any):
+        return self
+
+    def __div__(self, _: Any):
+        return self
+
+    def __rdiv__(self, _: Any):
+        return self
+
+    def __idiv__(self, _: Any):
+        return self
+
+class u64:
+    """64-bit unsigned integer."""
+
+    def __init__(self, _: Any):
+        return self
+
+    def __add__(self, _: Any):
+        return self
+
+    def __radd__(self, _: Any):
+        return self
+
+    def __iadd__(self, _: Any):
+        return self
+
+    def __sub__(self, _: Any):
+        return self
+
+    def __rsub__(self, _: Any):
+        return self
+
+    def __isub__(self, _: Any):
+        return self
+
+    def __mul__(self, _: Any):
+        return self
+
+    def __rmul__(self, _: Any):
+        return self
+
+    def __imul__(self, _: Any):
+        return self
+
+    def __div__(self, _: Any):
+        return self
+
+    def __rdiv__(self, _: Any):
+        return self
+
+    def __idiv__(self, _: Any):
+        return self
+
+class i64:
+    """64-bit signed integer."""
+
+    def __init__(self, _: Any):
+        return self
+
+    def __add__(self, _: Any):
+        return self
+
+    def __radd__(self, _: Any):
+        return self
+
+    def __iadd__(self, _: Any):
+        return self
+
+    def __sub__(self, _: Any):
+        return self
+
+    def __rsub__(self, _: Any):
+        return self
+
+    def __isub__(self, _: Any):
+        return self
+
+    def __mul__(self, _: Any):
+        return self
+
+    def __rmul__(self, _: Any):
+        return self
+
+    def __imul__(self, _: Any):
+        return self
+
+    def __div__(self, _: Any):
+        return self
+
+    def __rdiv__(self, _: Any):
+        return self
+
+    def __idiv__(self, _: Any):
+        return self
+
+class f64:
+    """64-bit floating point number."""
+
+    def __add__(self, _: Any):
+        return self
+
+    def __radd__(self, _: Any):
+        return self
+
+    def __iadd__(self, _: Any):
+        return self
+
+    def __sub__(self, _: Any):
+        return self
+
+    def __rsub__(self, _: Any):
+        return self
+
+    def __isub__(self, _: Any):
+        return self
+
+    def __mul__(self, _: Any):
+        return self
+
+    def __rmul__(self, _: Any):
+        return self
+
+    def __imul__(self, _: Any):
+        return self
+
+    def __div__(self, _: Any):
+        return self
+
+    def __rdiv__(self, _: Any):
+        return self
+
+    def __idiv__(self, _: Any):
+        return self
+
+
+class Array(Generic[T, N]):
+    """A fixed-length array: contains type T and has size N.
+
+    Lists (Python builtin type) can coerce to this type. Example:
+
+    ```
+    class MyData(Account):
+        data: Array[u64, 4]
+
+    @instruction
+    def set_data(my_data: MyData):
+        # Will successfully set `data` to [0, 1, 2, 3]
+        my_data.data = [i for i in range(0, 4)]
+        # Will attempt (and fail, crashing the instruction at runtime!) to set `data` to [0, 1, 2, 3, 4]
+        my_data.data = [i for i in range(0, 5)]
+    ```
+    """
+
+class Enum:
+    """A type that can have one of multiple named values.
+
+    Note that unlike Rust enums, these cannot contain any data (other than the variant itself). Example:
+
+    ```
+    class MyEnum(Enum):
+        ONE = 1
+        TWO = 2
+        THREE = 3
+
+    @instruction
+    def use_enum(code: MyEnum):
+        if code == MyEnum.ONE:
+            print(1)
+        # ...
+    ```
+    """
+
+# ============
+# Solana types
+# ============
+
+class Pubkey:
+    """32-byte account identifier."""
+
+class SolanaAccount:
+    """Generic Solana account."""
+
+    def key(self) -> Pubkey:
+        """Get this account's key."""
+
+    def transfer_lamports(self, to: SolanaAccount, amount: u64):
+        """Transfer some SOL (as an amount of lamports) to another account.
+
+        Note: this will successfully transfer from a program-owned account without needing to
+        provide the seeds for a PDA, so no signer field is required (unlike the SPL methods).
+        """
+
+class Account(SolanaAccount):
+    """User-defined Solana account."""
+
+class Signer(SolanaAccount):
+    """Instruction signer."""
+
+class Empty(Generic[T]):
+    """An account that needs to be initialized."""
+
+    def bump(self) -> u8:
+        """Get this account's bump, needed if you want to use this account to sign CPI calls."""
+
+    def init(self, payer: Signer, seeds: List[Union[str, Account, u8]], mint: TokenMint, authority: Account) -> T:
+        """
+        Initialize the account.
+        
+        @param payer: The account that will pay for the rent cost of the initialized account. Must be an instruction signer.
+        @param seeds: A list of parameters to uniquely identify this account among all accounts created by your program. These may be string literals or other accounts.
+        @param mint: If initializing a TokenAccount, this is the mint that the account belongs to.
+        @param decimals: If initializing a TokenMint, this is the number of decimals the new token has.
+        @param authority: If initializing a TokenAccount/TokenMint, this is the account that has authority over the account.
+        @returns: The new, initialized account. All of the data in this account will be set to 0.
+        """
+
+class TokenAccount(SolanaAccount):
+    """SPL token account."""
+
+    def authority(self) -> Pubkey:
+        """Get the owner of this token account."""
+
+    def amount(self) -> u64:
+        """Get the amount of token stored in this account."""
+
+    def transfer(self, authority: SolanaAccount, to: TokenAccount, amount: u64, signer: List[Union[str, Account, u8]] = None):
+        """
+        Transfer funds from this SPL token account to another.
+        
+        @param authority: The account that owns this TokenAccount. Must be an instruction signer or the account given by the `signer` param.
+        @param to: The recipient TokenAccount.
+        @param amount: How much (in *native* token units) to transfer.
+        @param signer: (Optional) seeds for the signature of a PDA.
+        """
+
+class TokenMint(SolanaAccount):
+    """SPL token mint."""
+
+    def authority(self) -> Pubkey:
+        """Get the owner of this token account."""
+
+    def mint(self, authority: SolanaAccount, to: TokenAccount, amount: u64, signer: List[Union[str, Account, u8]] = None):
+        """
+        Mint new tokens to a token account.
+
+        @param authority: The account that owns this TokenMint. Must be an instruction signer or the account given by the `signer` param.
+        @param to: The recipient TokenAccount.
+        @param amount: How much (in *native* token units) to mint.
+        @param signer: (Optional) seeds for the signature of a PDA.
+        """
+
+    def burn(self, authority: SolanaAccount, holder: TokenAccount, amount: u64, signer: List[Union[str, Account, u8]] = None):
+        """
+        Burn tokens from a token account.
+
+        @param authority: The account that owns the `holder` TokenAccount. Must be an instruction signer or the account given by the `signer` param.
+        @param holder: The TokenAccount to burn from.
+        @param amount: How much (in *native* token units) to burn.
+        @param signer: (Optional) seeds for the signature of a PDA.
+        """
+
+
+# ================
+# Helper functions
+# ================
+
+def declare_id(id: str):
+    """Inform Anchor what this program's ID is.
+
+    @param id: The program's ID, generated by Anchor in /target/idl/<program>.json. This must be copied-pasted straight from there as a string literal.
+    """
+
+def instruction(function: Callable[..., None]) -> Callable[..., ProgramResult]:
+    """Decorator to turn a function into a program instruction."""

+ 43 - 0
basics/counter/seahorse/tests/counter_seahorse.ts

@@ -0,0 +1,43 @@
+import * as anchor from "@project-serum/anchor";
+import { Program } from "@project-serum/anchor";
+import {
+  Keypair,
+  PublicKey,
+  SystemProgram
+} from '@solana/web3.js';
+import { assert } from "chai";
+import { CounterSeahorse } from "../target/types/counter_seahorse";
+
+describe("counter_seahorse", () => {
+  // Configure the client to use the local cluster.
+  anchor.setProvider(anchor.AnchorProvider.env());
+
+  const program = anchor.workspace.CounterSeahorse as Program<CounterSeahorse>;
+
+  it("Increment counter", async () => {
+    const seed = 69;
+    const counter = PublicKey.findProgramAddressSync(
+      [Buffer.from([0x45])],
+      program.programId
+    )[0];
+
+    // Initialize counter
+    await program.methods
+      .initializeCounter(seed)
+      .accounts({
+        payer: program.provider.publicKey,
+      })
+      .rpc();
+
+    // Increment counter
+    await program.methods
+      .increment()
+      .accounts({
+        counter
+      })
+      .rpc();
+
+    const count = (await program.account.counter.fetch(counter)).count.toNumber();
+    assert(count === 1, "Expected count to be 1");
+  });
+});

+ 10 - 0
basics/counter/seahorse/tsconfig.json

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