Bladeren bron

Added Python Tokens example + Tests

Valentin Madrid 2 jaren geleden
bovenliggende
commit
c8f47bb174

+ 7 - 0
tokens/transfer-tokens/seahorse/.gitignore

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

+ 8 - 0
tokens/transfer-tokens/seahorse/.prettierignore

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

+ 15 - 0
tokens/transfer-tokens/seahorse/Anchor.toml

@@ -0,0 +1,15 @@
+[features]
+seeds = true
+skip-lint = false
+[programs.localnet]
+seahorse = "5KCV219sxBAZMfXWP5EZ57D6K9568krgPKGe1Lq2nkxH"
+
+[registry]
+url = "https://api.apr.dev"
+
+[provider]
+cluster = "Localnet"
+wallet = "~/.config/solana/id.json"
+
+[scripts]
+test = "python3 tests/seahorse.py"

+ 13 - 0
tokens/transfer-tokens/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
tokens/transfer-tokens/seahorse/README.md

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

+ 12 - 0
tokens/transfer-tokens/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("@coral-xyz/anchor");
+
+module.exports = async function (provider) {
+  // Configure client to use the provider.
+  anchor.setProvider(provider);
+
+  // Add your deploy script here.
+};

+ 19 - 0
tokens/transfer-tokens/seahorse/package.json

@@ -0,0 +1,19 @@
+{
+    "scripts": {
+        "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w",
+        "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
+    },
+    "dependencies": {
+        "@coral-xyz/anchor": "^0.27.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"
+    }
+}

+ 21 - 0
tokens/transfer-tokens/seahorse/programs/seahorse/Cargo.toml

@@ -0,0 +1,21 @@
+[package]
+name = "seahorse"
+version = "0.1.0"
+description = "Created with Anchor"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "seahorse"
+
+[features]
+no-entrypoint = []
+no-idl = []
+no-log-ix-name = []
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+anchor-lang = "0.27.0"
+anchor-spl = "0.27.0"
+pyth-sdk-solana = { version = "0.7.1", optional = true }

+ 2 - 0
tokens/transfer-tokens/seahorse/programs/seahorse/Xargo.toml

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

+ 1 - 0
tokens/transfer-tokens/seahorse/programs/seahorse/src/dot/mod.rs

@@ -0,0 +1 @@
+pub mod program;

+ 62 - 0
tokens/transfer-tokens/seahorse/programs/seahorse/src/dot/program.rs

@@ -0,0 +1,62 @@
+#![allow(unused_imports)]
+#![allow(unused_variables)]
+#![allow(unused_mut)]
+use crate::{id, seahorse_util::*};
+use anchor_lang::{prelude::*, solana_program};
+use anchor_spl::token::{self, Mint, Token, TokenAccount};
+use std::{cell::RefCell, rc::Rc};
+
+pub fn create_associated_token_account_handler<'info>(
+    mut token_account: Empty<SeahorseAccount<'info, '_, TokenAccount>>,
+    mut mint: SeahorseAccount<'info, '_, Mint>,
+    mut signer: SeahorseSigner<'info, '_>,
+) -> () {
+    token_account.account.clone();
+}
+
+pub fn create_token_handler<'info>(
+    mut mint: Empty<SeahorseAccount<'info, '_, Mint>>,
+    mut signer: SeahorseSigner<'info, '_>,
+) -> () {
+    mint.account.clone();
+}
+
+pub fn mint_token_handler<'info>(
+    mut mint: SeahorseAccount<'info, '_, Mint>,
+    mut recipient: SeahorseAccount<'info, '_, TokenAccount>,
+    mut signer: SeahorseSigner<'info, '_>,
+    mut amount: u64,
+) -> () {
+    token::mint_to(
+        CpiContext::new(
+            mint.programs.get("token_program"),
+            token::MintTo {
+                mint: mint.to_account_info(),
+                authority: signer.clone().to_account_info(),
+                to: recipient.clone().to_account_info(),
+            },
+        ),
+        (amount * <u64 as TryFrom<_>>::try_from(10).unwrap().pow(6)),
+    )
+    .unwrap();
+}
+
+pub fn transfer_handler<'info>(
+    mut signer_token_account: SeahorseAccount<'info, '_, TokenAccount>,
+    mut recipient: SeahorseAccount<'info, '_, TokenAccount>,
+    mut signer: SeahorseSigner<'info, '_>,
+    mut amount: u64,
+) -> () {
+    token::transfer(
+        CpiContext::new(
+            signer_token_account.programs.get("token_program"),
+            token::Transfer {
+                from: signer_token_account.to_account_info(),
+                authority: signer.clone().to_account_info(),
+                to: recipient.clone().to_account_info(),
+            },
+        ),
+        (amount * <u64 as TryFrom<_>>::try_from(10).unwrap().pow(6)),
+    )
+    .unwrap();
+}

+ 362 - 0
tokens/transfer-tokens/seahorse/programs/seahorse/src/lib.rs

@@ -0,0 +1,362 @@
+#![allow(unused_imports)]
+#![allow(unused_variables)]
+#![allow(unused_mut)]
+
+pub mod dot;
+
+use anchor_lang::prelude::*;
+use anchor_spl::{
+    associated_token::{self, AssociatedToken},
+    token::{self, Mint, Token, TokenAccount},
+};
+
+use dot::program::*;
+use std::{cell::RefCell, rc::Rc};
+
+declare_id!("5KCV219sxBAZMfXWP5EZ57D6K9568krgPKGe1Lq2nkxH");
+
+pub mod seahorse_util {
+    use super::*;
+
+    #[cfg(feature = "pyth-sdk-solana")]
+    pub use pyth_sdk_solana::{load_price_feed_from_account_info, PriceFeed};
+    use std::{collections::HashMap, fmt::Debug, ops::Deref};
+
+    pub struct Mutable<T>(Rc<RefCell<T>>);
+
+    impl<T> Mutable<T> {
+        pub fn new(obj: T) -> Self {
+            Self(Rc::new(RefCell::new(obj)))
+        }
+    }
+
+    impl<T> Clone for Mutable<T> {
+        fn clone(&self) -> Self {
+            Self(self.0.clone())
+        }
+    }
+
+    impl<T> Deref for Mutable<T> {
+        type Target = Rc<RefCell<T>>;
+
+        fn deref(&self) -> &Self::Target {
+            &self.0
+        }
+    }
+
+    impl<T: Debug> Debug for Mutable<T> {
+        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+            write!(f, "{:?}", self.0)
+        }
+    }
+
+    impl<T: Default> Default for Mutable<T> {
+        fn default() -> Self {
+            Self::new(T::default())
+        }
+    }
+
+    impl<T: Clone> Mutable<Vec<T>> {
+        pub fn wrapped_index(&self, mut index: i128) -> usize {
+            if index >= 0 {
+                return index.try_into().unwrap();
+            }
+
+            index += self.borrow().len() as i128;
+
+            return index.try_into().unwrap();
+        }
+    }
+
+    impl<T: Clone, const N: usize> Mutable<[T; N]> {
+        pub fn wrapped_index(&self, mut index: i128) -> usize {
+            if index >= 0 {
+                return index.try_into().unwrap();
+            }
+
+            index += self.borrow().len() as i128;
+
+            return index.try_into().unwrap();
+        }
+    }
+
+    #[derive(Clone)]
+    pub struct Empty<T: Clone> {
+        pub account: T,
+        pub bump: Option<u8>,
+    }
+
+    #[derive(Clone, Debug)]
+    pub struct ProgramsMap<'info>(pub HashMap<&'static str, AccountInfo<'info>>);
+
+    impl<'info> ProgramsMap<'info> {
+        pub fn get(&self, name: &'static str) -> AccountInfo<'info> {
+            self.0.get(name).unwrap().clone()
+        }
+    }
+
+    #[derive(Clone, Debug)]
+    pub struct WithPrograms<'info, 'entrypoint, A> {
+        pub account: &'entrypoint A,
+        pub programs: &'entrypoint ProgramsMap<'info>,
+    }
+
+    impl<'info, 'entrypoint, A> Deref for WithPrograms<'info, 'entrypoint, A> {
+        type Target = A;
+
+        fn deref(&self) -> &Self::Target {
+            &self.account
+        }
+    }
+
+    pub type SeahorseAccount<'info, 'entrypoint, A> =
+        WithPrograms<'info, 'entrypoint, Box<Account<'info, A>>>;
+
+    pub type SeahorseSigner<'info, 'entrypoint> = WithPrograms<'info, 'entrypoint, Signer<'info>>;
+
+    #[derive(Clone, Debug)]
+    pub struct CpiAccount<'info> {
+        #[doc = "CHECK: CpiAccounts temporarily store AccountInfos."]
+        pub account_info: AccountInfo<'info>,
+        pub is_writable: bool,
+        pub is_signer: bool,
+        pub seeds: Option<Vec<Vec<u8>>>,
+    }
+
+    #[macro_export]
+    macro_rules! seahorse_const {
+        ($ name : ident , $ value : expr) => {
+            macro_rules! $name {
+                () => {
+                    $value
+                };
+            }
+
+            pub(crate) use $name;
+        };
+    }
+
+    #[macro_export]
+    macro_rules! assign {
+        ($ lval : expr , $ rval : expr) => {{
+            let temp = $rval;
+
+            $lval = temp;
+        }};
+    }
+
+    #[macro_export]
+    macro_rules! index_assign {
+        ($ lval : expr , $ idx : expr , $ rval : expr) => {
+            let temp_rval = $rval;
+            let temp_idx = $idx;
+
+            $lval[temp_idx] = temp_rval;
+        };
+    }
+
+    pub(crate) use assign;
+
+    pub(crate) use index_assign;
+
+    pub(crate) use seahorse_const;
+}
+
+#[program]
+mod seahorse {
+    use super::*;
+    use seahorse_util::*;
+    use std::collections::HashMap;
+
+    #[derive(Accounts)]
+    pub struct CreateAssociatedTokenAccount<'info> {
+        # [account (init , payer = signer , associated_token :: mint = mint , associated_token :: authority = signer)]
+        pub token_account: Box<Account<'info, TokenAccount>>,
+        #[account(mut)]
+        pub mint: Box<Account<'info, Mint>>,
+        #[account(mut)]
+        pub signer: Signer<'info>,
+        pub associated_token_program: Program<'info, AssociatedToken>,
+        pub rent: Sysvar<'info, Rent>,
+        pub system_program: Program<'info, System>,
+        pub token_program: Program<'info, Token>,
+    }
+
+    pub fn create_associated_token_account(
+        ctx: Context<CreateAssociatedTokenAccount>,
+    ) -> Result<()> {
+        let mut programs = HashMap::new();
+
+        programs.insert(
+            "associated_token_program",
+            ctx.accounts.associated_token_program.to_account_info(),
+        );
+
+        programs.insert(
+            "system_program",
+            ctx.accounts.system_program.to_account_info(),
+        );
+
+        programs.insert(
+            "token_program",
+            ctx.accounts.token_program.to_account_info(),
+        );
+
+        let programs_map = ProgramsMap(programs);
+        let token_account = Empty {
+            account: SeahorseAccount {
+                account: &ctx.accounts.token_account,
+                programs: &programs_map,
+            },
+            bump: ctx.bumps.get("token_account").map(|bump| *bump),
+        };
+
+        let mint = SeahorseAccount {
+            account: &ctx.accounts.mint,
+            programs: &programs_map,
+        };
+
+        let signer = SeahorseSigner {
+            account: &ctx.accounts.signer,
+            programs: &programs_map,
+        };
+
+        create_associated_token_account_handler(
+            token_account.clone(),
+            mint.clone(),
+            signer.clone(),
+        );
+
+        return Ok(());
+    }
+
+    #[derive(Accounts)]
+    pub struct CreateToken<'info> {
+        # [account (init , payer = signer , mint :: decimals = 6 , mint :: authority = signer)]
+        pub mint: Box<Account<'info, Mint>>,
+        #[account(mut)]
+        pub signer: Signer<'info>,
+        pub rent: Sysvar<'info, Rent>,
+        pub system_program: Program<'info, System>,
+        pub token_program: Program<'info, Token>,
+    }
+
+    pub fn create_token(ctx: Context<CreateToken>) -> Result<()> {
+        let mut programs = HashMap::new();
+
+        programs.insert(
+            "system_program",
+            ctx.accounts.system_program.to_account_info(),
+        );
+
+        programs.insert(
+            "token_program",
+            ctx.accounts.token_program.to_account_info(),
+        );
+
+        let programs_map = ProgramsMap(programs);
+        let mint = Empty {
+            account: SeahorseAccount {
+                account: &ctx.accounts.mint,
+                programs: &programs_map,
+            },
+            bump: ctx.bumps.get("mint").map(|bump| *bump),
+        };
+
+        let signer = SeahorseSigner {
+            account: &ctx.accounts.signer,
+            programs: &programs_map,
+        };
+
+        create_token_handler(mint.clone(), signer.clone());
+
+        return Ok(());
+    }
+
+    #[derive(Accounts)]
+    # [instruction (amount : u64)]
+    pub struct MintToken<'info> {
+        #[account(mut)]
+        pub mint: Box<Account<'info, Mint>>,
+        #[account(mut)]
+        pub recipient: Box<Account<'info, TokenAccount>>,
+        #[account(mut)]
+        pub signer: Signer<'info>,
+        pub token_program: Program<'info, Token>,
+    }
+
+    pub fn mint_token(ctx: Context<MintToken>, amount: u64) -> Result<()> {
+        let mut programs = HashMap::new();
+
+        programs.insert(
+            "token_program",
+            ctx.accounts.token_program.to_account_info(),
+        );
+
+        let programs_map = ProgramsMap(programs);
+        let mint = SeahorseAccount {
+            account: &ctx.accounts.mint,
+            programs: &programs_map,
+        };
+
+        let recipient = SeahorseAccount {
+            account: &ctx.accounts.recipient,
+            programs: &programs_map,
+        };
+
+        let signer = SeahorseSigner {
+            account: &ctx.accounts.signer,
+            programs: &programs_map,
+        };
+
+        mint_token_handler(mint.clone(), recipient.clone(), signer.clone(), amount);
+
+        return Ok(());
+    }
+
+    #[derive(Accounts)]
+    # [instruction (amount : u64)]
+    pub struct Transfer<'info> {
+        #[account(mut)]
+        pub signer_token_account: Box<Account<'info, TokenAccount>>,
+        #[account(mut)]
+        pub recipient: Box<Account<'info, TokenAccount>>,
+        #[account(mut)]
+        pub signer: Signer<'info>,
+        pub token_program: Program<'info, Token>,
+    }
+
+    pub fn transfer(ctx: Context<Transfer>, amount: u64) -> Result<()> {
+        let mut programs = HashMap::new();
+
+        programs.insert(
+            "token_program",
+            ctx.accounts.token_program.to_account_info(),
+        );
+
+        let programs_map = ProgramsMap(programs);
+        let signer_token_account = SeahorseAccount {
+            account: &ctx.accounts.signer_token_account,
+            programs: &programs_map,
+        };
+
+        let recipient = SeahorseAccount {
+            account: &ctx.accounts.recipient,
+            programs: &programs_map,
+        };
+
+        let signer = SeahorseSigner {
+            account: &ctx.accounts.signer,
+            programs: &programs_map,
+        };
+
+        transfer_handler(
+            signer_token_account.clone(),
+            recipient.clone(),
+            signer.clone(),
+            amount,
+        );
+
+        return Ok(());
+    }
+}

+ 57 - 0
tokens/transfer-tokens/seahorse/programs_py/seahorse.py

@@ -0,0 +1,57 @@
+# seahorse
+# Built with Seahorse v0.2.7
+
+from seahorse.prelude import *
+
+declare_id('5KCV219sxBAZMfXWP5EZ57D6K9568krgPKGe1Lq2nkxH')
+
+@instruction
+def create_token(
+  mint: Empty[TokenMint],
+  signer: Signer
+):
+  mint.init(
+    payer = signer,
+    decimals = 6,
+    authority = signer
+  )
+
+@instruction
+def mint_token(
+  mint: TokenMint,
+  recipient: TokenAccount,
+  signer: Signer,
+  amount: u64
+):
+  mint.mint(
+    authority = signer,
+    to = recipient,
+    amount = amount * u64(10) ** 6
+  )
+
+
+@instruction
+def create_associated_token_account(
+  token_account: Empty[TokenAccount],
+  mint: TokenMint,
+  signer: Signer
+):
+  token_account.init(
+    associated = True,
+    payer = signer,
+    mint = mint,
+    authority = signer
+  )
+
+@instruction
+def transfer(
+  signer_token_account: TokenAccount,
+  recipient: TokenAccount,
+  signer: Signer,
+  amount: u64
+):
+  signer_token_account.transfer(
+    authority = signer,
+    to = recipient,
+    amount = amount * u64(10) ** 6
+  )

+ 0 - 0
tokens/transfer-tokens/seahorse/programs_py/seahorse/__init__.py


+ 1062 - 0
tokens/transfer-tokens/seahorse/programs_py/seahorse/prelude.py

@@ -0,0 +1,1062 @@
+# 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')
+
+
+# ==========
+# Rust types
+# ==========
+
+class u8:
+    """8-bit unsigned integer."""
+
+    def __init__(self, _: Any) -> 'u8':
+        """Construct an u8."""
+
+    def __add__(self, other: 'u8') -> 'u8':
+        pass
+
+    def __radd__(self, other: 'u8') -> 'u8':
+        pass
+
+    def __iadd__(self, other: 'u8') -> 'u8':
+        pass
+
+    def __sub__(self, other: 'u8') -> 'u8':
+        pass
+
+    def __rsub__(self, other: 'u8') -> 'u8':
+        pass
+
+    def __isub__(self, other: 'u8') -> 'u8':
+        pass
+
+    def __mul__(self, other: 'u8') -> 'u8':
+        pass
+
+    def __rmul__(self, other: 'u8') -> 'u8':
+        pass
+
+    def __imul__(self, other: 'u8') -> 'u8':
+        pass
+
+    def __truediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __rtruediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __itruediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __floordiv__(self, other: 'u8') -> 'u8':
+        pass
+
+    def __rfloordiv__(self, other: 'u8') -> 'u8':
+        pass
+
+    def __ifloordiv__(self, other: 'u8') -> 'u8':
+        pass
+
+    def __lt__(self, other: 'u8') -> bool:
+        pass
+
+    def __le__(self, other: 'u8') -> bool:
+        pass
+
+    def __eq__(self, other: 'u8') -> bool:
+        pass
+
+    def __ne__(self, other: 'u8') -> bool:
+        pass
+
+    def __ge__(self, other: 'u8') -> bool:
+        pass
+
+    def __gt__(self, other: 'u8') -> bool:
+        pass
+
+
+class u16:
+    """16-bit unsigned integer."""
+
+    def __init__(self, _: Any) -> 'u16':
+        """Construct an u16."""
+
+    def __add__(self, other: 'u16') -> 'u16':
+        pass
+
+    def __radd__(self, other: 'u16') -> 'u16':
+        pass
+
+    def __iadd__(self, other: 'u16') -> 'u16':
+        pass
+
+    def __sub__(self, other: 'u16') -> 'u16':
+        pass
+
+    def __rsub__(self, other: 'u16') -> 'u16':
+        pass
+
+    def __isub__(self, other: 'u16') -> 'u16':
+        pass
+
+    def __mul__(self, other: 'u16') -> 'u16':
+        pass
+
+    def __rmul__(self, other: 'u16') -> 'u16':
+        pass
+
+    def __imul__(self, other: 'u16') -> 'u16':
+        pass
+
+    def __truediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __rtruediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __itruediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __floordiv__(self, other: 'u16') -> 'u16':
+        pass
+
+    def __rfloordiv__(self, other: 'u16') -> 'u16':
+        pass
+
+    def __ifloordiv__(self, other: 'u16') -> 'u16':
+        pass
+
+    def __lt__(self, other: 'u16') -> bool:
+        pass
+
+    def __le__(self, other: 'u16') -> bool:
+        pass
+
+    def __eq__(self, other: 'u16') -> bool:
+        pass
+
+    def __ne__(self, other: 'u16') -> bool:
+        pass
+
+    def __ge__(self, other: 'u16') -> bool:
+        pass
+
+    def __gt__(self, other: 'u16') -> bool:
+        pass
+
+class u32:
+    """32-bit unsigned integer."""
+
+    def __init__(self, _: Any) -> 'u32':
+        """Construct an u32."""
+
+    def __add__(self, other: 'u32') -> 'u32':
+        pass
+
+    def __radd__(self, other: 'u32') -> 'u32':
+        pass
+
+    def __iadd__(self, other: 'u32') -> 'u32':
+        pass
+
+    def __sub__(self, other: 'u32') -> 'u32':
+        pass
+
+    def __rsub__(self, other: 'u32') -> 'u32':
+        pass
+
+    def __isub__(self, other: 'u32') -> 'u32':
+        pass
+
+    def __mul__(self, other: 'u32') -> 'u32':
+        pass
+
+    def __rmul__(self, other: 'u32') -> 'u32':
+        pass
+
+    def __imul__(self, other: 'u32') -> 'u32':
+        pass
+
+    def __truediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __rtruediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __itruediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __floordiv__(self, other: 'u32') -> 'u32':
+        pass
+
+    def __rfloordiv__(self, other: 'u32') -> 'u32':
+        pass
+
+    def __ifloordiv__(self, other: 'u32') -> 'u32':
+        pass
+
+    def __lt__(self, other: 'u32') -> bool:
+        pass
+
+    def __le__(self, other: 'u32') -> bool:
+        pass
+
+    def __eq__(self, other: 'u32') -> bool:
+        pass
+
+    def __ne__(self, other: 'u32') -> bool:
+        pass
+
+    def __ge__(self, other: 'u32') -> bool:
+        pass
+
+    def __gt__(self, other: 'u32') -> bool:
+        pass
+
+class u64:
+    """64-bit unsigned integer."""
+
+    def __init__(self, _: Any) -> 'u64':
+        """Construct an u64."""
+
+    def __add__(self, other: 'u64') -> 'u64':
+        pass
+
+    def __radd__(self, other: 'u64') -> 'u64':
+        pass
+
+    def __iadd__(self, other: 'u64') -> 'u64':
+        pass
+
+    def __sub__(self, other: 'u64') -> 'u64':
+        pass
+
+    def __rsub__(self, other: 'u64') -> 'u64':
+        pass
+
+    def __isub__(self, other: 'u64') -> 'u64':
+        pass
+
+    def __mul__(self, other: 'u64') -> 'u64':
+        pass
+
+    def __rmul__(self, other: 'u64') -> 'u64':
+        pass
+
+    def __imul__(self, other: 'u64') -> 'u64':
+        pass
+
+    def __truediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __rtruediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __itruediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __floordiv__(self, other: 'u64') -> 'u64':
+        pass
+
+    def __rfloordiv__(self, other: 'u64') -> 'u64':
+        pass
+
+    def __ifloordiv__(self, other: 'u64') -> 'u64':
+        pass
+
+    def __lt__(self, other: 'u64') -> bool:
+        pass
+
+    def __le__(self, other: 'u64') -> bool:
+        pass
+
+    def __eq__(self, other: 'u64') -> bool:
+        pass
+
+    def __ne__(self, other: 'u64') -> bool:
+        pass
+
+    def __ge__(self, other: 'u64') -> bool:
+        pass
+
+    def __gt__(self, other: 'u64') -> bool:
+        pass
+
+class u128:
+    """128-bit unsigned integer."""
+
+    def __init__(self, _: Any) -> 'u128':
+        """Construct an u128."""
+
+    def __add__(self, other: 'u128') -> 'u128':
+        pass
+
+    def __radd__(self, other: 'u128') -> 'u128':
+        pass
+
+    def __iadd__(self, other: 'u128') -> 'u128':
+        pass
+
+    def __sub__(self, other: 'u128') -> 'u128':
+        pass
+
+    def __rsub__(self, other: 'u128') -> 'u128':
+        pass
+
+    def __isub__(self, other: 'u128') -> 'u128':
+        pass
+
+    def __mul__(self, other: 'u128') -> 'u128':
+        pass
+
+    def __rmul__(self, other: 'u128') -> 'u128':
+        pass
+
+    def __imul__(self, other: 'u128') -> 'u128':
+        pass
+
+    def __truediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __rtruediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __itruediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __floordiv__(self, other: 'u128') -> 'u128':
+        pass
+
+    def __rfloordiv__(self, other: 'u128') -> 'u128':
+        pass
+
+    def __ifloordiv__(self, other: 'u128') -> 'u128':
+        pass
+
+    def __lt__(self, other: 'u128') -> bool:
+        pass
+
+    def __le__(self, other: 'u128') -> bool:
+        pass
+
+    def __eq__(self, other: 'u128') -> bool:
+        pass
+
+    def __ne__(self, other: 'u128') -> bool:
+        pass
+
+    def __ge__(self, other: 'u128') -> bool:
+        pass
+
+    def __gt__(self, other: 'u128') -> bool:
+        pass
+
+class i8:
+    """8-bit signed integer."""
+
+    def __init__(self, _: Any) -> 'i8':
+        """Construct an i8."""
+
+    def __add__(self, other: 'i8') -> 'i8':
+        pass
+
+    def __radd__(self, other: 'i8') -> 'i8':
+        pass
+
+    def __iadd__(self, other: 'i8') -> 'i8':
+        pass
+
+    def __sub__(self, other: 'i8') -> 'i8':
+        pass
+
+    def __rsub__(self, other: 'i8') -> 'i8':
+        pass
+
+    def __isub__(self, other: 'i8') -> 'i8':
+        pass
+
+    def __mul__(self, other: 'i8') -> 'i8':
+        pass
+
+    def __rmul__(self, other: 'i8') -> 'i8':
+        pass
+
+    def __imul__(self, other: 'i8') -> 'i8':
+        pass
+
+    def __truediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __rtruediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __itruediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __floordiv__(self, other: 'i8') -> 'i8':
+        pass
+
+    def __rfloordiv__(self, other: 'i8') -> 'i8':
+        pass
+
+    def __ifloordiv__(self, other: 'i8') -> 'i8':
+        pass
+
+class i16:
+    """16-bit signed integer."""
+
+    def __init__(self, _: Any) -> 'i16':
+        """Construct an i16."""
+
+    def __add__(self, other: 'i16') -> 'i16':
+        pass
+
+    def __radd__(self, other: 'i16') -> 'i16':
+        pass
+
+    def __iadd__(self, other: 'i16') -> 'i16':
+        pass
+
+    def __sub__(self, other: 'i16') -> 'i16':
+        pass
+
+    def __rsub__(self, other: 'i16') -> 'i16':
+        pass
+
+    def __isub__(self, other: 'i16') -> 'i16':
+        pass
+
+    def __mul__(self, other: 'i16') -> 'i16':
+        pass
+
+    def __rmul__(self, other: 'i16') -> 'i16':
+        pass
+
+    def __imul__(self, other: 'i16') -> 'i16':
+        pass
+
+    def __truediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __rtruediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __itruediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __floordiv__(self, other: 'i16') -> 'i16':
+        pass
+
+    def __rfloordiv__(self, other: 'i16') -> 'i16':
+        pass
+
+    def __ifloordiv__(self, other: 'i16') -> 'i16':
+        pass
+
+    def __lt__(self, other: 'i16') -> bool:
+        pass
+
+    def __le__(self, other: 'i16') -> bool:
+        pass
+
+    def __eq__(self, other: 'i16') -> bool:
+        pass
+
+    def __ne__(self, other: 'i16') -> bool:
+        pass
+
+    def __ge__(self, other: 'i16') -> bool:
+        pass
+
+    def __gt__(self, other: 'i16') -> bool:
+        pass
+
+class i32:
+    """32-bit signed integer."""
+
+    def __init__(self, _: Any) -> 'i32':
+        """Construct an i32."""
+
+    def __add__(self, other: 'i32') -> 'i32':
+        pass
+
+    def __radd__(self, other: 'i32') -> 'i32':
+        pass
+
+    def __iadd__(self, other: 'i32') -> 'i32':
+        pass
+
+    def __sub__(self, other: 'i32') -> 'i32':
+        pass
+
+    def __rsub__(self, other: 'i32') -> 'i32':
+        pass
+
+    def __isub__(self, other: 'i32') -> 'i32':
+        pass
+
+    def __mul__(self, other: 'i32') -> 'i32':
+        pass
+
+    def __rmul__(self, other: 'i32') -> 'i32':
+        pass
+
+    def __imul__(self, other: 'i32') -> 'i32':
+        pass
+
+    def __truediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __rtruediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __itruediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __floordiv__(self, other: 'i32') -> 'i32':
+        pass
+
+    def __rfloordiv__(self, other: 'i32') -> 'i32':
+        pass
+
+    def __ifloordiv__(self, other: 'i32') -> 'i32':
+        pass
+
+    def __lt__(self, other: 'i32') -> bool:
+        pass
+
+    def __le__(self, other: 'i32') -> bool:
+        pass
+
+    def __eq__(self, other: 'i32') -> bool:
+        pass
+
+    def __ne__(self, other: 'i32') -> bool:
+        pass
+
+    def __ge__(self, other: 'i32') -> bool:
+        pass
+
+    def __gt__(self, other: 'i32') -> bool:
+        pass
+
+class i64:
+    """64-bit signed integer."""
+
+    def __init__(self, _: Any) -> 'i64':
+        """Construct an i64."""
+
+    def __add__(self, other: 'i64') -> 'i64':
+        pass
+
+    def __radd__(self, other: 'i64') -> 'i64':
+        pass
+
+    def __iadd__(self, other: 'i64') -> 'i64':
+        pass
+
+    def __sub__(self, other: 'i64') -> 'i64':
+        pass
+
+    def __rsub__(self, other: 'i64') -> 'i64':
+        pass
+
+    def __isub__(self, other: 'i64') -> 'i64':
+        pass
+
+    def __mul__(self, other: 'i64') -> 'i64':
+        pass
+
+    def __rmul__(self, other: 'i64') -> 'i64':
+        pass
+
+    def __imul__(self, other: 'i64') -> 'i64':
+        pass
+
+    def __truediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __rtruediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __itruediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __floordiv__(self, other: 'i64') -> 'i64':
+        pass
+
+    def __rfloordiv__(self, other: 'i64') -> 'i64':
+        pass
+
+    def __ifloordiv__(self, other: 'i64') -> 'i64':
+        pass
+
+    def __lt__(self, other: 'i64') -> bool:
+        pass
+
+    def __le__(self, other: 'i64') -> bool:
+        pass
+
+    def __eq__(self, other: 'i64') -> bool:
+        pass
+
+    def __ne__(self, other: 'i64') -> bool:
+        pass
+
+    def __ge__(self, other: 'i64') -> bool:
+        pass
+
+    def __gt__(self, other: 'i64') -> bool:
+        pass
+
+class i128:
+    """128-bit signed integer."""
+
+    def __init__(self, _: Any) -> 'i128':
+        """Construct an i128."""
+
+    def __add__(self, other: 'i128') -> 'i128':
+        pass
+
+    def __radd__(self, other: 'i128') -> 'i128':
+        pass
+
+    def __iadd__(self, other: 'i128') -> 'i128':
+        pass
+
+    def __sub__(self, other: 'i128') -> 'i128':
+        pass
+
+    def __rsub__(self, other: 'i128') -> 'i128':
+        pass
+
+    def __isub__(self, other: 'i128') -> 'i128':
+        pass
+
+    def __mul__(self, other: 'i128') -> 'i128':
+        pass
+
+    def __rmul__(self, other: 'i128') -> 'i128':
+        pass
+
+    def __imul__(self, other: 'i128') -> 'i128':
+        pass
+
+    def __truediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __rtruediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __itruediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __floordiv__(self, other: 'i128') -> 'i128':
+        pass
+
+    def __rfloordiv__(self, other: 'i128') -> 'i128':
+        pass
+
+    def __ifloordiv__(self, other: 'i128') -> 'i128':
+        pass
+
+    def __lt__(self, other: 'i128') -> bool:
+        pass
+
+    def __le__(self, other: 'i128') -> bool:
+        pass
+
+    def __eq__(self, other: 'i128') -> bool:
+        pass
+
+    def __ne__(self, other: 'i128') -> bool:
+        pass
+
+    def __ge__(self, other: 'i128') -> bool:
+        pass
+
+    def __gt__(self, other: 'i128') -> bool:
+        pass
+
+class f64:
+    """64-bit floating point number."""
+
+    def __init__(self, _: Any) -> 'f64':
+        """Construct an f64."""
+
+    def __add__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __radd__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __iadd__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __sub__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __rsub__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __isub__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __mul__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __rmul__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __imul__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __truediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __rtruediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __itruediv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __floordiv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __rfloordiv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __ifloordiv__(self, other: 'f64') -> 'f64':
+        pass
+
+    def __lt__(self, other: 'f64') -> bool:
+        pass
+
+    def __le__(self, other: 'f64') -> bool:
+        pass
+
+    def __eq__(self, other: 'f64') -> bool:
+        pass
+
+    def __ne__(self, other: 'f64') -> bool:
+        pass
+
+    def __ge__(self, other: 'f64') -> bool:
+        pass
+
+    def __gt__(self, other: 'f64') -> bool:
+        pass
+
+class Array(Generic[T, N]):
+    """
+    A fixed-length array: contains type T and has size N.
+
+    N must be known at compile-time, and may not be anything other than a non-negative integer literal. Example:
+
+    ```
+    # Good
+    a: Array[u8, 4]
+
+    # Bad
+    N = 4
+    a: Array[u8, N]
+    ```
+    """
+
+    def __init__(iterable: Iterable[T], len: N) -> 'Array[T, N]':
+        """
+        Construct an array from an iterable and a length.
+
+        The parameter len must be known at compile-time, and may not be anything other than a non-negative integer literal. Example:
+
+        ```
+        a = [0, 1, 2, 3]
+
+        # Good
+        Array(a, 4)
+        # Compiles, but will definitely error at runtime
+        Array(a, 5)
+
+        # Bad (will not compile)
+        a = [0, 1, 2, 3]
+        Array(a, len(a))
+        ```
+        """
+
+    def __getitem__(self, index: Any) -> T:
+        """
+        Index into this array.
+        
+        Like Python's native list type, performs wrapping indexing - if you pass in -1, you'll get the last element of the array.
+        """
+
+def array(*elements: T) -> Array[T, N]:
+    """
+    Create an array from a variadic list of elements. Example:
+
+    ```
+    # Array[u64, 3]
+    array(u64(0), 1, 2)
+
+    # Array[str, 4]
+    array('seahorse', 'is', 'the', 'best!')
+    ```
+    """
+
+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."""
+
+    def find_program_address(seeds: List[Any], program_id: 'Pubkey' = None) -> Tuple['Pubkey', u8]:
+        """
+        Find a valid program derived address and its corresponding bump seed. Calls the same function from Solana's Pubkey struct - read more [here](https://docs.rs/solana-program/latest/solana_program/pubkey/struct.Pubkey.html#method.find_program_address).
+        
+        @param seeds: A list of parameters to uniquely identify this account among all accounts created by your program. These may be string literals, other accounts, integers, or lists of bytes.
+        @param program_id: The pubkey of the program that the PDA belongs to. Defaults to the current program's key.
+        @returns: The canonical pubkey and bump seed.
+        """
+
+class AccountWithKey:
+    """Generic Solana account."""
+
+    def key(self) -> Pubkey:
+        """Get this account's key."""
+
+class Account(AccountWithKey):
+    """User-defined Solana account."""
+
+    def transfer_lamports(self, to: AccountWithKey, amount: u64):
+        """
+        Transfer some SOL directly to another account. Since this account is program-owned, this transfer does not require a CPI.
+
+        @param to: The recipient Solana account.
+        @param amount: The amount (in lamports, not SOL) to transfer.
+        """
+
+class Event:
+    """Anchor event that clients can listen for"""
+
+    def emit(self):
+        """
+        Emit the event to the blockchain
+        """
+
+class Signer(AccountWithKey):
+    """Instruction signer."""
+
+    def transfer_lamports(self, to: AccountWithKey, amount: u64):
+        """
+        Transfer some SOL directly to another account. Unlike using transfer_lamports from a program account, this transfer will require a CPI.
+
+        @param to: The recipient Solana account.
+        @param amount: The amount (in lamports, not SOL) to transfer.
+        """
+
+class Empty(Generic[T]):
+    """An account that needs to be initialized."""
+
+    def init(self, payer: Signer, seeds: List[Any] = None, mint: 'TokenMint' = None, decimals: u8 = None, authority: AccountWithKey = None, associated: bool = False, space: u64 = None, padding: u64 = None)  -> 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, other accounts, integers, or lists of bytes.
+        @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.
+        @param associated: If initializing an associated token account, must be set to true.
+        @param space: If initializing a program account, you can use this to overwrite Seahorse's calculation of the account size.
+        @param padding: If initializing a program account, you can use this to add extra space to Seahorse's calculation of the account size.
+        @returns: The new, initialized account. All of the data in this account will be set to 0, bytewise.
+        """
+
+    def bump(self) -> u8:
+        """
+        Get this account's bump, needed if you want to use this account to sign CPI calls.
+        
+        If you've initialized an account without seeds, then a bump will not have been calculated. This will result in a runtime error when you try to access it.
+        """
+
+    def key(self) -> Pubkey:
+        """Get this account's key."""
+
+class CpiAccount:
+    """Account and metadata used for making arbitrary CPIs (via `Program.invoke`)."""
+
+    def __init__(account: AccountWithKey, mut: bool = False, signer: bool = False, seeds: List[Any] = None) -> 'CpiAccount':
+        """
+        Create the CpiAccount.
+
+        @param account: The account being passed to the CPI.
+        @param mut: Whether this account needs to be mutable for the CPI - defaults to false.
+        @param signer: Whether this account needs to be an instruction signer - defaults to false. Mutually exclusive with seeds, and should only really be true if account is a Signer.
+        @param seeds: PDA signer seeds, if this account needs to sign the CPI. Mutually exclusive with signer.
+        """
+
+class Program(AccountWithKey):
+    """Arbitrary program."""
+
+    def invoke(self, accounts: List[CpiAccount], data: List[u8]):
+        """
+        Call this program in a cross-program invocation. Make sure you know what you're doing before you try using this - a poorly crafted data list could cost you real money.
+
+        @param accounts: List of accounts being passed to the CPI - the program itself does not need to be in here.
+        @param data: "Raw" list of bytes used to tell the program what to do, pass args, etc.
+        """
+
+class UncheckedAccount(AccountWithKey):
+    """
+    Raw account that has had no safety checks performed on it.
+    
+    The underlying Anchor code cannot guarantee anything about the account unless you check it in your instruction - not the type, not the data, not the program it came from. Use carefully.
+    """
+
+class Clock:
+    """
+    Solana's Clock sysvar.
+    
+    Consult Solana's reference to learn more. Information copied from https://docs.rs/solana-program/1.14.3/solana_program/clock/struct.Clock.html.
+    """
+
+    def slot(self) -> u64:
+        """Get the current network/bank Slot."""
+
+    def epoch_start_timestamp(self) -> i64:
+        """Get the timestamp of the first Slot in this Epoch."""
+
+    def epoch(self) -> u64:
+        """Get the bank Epoch."""
+
+    def leader_schedule_epoch(self) -> u64:
+        """Get the future Epoch for which the leader schedule has most recently been calculated."""
+
+    def unix_timestamp(self) -> i64:
+        """
+        Get the estimated current UNIX timestamp.
+        
+        Originally computed from genesis creation time and network time in slots (drifty); corrected using validator timestamp oracle as of timestamp_correction and timestamp_bounding features.
+        """
+
+class TokenAccount(AccountWithKey):
+    """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 mint(self) -> Pubkey:
+        """Get the mint that this token account corresponds to."""
+
+    def transfer(self, authority: AccountWithKey, to: 'TokenAccount', amount: u64, signer: List[Any] = 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(AccountWithKey):
+    """SPL token mint."""
+
+    def authority(self) -> Pubkey:
+        """Get the owner of this token mint."""
+
+    def freeze_authority(self) -> Pubkey:
+        """Get the freeze authority of this token mint."""
+
+    def decimals(self) -> u8:
+        """Get the number of decimals for this token."""
+
+    def supply(self) -> u64:
+        """Get the amount of this token that exists."""
+
+    def mint(self, authority: AccountWithKey, to: TokenAccount, amount: u64, signer: List[Any] = 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: AccountWithKey, holder: TokenAccount, amount: u64, signer: List[Any] = 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[..., None]:
+    """Decorator to turn a function into a program instruction."""
+
+def dataclass(function: Callable[..., None]) -> Callable[..., None]:
+    """Decorator to create an automatic default class constructor."""
+
+def int_bytes(n: Any, be: bool = False) -> List[u8]:
+    """
+    Convenience method to turn an integer type into a little-endian (by default) list of bytes.
+    
+    @param n: The integer you wish to convert to bytes.
+    @param be: Whether you want the conversion to be big-endian - defaults to false.
+    """
+
+def size(ob: str) -> u64:
+    """
+    Get the size of an object in bytes.
+    Currently this is only supported for strings.
+    
+    @param ob: The object to get the size of.
+    """

+ 2 - 0
tokens/transfer-tokens/seahorse/tests/pyproject.toml

@@ -0,0 +1,2 @@
+[tool.pyright]
+reportMissingModuleSource = false

+ 78 - 0
tokens/transfer-tokens/seahorse/tests/seahorse.py

@@ -0,0 +1,78 @@
+import asyncio
+from anchorpy import create_workspace, close_workspace, Context
+from solders.system_program import ID as SYS_PROGRAM_ID
+from solders.pubkey import Pubkey
+from solders.keypair import Keypair
+from solders.sysvar import RENT
+
+
+
+async def main():
+    # Read the deployed program from the workspace.
+    workspace = create_workspace()
+    program = workspace["seahorse"]
+
+    TOKEN_PROGRAM_ID = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
+    ASSOCITAED_TOKEN_PROGRAM_ID = Pubkey.from_string("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL")
+
+    # Create a Mint keypair. This will be our token mint.
+    mint = Keypair()
+
+
+    # Execute the instructions
+
+    # Create a token
+    create_token = await program.rpc["create_token"](ctx=Context(accounts={
+        "mint": mint.pubkey(),
+        "signer": program.provider.wallet.payer.pubkey(),
+        "system_program": SYS_PROGRAM_ID,
+        "rent": RENT,
+        "token_program": TOKEN_PROGRAM_ID
+    }, signers=[program.provider.wallet.payer, mint]))
+
+    print("Create token signature: ", create_token)
+
+    # Create a token account
+    associated_token_account_pubkey, nonce = Pubkey.find_program_address([bytes(program.provider.wallet.payer.pubkey()), bytes(TOKEN_PROGRAM_ID), bytes(mint.pubkey())], ASSOCITAED_TOKEN_PROGRAM_ID)
+    create_associated_token_account = await program.rpc["create_associated_token_account"](ctx=Context(accounts={
+        "mint": mint.pubkey(),
+        "token_account" : associated_token_account_pubkey,
+        "signer": program.provider.wallet.payer.pubkey(),
+        "system_program": SYS_PROGRAM_ID,
+        "rent": RENT,
+        "token_program": TOKEN_PROGRAM_ID,
+        "associated_token_program" : ASSOCITAED_TOKEN_PROGRAM_ID
+    }, signers=[program.provider.wallet.payer]))
+
+    print("Create associated token account signature: ", create_associated_token_account)
+
+    # Mint tokens
+    mint_token = await program.rpc["mint_token"](1000, ctx=Context(accounts={
+        "mint": mint.pubkey(),
+        "recipient" : associated_token_account_pubkey,
+        "signer": program.provider.wallet.payer.pubkey(),
+        "system_program": SYS_PROGRAM_ID,
+        "rent": RENT,
+        "token_program": TOKEN_PROGRAM_ID,
+    }, signers=[program.provider.wallet.payer]))
+
+    print("Mint token signature: ", mint_token)
+
+    # Transfer tokens (the tokens are transfered to the sender here, but you can of course create a new associated token account for the recipient)
+    transfer = await program.rpc["transfer"](1000, ctx=Context(accounts={
+        "signer_token_account": associated_token_account_pubkey,
+        "recipient" : associated_token_account_pubkey,
+        "signer": program.provider.wallet.payer.pubkey(),
+        "system_program": SYS_PROGRAM_ID,
+        "rent": RENT,
+        "token_program": TOKEN_PROGRAM_ID,
+    }, signers=[program.provider.wallet.payer]))
+
+    print("Transfer signature: ", transfer)
+
+
+    # Close all HTTP clients in the workspace.
+    await close_workspace(workspace)
+
+
+asyncio.run(main())

+ 11 - 0
tokens/transfer-tokens/seahorse/tsconfig.json

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