diff --git a/programs/token-2022/Cargo.toml b/programs/token-2022/Cargo.toml
new file mode 100644
index 00000000..66ab6e52
--- /dev/null
+++ b/programs/token-2022/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "pinocchio-token-2022"
+description = "Pinocchio helpers to invoke Token 2022 program instructions"
+version = "0.1.0"
+edition = { workspace = true }
+license = { workspace = true }
+readme = "./README.md"
+repository = { workspace = true }
+
+[lib]
+crate-type = ["rlib"]
+
+[dependencies]
+pinocchio = { workspace = true }
+pinocchio-pubkey = { workspace = true }
diff --git a/programs/token-2022/README.md b/programs/token-2022/README.md
new file mode 100644
index 00000000..b9579f87
--- /dev/null
+++ b/programs/token-2022/README.md
@@ -0,0 +1,40 @@
+#
+
+This crate contains [`pinocchio`](https://crates.io/crates/pinocchio) helpers to perform cross-program invocations (CPIs) for SPL Token 2022 instructions.
+
+Each instruction defines an `struct` with the accounts and parameters required. Once all values are set, you can call directly `invoke` or `invoke_signed` to perform the CPI.
+
+This is a `no_std` crate.
+
+> **Note:** The API defined in this crate is subject to change.
+
+## Examples
+
+Initializing a mint account:
+```rust
+// This example assumes that the instruction receives a writable `mint`
+// account; `authority` is a `Pubkey`.
+InitializeMint {
+ mint,
+ rent_sysvar,
+ decimals: 9,
+ mint_authority: authority,
+ freeze_authority: Some(authority),
+}.invoke()?;
+```
+
+Performing a transfer of tokens:
+```rust
+// This example assumes that the instruction receives writable `from` and `to`
+// accounts, and a signer `authority` account.
+Transfer {
+ from,
+ to,
+ authority,
+ amount: 10,
+}.invoke()?;
+```
+
+## License
+
+The code is licensed under the [Apache License Version 2.0](../LICENSE)
diff --git a/programs/token-2022/src/instructions/approve.rs b/programs/token-2022/src/instructions/approve.rs
new file mode 100644
index 00000000..80355a7d
--- /dev/null
+++ b/programs/token-2022/src/instructions/approve.rs
@@ -0,0 +1,65 @@
+use core::slice::from_raw_parts;
+
+use pinocchio::{
+ account_info::AccountInfo,
+ instruction::{AccountMeta, Instruction, Signer},
+ program::invoke_signed,
+ ProgramResult,
+};
+
+use crate::{write_bytes, UNINIT_BYTE};
+
+/// Approves a delegate.
+///
+/// ### Accounts:
+/// 0. `[WRITE]` The token account.
+/// 1. `[]` The delegate.
+/// 2. `[SIGNER]` The source account owner.
+pub struct Approve<'a> {
+ /// Source Account.
+ pub source: &'a AccountInfo,
+ /// Delegate Account
+ pub delegate: &'a AccountInfo,
+ /// Source Owner Account
+ pub authority: &'a AccountInfo,
+ /// Amount
+ pub amount: u64,
+}
+
+impl Approve<'_> {
+ #[inline(always)]
+ pub fn invoke(&self) -> ProgramResult {
+ self.invoke_signed(&[])
+ }
+
+ pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
+ // Account metadata
+ let account_metas: [AccountMeta; 3] = [
+ AccountMeta::writable(self.source.key()),
+ AccountMeta::readonly(self.delegate.key()),
+ AccountMeta::readonly_signer(self.authority.key()),
+ ];
+
+ // Instruction data
+ // - [0]: instruction discriminator (1 byte, u8)
+ // - [1..9]: amount (8 bytes, u64)
+ let mut instruction_data = [UNINIT_BYTE; 9];
+
+ // Set discriminator as u8 at offset [0]
+ write_bytes(&mut instruction_data, &[4]);
+ // Set amount as u64 at offset [1..9]
+ write_bytes(&mut instruction_data[1..], &self.amount.to_le_bytes());
+
+ let instruction = Instruction {
+ program_id: &crate::ID,
+ accounts: &account_metas,
+ data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 9) },
+ };
+
+ invoke_signed(
+ &instruction,
+ &[self.source, self.delegate, self.authority],
+ signers,
+ )
+ }
+}
diff --git a/programs/token-2022/src/instructions/approve_checked.rs b/programs/token-2022/src/instructions/approve_checked.rs
new file mode 100644
index 00000000..9c924936
--- /dev/null
+++ b/programs/token-2022/src/instructions/approve_checked.rs
@@ -0,0 +1,74 @@
+use core::slice::from_raw_parts;
+
+use pinocchio::{
+ account_info::AccountInfo,
+ instruction::{AccountMeta, Instruction, Signer},
+ program::invoke_signed,
+ ProgramResult,
+};
+
+use crate::{write_bytes, UNINIT_BYTE};
+
+/// Approves a delegate.
+///
+/// ### Accounts:
+/// 0. `[WRITE]` The source account.
+/// 1. `[]` The token mint.
+/// 2. `[]` The delegate.
+/// 3. `[SIGNER]` The source account owner.
+pub struct ApproveChecked<'a> {
+ /// Source Account.
+ pub source: &'a AccountInfo,
+ /// Mint Account.
+ pub mint: &'a AccountInfo,
+ /// Delegate Account.
+ pub delegate: &'a AccountInfo,
+ /// Source Owner Account.
+ pub authority: &'a AccountInfo,
+ /// Amount.
+ pub amount: u64,
+ /// Decimals.
+ pub decimals: u8,
+}
+
+impl ApproveChecked<'_> {
+ #[inline(always)]
+ pub fn invoke(&self) -> ProgramResult {
+ self.invoke_signed(&[])
+ }
+
+ pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
+ // Account metadata
+ let account_metas: [AccountMeta; 4] = [
+ AccountMeta::writable(self.source.key()),
+ AccountMeta::readonly(self.mint.key()),
+ AccountMeta::readonly(self.delegate.key()),
+ AccountMeta::readonly_signer(self.authority.key()),
+ ];
+
+ // Instruction data
+ // - [0] : instruction discriminator (1 byte, u8)
+ // - [1..9]: amount (8 bytes, u64)
+ // - [9] : decimals (1 byte, u8)
+ let mut instruction_data = [UNINIT_BYTE; 10];
+
+ // Set discriminator as u8 at offset [0]
+ write_bytes(&mut instruction_data, &[13]);
+ // Set amount as u64 at offset [1..9]
+ write_bytes(&mut instruction_data[1..9], &self.amount.to_le_bytes());
+ // Set decimals as u8 at offset [9]
+ write_bytes(&mut instruction_data[9..], &[self.decimals]);
+
+ let instruction = Instruction {
+ program_id: &crate::ID,
+ accounts: &account_metas,
+ data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 10) },
+ };
+
+ invoke_signed(
+ &instruction,
+ &[self.source, self.mint, self.delegate, self.authority],
+ signers,
+ )
+ }
+}
diff --git a/programs/token-2022/src/instructions/burn.rs b/programs/token-2022/src/instructions/burn.rs
new file mode 100644
index 00000000..d379faf1
--- /dev/null
+++ b/programs/token-2022/src/instructions/burn.rs
@@ -0,0 +1,65 @@
+use core::slice::from_raw_parts;
+
+use pinocchio::{
+ account_info::AccountInfo,
+ instruction::{AccountMeta, Instruction, Signer},
+ program::invoke_signed,
+ ProgramResult,
+};
+
+use crate::{write_bytes, UNINIT_BYTE};
+
+/// Burns tokens by removing them from an account.
+///
+/// ### Accounts:
+/// 0. `[WRITE]` The account to burn from.
+/// 1. `[WRITE]` The token mint.
+/// 2. `[SIGNER]` The account's owner/delegate.
+pub struct Burn<'a> {
+ /// Source of the Burn Account
+ pub account: &'a AccountInfo,
+ /// Mint Account
+ pub mint: &'a AccountInfo,
+ /// Owner of the Token Account
+ pub authority: &'a AccountInfo,
+ /// Amount
+ pub amount: u64,
+}
+
+impl Burn<'_> {
+ #[inline(always)]
+ pub fn invoke(&self) -> ProgramResult {
+ self.invoke_signed(&[])
+ }
+
+ pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
+ // Account metadata
+ let account_metas: [AccountMeta; 3] = [
+ AccountMeta::writable(self.account.key()),
+ AccountMeta::writable(self.mint.key()),
+ AccountMeta::readonly_signer(self.authority.key()),
+ ];
+
+ // Instruction data
+ // - [0]: instruction discriminator (1 byte, u8)
+ // - [1..9]: amount (8 bytes, u64)
+ let mut instruction_data = [UNINIT_BYTE; 9];
+
+ // Set discriminator as u8 at offset [0]
+ write_bytes(&mut instruction_data, &[8]);
+ // Set amount as u64 at offset [1..9]
+ write_bytes(&mut instruction_data[1..], &self.amount.to_le_bytes());
+
+ let instruction = Instruction {
+ program_id: &crate::ID,
+ accounts: &account_metas,
+ data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 9) },
+ };
+
+ invoke_signed(
+ &instruction,
+ &[self.account, self.mint, self.authority],
+ signers,
+ )
+ }
+}
diff --git a/programs/token-2022/src/instructions/burn_checked.rs b/programs/token-2022/src/instructions/burn_checked.rs
new file mode 100644
index 00000000..cd316644
--- /dev/null
+++ b/programs/token-2022/src/instructions/burn_checked.rs
@@ -0,0 +1,69 @@
+use core::slice::from_raw_parts;
+
+use crate::{write_bytes, UNINIT_BYTE};
+use pinocchio::{
+ account_info::AccountInfo,
+ instruction::{AccountMeta, Instruction, Signer},
+ program::invoke_signed,
+ ProgramResult,
+};
+
+/// Burns tokens by removing them from an account.
+///
+/// ### Accounts:
+/// 0. `[WRITE]` The account to burn from.
+/// 1. `[WRITE]` The token mint.
+/// 2. `[SIGNER]` The account's owner/delegate.
+pub struct BurnChecked<'a> {
+ /// Source of the Burn Account
+ pub account: &'a AccountInfo,
+ /// Mint Account
+ pub mint: &'a AccountInfo,
+ /// Owner of the Token Account
+ pub authority: &'a AccountInfo,
+ /// Amount
+ pub amount: u64,
+ /// Decimals
+ pub decimals: u8,
+}
+
+impl BurnChecked<'_> {
+ #[inline(always)]
+ pub fn invoke(&self) -> ProgramResult {
+ self.invoke_signed(&[])
+ }
+
+ pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
+ // Account metadata
+ let account_metas: [AccountMeta; 3] = [
+ AccountMeta::writable(self.account.key()),
+ AccountMeta::writable(self.mint.key()),
+ AccountMeta::readonly_signer(self.authority.key()),
+ ];
+
+ // Instruction data
+ // - [0]: instruction discriminator (1 byte, u8)
+ // - [1..9]: amount (8 bytes, u64)
+ // - [9]: decimals (1 byte, u8)
+ let mut instruction_data = [UNINIT_BYTE; 10];
+
+ // Set discriminator as u8 at offset [0]
+ write_bytes(&mut instruction_data, &[15]);
+ // Set amount as u64 at offset [1..9]
+ write_bytes(&mut instruction_data[1..9], &self.amount.to_le_bytes());
+ // Set decimals as u8 at offset [9]
+ write_bytes(&mut instruction_data[9..], &[self.decimals]);
+
+ let instruction = Instruction {
+ program_id: &crate::ID,
+ accounts: &account_metas,
+ data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 10) },
+ };
+
+ invoke_signed(
+ &instruction,
+ &[self.account, self.mint, self.authority],
+ signers,
+ )
+ }
+}
diff --git a/programs/token-2022/src/instructions/close_account.rs b/programs/token-2022/src/instructions/close_account.rs
new file mode 100644
index 00000000..0d6d6947
--- /dev/null
+++ b/programs/token-2022/src/instructions/close_account.rs
@@ -0,0 +1,49 @@
+use pinocchio::{
+ account_info::AccountInfo,
+ instruction::{AccountMeta, Instruction, Signer},
+ program::invoke_signed,
+ ProgramResult,
+};
+
+/// Close an account by transferring all its SOL to the destination account.
+///
+/// ### Accounts:
+/// 0. `[WRITE]` The account to close.
+/// 1. `[WRITE]` The destination account.
+/// 2. `[SIGNER]` The account's owner.
+pub struct CloseAccount<'a> {
+ /// Token Account.
+ pub account: &'a AccountInfo,
+ /// Destination Account
+ pub destination: &'a AccountInfo,
+ /// Owner Account
+ pub authority: &'a AccountInfo,
+}
+
+impl CloseAccount<'_> {
+ #[inline(always)]
+ pub fn invoke(&self) -> ProgramResult {
+ self.invoke_signed(&[])
+ }
+
+ pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
+ // account metadata
+ let account_metas: [AccountMeta; 3] = [
+ AccountMeta::writable(self.account.key()),
+ AccountMeta::writable(self.destination.key()),
+ AccountMeta::readonly_signer(self.authority.key()),
+ ];
+
+ let instruction = Instruction {
+ program_id: &crate::ID,
+ accounts: &account_metas,
+ data: &[9],
+ };
+
+ invoke_signed(
+ &instruction,
+ &[self.account, self.destination, self.authority],
+ signers,
+ )
+ }
+}
diff --git a/programs/token-2022/src/instructions/freeze_account.rs b/programs/token-2022/src/instructions/freeze_account.rs
new file mode 100644
index 00000000..3cf2fb55
--- /dev/null
+++ b/programs/token-2022/src/instructions/freeze_account.rs
@@ -0,0 +1,49 @@
+use pinocchio::{
+ account_info::AccountInfo,
+ instruction::{AccountMeta, Instruction, Signer},
+ program::invoke_signed,
+ ProgramResult,
+};
+
+/// Freeze an Initialized account using the Mint's freeze_authority
+///
+/// ### Accounts:
+/// 0. `[WRITE]` The account to freeze.
+/// 1. `[]` The token mint.
+/// 2. `[SIGNER]` The mint freeze authority.
+pub struct FreezeAccount<'a> {
+ /// Token Account to freeze.
+ pub account: &'a AccountInfo,
+ /// Mint Account.
+ pub mint: &'a AccountInfo,
+ /// Mint Freeze Authority Account
+ pub freeze_authority: &'a AccountInfo,
+}
+
+impl FreezeAccount<'_> {
+ #[inline(always)]
+ pub fn invoke(&self) -> ProgramResult {
+ self.invoke_signed(&[])
+ }
+
+ pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
+ // account metadata
+ let account_metas: [AccountMeta; 3] = [
+ AccountMeta::writable(self.account.key()),
+ AccountMeta::readonly(self.mint.key()),
+ AccountMeta::readonly_signer(self.freeze_authority.key()),
+ ];
+
+ let instruction = Instruction {
+ program_id: &crate::ID,
+ accounts: &account_metas,
+ data: &[10],
+ };
+
+ invoke_signed(
+ &instruction,
+ &[self.account, self.mint, self.freeze_authority],
+ signers,
+ )
+ }
+}
diff --git a/programs/token-2022/src/instructions/initialize_account.rs b/programs/token-2022/src/instructions/initialize_account.rs
new file mode 100644
index 00000000..cd608146
--- /dev/null
+++ b/programs/token-2022/src/instructions/initialize_account.rs
@@ -0,0 +1,53 @@
+use pinocchio::{
+ account_info::AccountInfo,
+ instruction::{AccountMeta, Instruction, Signer},
+ program::invoke_signed,
+ ProgramResult,
+};
+
+/// Initialize a new Token Account.
+///
+/// ### Accounts:
+/// 0. `[WRITE]` The account to initialize.
+/// 1. `[]` The mint this account will be associated with.
+/// 2. `[]` The new account's owner/multisignature.
+/// 3. `[]` Rent sysvar
+pub struct InitializeAccount<'a> {
+ /// New Account.
+ pub account: &'a AccountInfo,
+ /// Mint Account.
+ pub mint: &'a AccountInfo,
+ /// Owner of the new Account.
+ pub owner: &'a AccountInfo,
+ /// Rent Sysvar Account
+ pub rent_sysvar: &'a AccountInfo,
+}
+
+impl InitializeAccount<'_> {
+ #[inline(always)]
+ pub fn invoke(&self) -> ProgramResult {
+ self.invoke_signed(&[])
+ }
+
+ pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
+ // account metadata
+ let account_metas: [AccountMeta; 4] = [
+ AccountMeta::writable(self.account.key()),
+ AccountMeta::readonly(self.mint.key()),
+ AccountMeta::readonly(self.owner.key()),
+ AccountMeta::readonly(self.rent_sysvar.key()),
+ ];
+
+ let instruction = Instruction {
+ program_id: &crate::ID,
+ accounts: &account_metas,
+ data: &[1],
+ };
+
+ invoke_signed(
+ &instruction,
+ &[self.account, self.mint, self.owner, self.rent_sysvar],
+ signers,
+ )
+ }
+}
diff --git a/programs/token-2022/src/instructions/initialize_account_2.rs b/programs/token-2022/src/instructions/initialize_account_2.rs
new file mode 100644
index 00000000..8dfa41b8
--- /dev/null
+++ b/programs/token-2022/src/instructions/initialize_account_2.rs
@@ -0,0 +1,66 @@
+use core::slice::from_raw_parts;
+
+use pinocchio::{
+ account_info::AccountInfo,
+ instruction::{AccountMeta, Instruction, Signer},
+ program::invoke_signed,
+ pubkey::Pubkey,
+ ProgramResult,
+};
+
+use crate::{write_bytes, UNINIT_BYTE};
+
+/// Initialize a new Token Account.
+///
+/// ### Accounts:
+/// 0. `[WRITE]` The account to initialize.
+/// 1. `[]` The mint this account will be associated with.
+/// 3. `[]` Rent sysvar
+pub struct InitializeAccount2<'a> {
+ /// New Account.
+ pub account: &'a AccountInfo,
+ /// Mint Account.
+ pub mint: &'a AccountInfo,
+ /// Rent Sysvar Account
+ pub rent_sysvar: &'a AccountInfo,
+ /// Owner of the new Account.
+ pub owner: &'a Pubkey,
+}
+
+impl InitializeAccount2<'_> {
+ #[inline(always)]
+ pub fn invoke(&self) -> ProgramResult {
+ self.invoke_signed(&[])
+ }
+
+ pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
+ // account metadata
+ let account_metas: [AccountMeta; 3] = [
+ AccountMeta::writable(self.account.key()),
+ AccountMeta::readonly(self.mint.key()),
+ AccountMeta::readonly(self.rent_sysvar.key()),
+ ];
+
+ // instruction data
+ // - [0]: instruction discriminator (1 byte, u8)
+ // - [1..33]: owner (32 bytes, Pubkey)
+ let mut instruction_data = [UNINIT_BYTE; 33];
+
+ // Set discriminator as u8 at offset [0]
+ write_bytes(&mut instruction_data, &[16]);
+ // Set owner as [u8; 32] at offset [1..33]
+ write_bytes(&mut instruction_data[1..], self.owner);
+
+ let instruction = Instruction {
+ program_id: &crate::ID,
+ accounts: &account_metas,
+ data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 33) },
+ };
+
+ invoke_signed(
+ &instruction,
+ &[self.account, self.mint, self.rent_sysvar],
+ signers,
+ )
+ }
+}
diff --git a/programs/token-2022/src/instructions/initialize_account_3.rs b/programs/token-2022/src/instructions/initialize_account_3.rs
new file mode 100644
index 00000000..65c06392
--- /dev/null
+++ b/programs/token-2022/src/instructions/initialize_account_3.rs
@@ -0,0 +1,58 @@
+use core::slice::from_raw_parts;
+
+use pinocchio::{
+ account_info::AccountInfo,
+ instruction::{AccountMeta, Instruction, Signer},
+ program::invoke_signed,
+ pubkey::Pubkey,
+ ProgramResult,
+};
+
+use crate::{write_bytes, UNINIT_BYTE};
+
+/// Initialize a new Token Account.
+///
+/// ### Accounts:
+/// 0. `[WRITE]` The account to initialize.
+/// 1. `[]` The mint this account will be associated with.
+pub struct InitializeAccount3<'a> {
+ /// New Account.
+ pub account: &'a AccountInfo,
+ /// Mint Account.
+ pub mint: &'a AccountInfo,
+ /// Owner of the new Account.
+ pub owner: &'a Pubkey,
+}
+
+impl InitializeAccount3<'_> {
+ #[inline(always)]
+ pub fn invoke(&self) -> ProgramResult {
+ self.invoke_signed(&[])
+ }
+
+ pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
+ // account metadata
+ let account_metas: [AccountMeta; 2] = [
+ AccountMeta::writable(self.account.key()),
+ AccountMeta::readonly(self.mint.key()),
+ ];
+
+ // instruction data
+ // - [0]: instruction discriminator (1 byte, u8)
+ // - [1..33]: owner (32 bytes, Pubkey)
+ let mut instruction_data = [UNINIT_BYTE; 33];
+
+ // Set discriminator as u8 at offset [0]
+ write_bytes(&mut instruction_data, &[18]);
+ // Set owner as [u8; 32] at offset [1..33]
+ write_bytes(&mut instruction_data[1..], self.owner);
+
+ let instruction = Instruction {
+ program_id: &crate::ID,
+ accounts: &account_metas,
+ data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 33) },
+ };
+
+ invoke_signed(&instruction, &[self.account, self.mint], signers)
+ }
+}
diff --git a/programs/token-2022/src/instructions/initialize_mint.rs b/programs/token-2022/src/instructions/initialize_mint.rs
new file mode 100644
index 00000000..2e93eea2
--- /dev/null
+++ b/programs/token-2022/src/instructions/initialize_mint.rs
@@ -0,0 +1,74 @@
+use core::slice::from_raw_parts;
+
+use pinocchio::{
+ account_info::AccountInfo,
+ instruction::{AccountMeta, Instruction, Signer},
+ program::invoke_signed,
+ pubkey::Pubkey,
+ ProgramResult,
+};
+
+use crate::{write_bytes, UNINIT_BYTE};
+
+/// Initialize a new mint.
+///
+/// ### Accounts:
+/// 0. `[WRITABLE]` Mint account
+/// 1. `[]` Rent sysvar
+pub struct InitializeMint<'a> {
+ /// Mint Account.
+ pub mint: &'a AccountInfo,
+ /// Rent sysvar Account.
+ pub rent_sysvar: &'a AccountInfo,
+ /// Decimals.
+ pub decimals: u8,
+ /// Mint Authority.
+ pub mint_authority: &'a Pubkey,
+ /// Freeze Authority.
+ pub freeze_authority: Option<&'a Pubkey>,
+}
+
+impl InitializeMint<'_> {
+ #[inline(always)]
+ pub fn invoke(&self) -> ProgramResult {
+ self.invoke_signed(&[])
+ }
+
+ pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
+ // Account metadata
+ let account_metas: [AccountMeta; 2] = [
+ AccountMeta::writable(self.mint.key()),
+ AccountMeta::readonly(self.rent_sysvar.key()),
+ ];
+
+ // Instruction data layout:
+ // - [0]: instruction discriminator (1 byte, u8)
+ // - [1]: decimals (1 byte, u8)
+ // - [2..34]: mint_authority (32 bytes, Pubkey)
+ // - [34]: freeze_authority presence flag (1 byte, u8)
+ // - [35..67]: freeze_authority (optional, 32 bytes, Pubkey)
+ let mut instruction_data = [UNINIT_BYTE; 67];
+
+ // Set discriminator as u8 at offset [0]
+ write_bytes(&mut instruction_data, &[0]);
+ // Set decimals as u8 at offset [1]
+ write_bytes(&mut instruction_data[1..2], &[self.decimals]);
+ // Set mint_authority as Pubkey at offset [2..34]
+ write_bytes(&mut instruction_data[2..34], self.mint_authority);
+ // Set COption & freeze_authority at offset [34..67]
+ if let Some(freeze_auth) = self.freeze_authority {
+ write_bytes(&mut instruction_data[34..35], &[1]);
+ write_bytes(&mut instruction_data[35..], freeze_auth);
+ } else {
+ write_bytes(&mut instruction_data[34..35], &[0]);
+ }
+
+ let instruction = Instruction {
+ program_id: &crate::ID,
+ accounts: &account_metas,
+ data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 67) },
+ };
+
+ invoke_signed(&instruction, &[self.mint, self.rent_sysvar], signers)
+ }
+}
diff --git a/programs/token-2022/src/instructions/initialize_mint_2.rs b/programs/token-2022/src/instructions/initialize_mint_2.rs
new file mode 100644
index 00000000..7bf9f81c
--- /dev/null
+++ b/programs/token-2022/src/instructions/initialize_mint_2.rs
@@ -0,0 +1,68 @@
+use core::slice::from_raw_parts;
+
+use pinocchio::{
+ account_info::AccountInfo,
+ instruction::{AccountMeta, Instruction, Signer},
+ program::invoke_signed,
+ pubkey::Pubkey,
+ ProgramResult,
+};
+
+use crate::{write_bytes, UNINIT_BYTE};
+
+/// Initialize a new mint.
+///
+/// ### Accounts:
+/// 0. `[WRITABLE]` Mint account
+pub struct InitializeMint2<'a> {
+ /// Mint Account.
+ pub mint: &'a AccountInfo,
+ /// Decimals.
+ pub decimals: u8,
+ /// Mint Authority.
+ pub mint_authority: &'a Pubkey,
+ /// Freeze Authority.
+ pub freeze_authority: Option<&'a Pubkey>,
+}
+
+impl InitializeMint2<'_> {
+ #[inline(always)]
+ pub fn invoke(&self) -> ProgramResult {
+ self.invoke_signed(&[])
+ }
+
+ pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
+ // Account metadata
+ let account_metas: [AccountMeta; 1] = [AccountMeta::writable(self.mint.key())];
+
+ // Instruction data layout:
+ // - [0]: instruction discriminator (1 byte, u8)
+ // - [1]: decimals (1 byte, u8)
+ // - [2..34]: mint_authority (32 bytes, Pubkey)
+ // - [34]: freeze_authority presence flag (1 byte, u8)
+ // - [35..67]: freeze_authority (optional, 32 bytes, Pubkey)
+ let mut instruction_data = [UNINIT_BYTE; 67];
+
+ // Set discriminator as u8 at offset [0]
+ write_bytes(&mut instruction_data, &[20]);
+ // Set decimals as u8 at offset [1]
+ write_bytes(&mut instruction_data[1..2], &[self.decimals]);
+ // Set mint_authority as Pubkey at offset [2..34]
+ write_bytes(&mut instruction_data[2..34], self.mint_authority);
+ // Set COption & freeze_authority at offset [34..67]
+ if let Some(freeze_auth) = self.freeze_authority {
+ write_bytes(&mut instruction_data[34..35], &[1]);
+ write_bytes(&mut instruction_data[35..], freeze_auth);
+ } else {
+ write_bytes(&mut instruction_data[34..35], &[0]);
+ }
+
+ let instruction = Instruction {
+ program_id: &crate::ID,
+ accounts: &account_metas,
+ data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 67) },
+ };
+
+ invoke_signed(&instruction, &[self.mint], signers)
+ }
+}
diff --git a/programs/token-2022/src/instructions/mint_to.rs b/programs/token-2022/src/instructions/mint_to.rs
new file mode 100644
index 00000000..91ce6f90
--- /dev/null
+++ b/programs/token-2022/src/instructions/mint_to.rs
@@ -0,0 +1,66 @@
+use core::slice::from_raw_parts;
+
+use pinocchio::{
+ account_info::AccountInfo,
+ instruction::{AccountMeta, Instruction, Signer},
+ program::invoke_signed,
+ ProgramResult,
+};
+
+use crate::{write_bytes, UNINIT_BYTE};
+
+/// Mints new tokens to an account.
+///
+/// ### Accounts:
+/// 0. `[WRITE]` The mint.
+/// 1. `[WRITE]` The account to mint tokens to.
+/// 2. `[SIGNER]` The mint's minting authority.
+///
+pub struct MintTo<'a> {
+ /// Mint Account.
+ pub mint: &'a AccountInfo,
+ /// Token Account.
+ pub account: &'a AccountInfo,
+ /// Mint Authority
+ pub mint_authority: &'a AccountInfo,
+ /// Amount
+ pub amount: u64,
+}
+
+impl MintTo<'_> {
+ #[inline(always)]
+ pub fn invoke(&self) -> ProgramResult {
+ self.invoke_signed(&[])
+ }
+
+ pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
+ // account metadata
+ let account_metas: [AccountMeta; 3] = [
+ AccountMeta::writable(self.mint.key()),
+ AccountMeta::writable(self.account.key()),
+ AccountMeta::readonly_signer(self.mint_authority.key()),
+ ];
+
+ // Instruction data layout:
+ // - [0]: instruction discriminator (1 byte, u8)
+ // - [1..9]: amount (8 bytes, u64)
+ let mut instruction_data = [UNINIT_BYTE; 9];
+
+ // Set discriminator as u8 at offset [0]
+ write_bytes(&mut instruction_data, &[7]);
+ // Set amount as u64 at offset [1..9]
+ write_bytes(&mut instruction_data[1..9], &self.amount.to_le_bytes());
+
+ let instruction = Instruction {
+ program_id: &crate::ID,
+ accounts: &account_metas,
+ data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 9) },
+ };
+
+ invoke_signed(
+ &instruction,
+ &[self.mint, self.account, self.mint_authority],
+ signers,
+ )
+ }
+}
diff --git a/programs/token-2022/src/instructions/mint_to_checked.rs b/programs/token-2022/src/instructions/mint_to_checked.rs
new file mode 100644
index 00000000..9496932c
--- /dev/null
+++ b/programs/token-2022/src/instructions/mint_to_checked.rs
@@ -0,0 +1,71 @@
+use core::slice::from_raw_parts;
+
+use pinocchio::{
+ account_info::AccountInfo,
+ instruction::{AccountMeta, Instruction, Signer},
+ program::invoke_signed,
+ ProgramResult,
+};
+
+use crate::{write_bytes, UNINIT_BYTE};
+
+/// Mints new tokens to an account.
+///
+/// ### Accounts:
+/// 0. `[WRITE]` The mint.
+/// 1. `[WRITE]` The account to mint tokens to.
+/// 2. `[SIGNER]` The mint's minting authority.
+///
+pub struct MintToChecked<'a> {
+ /// Mint Account.
+ pub mint: &'a AccountInfo,
+ /// Token Account.
+ pub account: &'a AccountInfo,
+ /// Mint Authority
+ pub mint_authority: &'a AccountInfo,
+ /// Amount
+ pub amount: u64,
+ /// Decimals
+ pub decimals: u8,
+}
+
+impl MintToChecked<'_> {
+ #[inline(always)]
+ pub fn invoke(&self) -> ProgramResult {
+ self.invoke_signed(&[])
+ }
+
+ pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
+ // account metadata
+ let account_metas: [AccountMeta; 3] = [
+ AccountMeta::writable(self.mint.key()),
+ AccountMeta::writable(self.account.key()),
+ AccountMeta::readonly_signer(self.mint_authority.key()),
+ ];
+
+ // Instruction data layout:
+ // - [0]: instruction discriminator (1 byte, u8)
+ // - [1..9]: amount (8 bytes, u64)
+ // - [9]: decimals (1 byte, u8)
+ let mut instruction_data = [UNINIT_BYTE; 10];
+
+ // Set discriminator as u8 at offset [0]
+ write_bytes(&mut instruction_data, &[14]);
+ // Set amount as u64 at offset [1..9]
+ write_bytes(&mut instruction_data[1..9], &self.amount.to_le_bytes());
+ // Set decimals as u8 at offset [9]
+ write_bytes(&mut instruction_data[9..], &[self.decimals]);
+
+ let instruction = Instruction {
+ program_id: &crate::ID,
+ accounts: &account_metas,
+ data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 10) },
+ };
+
+ invoke_signed(
+ &instruction,
+ &[self.mint, self.account, self.mint_authority],
+ signers,
+ )
+ }
+}
diff --git a/programs/token-2022/src/instructions/mod.rs b/programs/token-2022/src/instructions/mod.rs
new file mode 100644
index 00000000..9287f8f9
--- /dev/null
+++ b/programs/token-2022/src/instructions/mod.rs
@@ -0,0 +1,39 @@
+mod approve;
+mod approve_checked;
+mod burn;
+mod burn_checked;
+mod close_account;
+mod freeze_account;
+mod initialize_account;
+mod initialize_account_2;
+mod initialize_account_3;
+mod initialize_mint;
+mod initialize_mint_2;
+mod mint_to;
+mod mint_to_checked;
+mod revoke;
+mod set_authority;
+mod sync_native;
+mod thaw_account;
+mod transfer;
+mod transfer_checked;
+
+pub use approve::*;
+pub use approve_checked::*;
+pub use burn::*;
+pub use burn_checked::*;
+pub use close_account::*;
+pub use freeze_account::*;
+pub use initialize_account::*;
+pub use initialize_account_2::*;
+pub use initialize_account_3::*;
+pub use initialize_mint::*;
+pub use initialize_mint_2::*;
+pub use mint_to::*;
+pub use mint_to_checked::*;
+pub use revoke::*;
+pub use set_authority::*;
+pub use sync_native::*;
+pub use thaw_account::*;
+pub use transfer::*;
+pub use transfer_checked::*;
diff --git a/programs/token-2022/src/instructions/revoke.rs b/programs/token-2022/src/instructions/revoke.rs
new file mode 100644
index 00000000..cbb4c815
--- /dev/null
+++ b/programs/token-2022/src/instructions/revoke.rs
@@ -0,0 +1,41 @@
+use pinocchio::{
+ account_info::AccountInfo,
+ instruction::{AccountMeta, Instruction, Signer},
+ program::invoke_signed,
+ ProgramResult,
+};
+
+/// Revokes the delegate's authority.
+///
+/// ### Accounts:
+/// 0. `[WRITE]` The source account.
+/// 1. `[SIGNER]` The source account owner.
+pub struct Revoke<'a> {
+ /// Source Account.
+ pub source: &'a AccountInfo,
+ /// Source Owner Account.
+ pub authority: &'a AccountInfo,
+}
+
+impl Revoke<'_> {
+ #[inline(always)]
+ pub fn invoke(&self) -> ProgramResult {
+ self.invoke_signed(&[])
+ }
+
+ pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
+ // account metadata
+ let account_metas: [AccountMeta; 2] = [
+ AccountMeta::writable(self.source.key()),
+ AccountMeta::readonly_signer(self.authority.key()),
+ ];
+
+ let instruction = Instruction {
+ program_id: &crate::ID,
+ accounts: &account_metas,
+ data: &[5],
+ };
+
+ invoke_signed(&instruction, &[self.source, self.authority], signers)
+ }
+}
diff --git a/programs/token-2022/src/instructions/set_authority.rs b/programs/token-2022/src/instructions/set_authority.rs
new file mode 100644
index 00000000..ec39bbe8
--- /dev/null
+++ b/programs/token-2022/src/instructions/set_authority.rs
@@ -0,0 +1,81 @@
+use core::slice::from_raw_parts;
+
+use pinocchio::{
+ account_info::AccountInfo,
+ instruction::{AccountMeta, Instruction, Signer},
+ program::invoke_signed,
+ pubkey::Pubkey,
+ ProgramResult,
+};
+
+use crate::{write_bytes, UNINIT_BYTE};
+
+#[repr(u8)]
+#[derive(Clone, Copy)]
+pub enum AuthorityType {
+ MintTokens = 0,
+ FreezeAccount = 1,
+ AccountOwner = 2,
+ CloseAccount = 3,
+}
+
+/// Sets a new authority of a mint or account.
+///
+/// ### Accounts:
+/// 0. `[WRITE]` The mint or account to change the authority of.
+/// 1. `[SIGNER]` The current authority of the mint or account.
+pub struct SetAuthority<'a> {
+ /// Account (Mint or Token)
+ pub account: &'a AccountInfo,
+
+ /// Authority of the Account.
+ pub authority: &'a AccountInfo,
+
+ /// The type of authority to update.
+ pub authority_type: AuthorityType,
+
+ /// The new authority
+ pub new_authority: Option<&'a Pubkey>,
+}
+
+impl SetAuthority<'_> {
+ #[inline(always)]
+ pub fn invoke(&self) -> ProgramResult {
+ self.invoke_signed(&[])
+ }
+
+ pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
+ // account metadata
+ let account_metas: [AccountMeta; 2] = [
+ AccountMeta::writable(self.account.key()),
+ AccountMeta::readonly_signer(self.authority.key()),
+ ];
+
+ // instruction data
+ // - [0]: instruction discriminator (1 byte, u8)
+ // - [1]: authority_type (1 byte, u8)
+ // - [2]: new_authority presence flag (1 byte, AuthorityType)
+ // - [3..35] new_authority (optional, 32 bytes, Pubkey)
+ let mut instruction_data = [UNINIT_BYTE; 35];
+
+ // Set discriminator as u8 at offset [0]
+ write_bytes(&mut instruction_data, &[6]);
+ // Set authority_type as u8 at offset [1]
+ write_bytes(&mut instruction_data[1..2], &[self.authority_type as u8]);
+ // Set new_authority as [u8; 32] at offset [2..35]
+ if let Some(new_authority) = self.new_authority {
+ write_bytes(&mut instruction_data[2..3], &[1]);
+ write_bytes(&mut instruction_data[3..], new_authority);
+ } else {
+ write_bytes(&mut instruction_data[2..3], &[0]);
+ }
+
+ let instruction = Instruction {
+ program_id: &crate::ID,
+ accounts: &account_metas,
+ data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 35) },
+ };
+
+ invoke_signed(&instruction, &[self.account, self.authority], signers)
+ }
+}
diff --git a/programs/token-2022/src/instructions/sync_native.rs b/programs/token-2022/src/instructions/sync_native.rs
new file mode 100644
index 00000000..b6ea680c
--- /dev/null
+++ b/programs/token-2022/src/instructions/sync_native.rs
@@ -0,0 +1,37 @@
+use pinocchio::{
+ account_info::AccountInfo,
+ instruction::{AccountMeta, Instruction, Signer},
+ program::invoke_signed,
+ ProgramResult,
+};
+
+/// Given a native token account updates its amount field based
+/// on the account's underlying `lamports`.
+///
+/// ### Accounts:
+/// 0. `[WRITE]` The native token account to sync with its underlying
+/// lamports.
+pub struct SyncNative<'a> {
+ /// Native Token Account
+ pub native_token: &'a AccountInfo,
+}
+
+impl SyncNative<'_> {
+ #[inline(always)]
+ pub fn invoke(&self) -> ProgramResult {
+ self.invoke_signed(&[])
+ }
+
+ pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
+ // account metadata
+ let account_metas: [AccountMeta; 1] = [AccountMeta::writable(self.native_token.key())];
+
+ let instruction = Instruction {
+ program_id: &crate::ID,
+ accounts: &account_metas,
+ data: &[17],
+ };
+
+ invoke_signed(&instruction, &[self.native_token], signers)
+ }
+}
diff --git a/programs/token-2022/src/instructions/thaw_account.rs b/programs/token-2022/src/instructions/thaw_account.rs
new file mode 100644
index 00000000..1ffacf92
--- /dev/null
+++ b/programs/token-2022/src/instructions/thaw_account.rs
@@ -0,0 +1,49 @@
+use pinocchio::{
+ account_info::AccountInfo,
+ instruction::{AccountMeta, Instruction, Signer},
+ program::invoke_signed,
+ ProgramResult,
+};
+
+/// Thaw a Frozen account using the Mint's freeze_authority
+///
+/// ### Accounts:
+/// 0. `[WRITE]` The account to thaw.
+/// 1. `[]` The token mint.
+/// 2. `[SIGNER]` The mint freeze authority.
+pub struct ThawAccount<'a> {
+ /// Token Account to thaw.
+ pub account: &'a AccountInfo,
+ /// Mint Account.
+ pub mint: &'a AccountInfo,
+ /// Mint Freeze Authority Account
+ pub freeze_authority: &'a AccountInfo,
+}
+
+impl ThawAccount<'_> {
+ #[inline(always)]
+ pub fn invoke(&self) -> ProgramResult {
+ self.invoke_signed(&[])
+ }
+
+ pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
+ // account metadata
+ let account_metas: [AccountMeta; 3] = [
+ AccountMeta::writable(self.account.key()),
+ AccountMeta::readonly(self.mint.key()),
+ AccountMeta::readonly_signer(self.freeze_authority.key()),
+ ];
+
+ let instruction = Instruction {
+ program_id: &crate::ID,
+ accounts: &account_metas,
+ data: &[11],
+ };
+
+ invoke_signed(
+ &instruction,
+ &[self.account, self.mint, self.freeze_authority],
+ signers,
+ )
+ }
+}
diff --git a/programs/token-2022/src/instructions/transfer.rs b/programs/token-2022/src/instructions/transfer.rs
new file mode 100644
index 00000000..389b0c23
--- /dev/null
+++ b/programs/token-2022/src/instructions/transfer.rs
@@ -0,0 +1,61 @@
+use core::slice::from_raw_parts;
+
+use pinocchio::{
+ account_info::AccountInfo,
+ instruction::{AccountMeta, Instruction, Signer},
+ program::invoke_signed,
+ ProgramResult,
+};
+
+use crate::{write_bytes, UNINIT_BYTE};
+
+/// Transfer Tokens from one Token Account to another.
+///
+/// ### Accounts:
+/// 0. `[WRITE]` Sender account
+/// 1. `[WRITE]` Recipient account
+/// 2. `[SIGNER]` Authority account
+pub struct Transfer<'a> {
+ /// Sender account.
+ pub from: &'a AccountInfo,
+ /// Recipient account.
+ pub to: &'a AccountInfo,
+ /// Authority account.
+ pub authority: &'a AccountInfo,
+ /// Amount of microtokens to transfer.
+ pub amount: u64,
+}
+
+impl Transfer<'_> {
+ #[inline(always)]
+ pub fn invoke(&self) -> ProgramResult {
+ self.invoke_signed(&[])
+ }
+
+ pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
+ // account metadata
+ let account_metas: [AccountMeta; 3] = [
+ AccountMeta::writable(self.from.key()),
+ AccountMeta::writable(self.to.key()),
+ AccountMeta::readonly_signer(self.authority.key()),
+ ];
+
+ // Instruction data layout:
+ // - [0]: instruction discriminator (1 byte, u8)
+ // - [1..9]: amount (8 bytes, u64)
+ let mut instruction_data = [UNINIT_BYTE; 9];
+
+ // Set discriminator as u8 at offset [0]
+ write_bytes(&mut instruction_data, &[3]);
+ // Set amount as u64 at offset [1..9]
+ write_bytes(&mut instruction_data[1..9], &self.amount.to_le_bytes());
+
+ let instruction = Instruction {
+ program_id: &crate::ID,
+ accounts: &account_metas,
+ data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 9) },
+ };
+
+ invoke_signed(&instruction, &[self.from, self.to, self.authority], signers)
+ }
+}
diff --git a/programs/token-2022/src/instructions/transfer_checked.rs b/programs/token-2022/src/instructions/transfer_checked.rs
new file mode 100644
index 00000000..f973e95a
--- /dev/null
+++ b/programs/token-2022/src/instructions/transfer_checked.rs
@@ -0,0 +1,70 @@
+use core::slice::from_raw_parts;
+
+use pinocchio::{
+ account_info::AccountInfo,
+ instruction::{AccountMeta, Instruction, Signer},
+ program::invoke_signed,
+ ProgramResult,
+};
+
+use crate::{write_bytes, UNINIT_BYTE};
+
+/// Transfer Tokens from one Token Account to another.
+///
+/// ### Accounts:
+/// 0. `[WRITE]` The source account.
+/// 1. `[]` The token mint.
+/// 2. `[WRITE]` The destination account.
+/// 3. `[SIGNER]` The source account's owner/delegate.
+pub struct TransferChecked<'a> {
+ /// Sender account.
+ pub from: &'a AccountInfo,
+ /// Mint Account
+ pub mint: &'a AccountInfo,
+ /// Recipient account.
+ pub to: &'a AccountInfo,
+ /// Authority account.
+ pub authority: &'a AccountInfo,
+ /// Amount of microtokens to transfer.
+ pub amount: u64,
+ /// Decimal for the Token
+ pub decimals: u8,
+}
+
+impl TransferChecked<'_> {
+ #[inline(always)]
+ pub fn invoke(&self) -> ProgramResult {
+ self.invoke_signed(&[])
+ }
+
+ pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
+ // account metadata
+ let account_metas: [AccountMeta; 4] = [
+ AccountMeta::writable(self.from.key()),
+ AccountMeta::readonly(self.mint.key()),
+ AccountMeta::writable(self.to.key()),
+ AccountMeta::readonly_signer(self.authority.key()),
+ ];
+
+ // Instruction data layout:
+ // - [0]: instruction discriminator (1 byte, u8)
+ // - [1..9]: amount (8 bytes, u64)
+ // - [9]: decimals (1 byte, u8)
+ let mut instruction_data = [UNINIT_BYTE; 10];
+
+ // Set discriminator as u8 at offset [0]
+ write_bytes(&mut instruction_data, &[12]);
+ // Set amount as u64 at offset [1..9]
+ write_bytes(&mut instruction_data[1..9], &self.amount.to_le_bytes());
+ // Set decimals as u8 at offset [9]
+ write_bytes(&mut instruction_data[9..], &[self.decimals]);
+
+ let instruction = Instruction {
+ program_id: &crate::ID,
+ accounts: &account_metas,
+ data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 10) },
+ };
+
+ invoke_signed(&instruction, &[self.from, self.to, self.authority], signers)
+ }
+}
diff --git a/programs/token-2022/src/lib.rs b/programs/token-2022/src/lib.rs
new file mode 100644
index 00000000..62e593b6
--- /dev/null
+++ b/programs/token-2022/src/lib.rs
@@ -0,0 +1,17 @@
+#![no_std]
+
+pub mod instructions;
+pub mod state;
+
+pinocchio_pubkey::declare_id!("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb");
+
+use core::mem::MaybeUninit;
+
+const UNINIT_BYTE: MaybeUninit = MaybeUninit::::uninit();
+
+#[inline(always)]
+fn write_bytes(destination: &mut [MaybeUninit], source: &[u8]) {
+ for (d, s) in destination.iter_mut().zip(source.iter()) {
+ d.write(*s);
+ }
+}
diff --git a/programs/token-2022/src/state/account_state.rs b/programs/token-2022/src/state/account_state.rs
new file mode 100644
index 00000000..5380c96f
--- /dev/null
+++ b/programs/token-2022/src/state/account_state.rs
@@ -0,0 +1,36 @@
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum AccountState {
+ /// Account is not yet initialized
+ Uninitialized,
+
+ /// Account is initialized; the account owner and/or delegate may perform
+ /// permitted operations on this account
+ Initialized,
+
+ /// Account has been frozen by the mint freeze authority. Neither the
+ /// account owner nor the delegate are able to perform operations on
+ /// this account.
+ Frozen,
+}
+
+impl From for AccountState {
+ fn from(value: u8) -> Self {
+ match value {
+ 0 => AccountState::Uninitialized,
+ 1 => AccountState::Initialized,
+ 2 => AccountState::Frozen,
+ _ => panic!("invalid account state value: {value}"),
+ }
+ }
+}
+
+impl From for u8 {
+ fn from(value: AccountState) -> Self {
+ match value {
+ AccountState::Uninitialized => 0,
+ AccountState::Initialized => 1,
+ AccountState::Frozen => 2,
+ }
+ }
+}
diff --git a/programs/token-2022/src/state/mint.rs b/programs/token-2022/src/state/mint.rs
new file mode 100644
index 00000000..25c1e6ee
--- /dev/null
+++ b/programs/token-2022/src/state/mint.rs
@@ -0,0 +1,145 @@
+use pinocchio::{
+ account_info::{AccountInfo, Ref},
+ program_error::ProgramError,
+ pubkey::Pubkey,
+};
+
+use crate::ID;
+
+/// Mint data.
+#[repr(C)]
+pub struct Mint {
+ /// Indicates whether the mint authority is present or not.
+ mint_authority_flag: [u8; 4],
+
+ /// Optional authority used to mint new tokens. The mint authority may only
+ /// be provided during mint creation. If no mint authority is present
+ /// then the mint has a fixed supply and no further tokens may be
+ /// minted.
+ mint_authority: Pubkey,
+
+ /// Total supply of tokens.
+ supply: [u8; 8],
+
+ /// Number of base 10 digits to the right of the decimal place.
+ decimals: u8,
+
+ /// Is `true` if this structure has been initialized.
+ is_initialized: u8,
+
+ /// Indicates whether the freeze authority is present or not.
+ freeze_authority_flag: [u8; 4],
+
+ /// Optional authority to freeze token accounts.
+ freeze_authority: Pubkey,
+}
+
+impl Mint {
+ /// The length of the `Mint` account data.
+ pub const LEN: usize = core::mem::size_of::();
+
+ /// Return a `Mint` from the given account info.
+ ///
+ /// This method performs owner and length validation on `AccountInfo`, safe borrowing
+ /// the account data.
+ #[inline]
+ pub fn from_account_info(account_info: &AccountInfo) -> Result[, ProgramError> {
+ if account_info.data_len() != Self::LEN {
+ return Err(ProgramError::InvalidAccountData);
+ }
+ if account_info.owner() != &ID {
+ return Err(ProgramError::InvalidAccountOwner);
+ }
+ Ok(Ref::map(account_info.try_borrow_data()?, |data| unsafe {
+ Self::from_bytes(data)
+ }))
+ }
+
+ /// Return a `Mint` from the given account info.
+ ///
+ /// This method performs owner and length validation on `AccountInfo`, but does not
+ /// perform the borrow check.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure that it is safe to borrow the account data – e.g., there are
+ /// no mutable borrows of the account data.
+ #[inline]
+ pub unsafe fn from_account_info_unchecked(
+ account_info: &AccountInfo,
+ ) -> Result<&Self, ProgramError> {
+ if account_info.data_len() != Self::LEN {
+ return Err(ProgramError::InvalidAccountData);
+ }
+ if account_info.owner() != &ID {
+ return Err(ProgramError::InvalidAccountOwner);
+ }
+ Ok(Self::from_bytes(account_info.borrow_data_unchecked()))
+ }
+
+ /// Return a `Mint` from the given bytes.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure that `bytes` contains a valid representation of `Mint`.
+ #[inline(always)]
+ pub unsafe fn from_bytes(bytes: &[u8]) -> &Self {
+ &*(bytes.as_ptr() as *const Mint)
+ }
+
+ #[inline(always)]
+ pub fn has_mint_authority(&self) -> bool {
+ self.mint_authority_flag[0] == 1
+ }
+
+ pub fn mint_authority(&self) -> Option<&Pubkey> {
+ if self.has_mint_authority() {
+ Some(self.mint_authority_unchecked())
+ } else {
+ None
+ }
+ }
+
+ /// Return the mint authority.
+ ///
+ /// This method should be used when the caller knows that the mint will have a mint
+ /// authority set since it skips the `Option` check.
+ #[inline(always)]
+ pub fn mint_authority_unchecked(&self) -> &Pubkey {
+ &self.mint_authority
+ }
+
+ pub fn supply(&self) -> u64 {
+ unsafe { core::ptr::read_unaligned(self.supply.as_ptr() as *const u64) }
+ }
+
+ pub fn decimals(&self) -> u8 {
+ self.decimals
+ }
+
+ pub fn is_initialized(&self) -> bool {
+ self.is_initialized == 1
+ }
+
+ #[inline(always)]
+ pub fn has_freeze_authority(&self) -> bool {
+ self.freeze_authority_flag[0] == 1
+ }
+
+ pub fn freeze_authority(&self) -> Option<&Pubkey> {
+ if self.has_freeze_authority() {
+ Some(self.freeze_authority_unchecked())
+ } else {
+ None
+ }
+ }
+
+ /// Return the freeze authority.
+ ///
+ /// This method should be used when the caller knows that the mint will have a freeze
+ /// authority set since it skips the `Option` check.
+ #[inline(always)]
+ pub fn freeze_authority_unchecked(&self) -> &Pubkey {
+ &self.freeze_authority
+ }
+}
diff --git a/programs/token-2022/src/state/mod.rs b/programs/token-2022/src/state/mod.rs
new file mode 100644
index 00000000..f73c8320
--- /dev/null
+++ b/programs/token-2022/src/state/mod.rs
@@ -0,0 +1,7 @@
+mod account_state;
+mod mint;
+mod token;
+
+pub use account_state::*;
+pub use mint::*;
+pub use token::*;
diff --git a/programs/token-2022/src/state/token.rs b/programs/token-2022/src/state/token.rs
new file mode 100644
index 00000000..2250a217
--- /dev/null
+++ b/programs/token-2022/src/state/token.rs
@@ -0,0 +1,198 @@
+use super::AccountState;
+use pinocchio::{
+ account_info::{AccountInfo, Ref},
+ program_error::ProgramError,
+ pubkey::Pubkey,
+};
+
+use crate::ID;
+
+/// Token account data.
+#[repr(C)]
+pub struct TokenAccount {
+ /// The mint associated with this account
+ mint: Pubkey,
+
+ /// The owner of this account.
+ owner: Pubkey,
+
+ /// The amount of tokens this account holds.
+ amount: [u8; 8],
+
+ /// Indicates whether the delegate is present or not.
+ delegate_flag: [u8; 4],
+
+ /// If `delegate` is `Some` then `delegated_amount` represents
+ /// the amount authorized by the delegate.
+ delegate: Pubkey,
+
+ /// The account's state.
+ state: u8,
+
+ /// Indicates whether this account represents a native token or not.
+ is_native: [u8; 4],
+
+ /// If is_native.is_some, this is a native token, and the value logs the
+ /// rent-exempt reserve. An Account is required to be rent-exempt, so
+ /// the value is used by the Processor to ensure that wrapped SOL
+ /// accounts do not drop below this threshold.
+ native_amount: [u8; 8],
+
+ /// The amount delegated.
+ delegated_amount: [u8; 8],
+
+ /// Indicates whether the close authority is present or not.
+ close_authority_flag: [u8; 4],
+
+ /// Optional authority to close the account.
+ close_authority: Pubkey,
+}
+
+impl TokenAccount {
+ pub const LEN: usize = core::mem::size_of::();
+
+ /// Return a `TokenAccount` from the given account info.
+ ///
+ /// This method performs owner and length validation on `AccountInfo`, safe borrowing
+ /// the account data.
+ #[inline]
+ pub fn from_account_info(
+ account_info: &AccountInfo,
+ ) -> Result][, ProgramError> {
+ if account_info.data_len() != Self::LEN {
+ return Err(ProgramError::InvalidAccountData);
+ }
+ if account_info.owner() != &ID {
+ return Err(ProgramError::InvalidAccountData);
+ }
+ Ok(Ref::map(account_info.try_borrow_data()?, |data| unsafe {
+ Self::from_bytes(data)
+ }))
+ }
+
+ /// Return a `TokenAccount` from the given account info.
+ ///
+ /// This method performs owner and length validation on `AccountInfo`, but does not
+ /// perform the borrow check.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure that it is safe to borrow the account data – e.g., there are
+ /// no mutable borrows of the account data.
+ #[inline]
+ pub unsafe fn from_account_info_unchecked(
+ account_info: &AccountInfo,
+ ) -> Result<&TokenAccount, ProgramError> {
+ if account_info.data_len() != Self::LEN {
+ return Err(ProgramError::InvalidAccountData);
+ }
+ if account_info.owner() != &ID {
+ return Err(ProgramError::InvalidAccountData);
+ }
+ Ok(Self::from_bytes(account_info.borrow_data_unchecked()))
+ }
+
+ /// Return a `TokenAccount` from the given bytes.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure that `bytes` contains a valid representation of `TokenAccount`.
+ #[inline(always)]
+ pub unsafe fn from_bytes(bytes: &[u8]) -> &Self {
+ &*(bytes.as_ptr() as *const TokenAccount)
+ }
+
+ pub fn mint(&self) -> &Pubkey {
+ &self.mint
+ }
+
+ pub fn owner(&self) -> &Pubkey {
+ &self.owner
+ }
+
+ pub fn amount(&self) -> u64 {
+ unsafe { core::ptr::read_unaligned(self.amount.as_ptr() as *const u64) }
+ }
+
+ #[inline(always)]
+ pub fn has_delegate(&self) -> bool {
+ self.delegate_flag[0] == 1
+ }
+
+ pub fn delegate(&self) -> Option<&Pubkey> {
+ if self.has_delegate() {
+ Some(self.delegate_unchecked())
+ } else {
+ None
+ }
+ }
+
+ /// Use this when you know the account will have a delegate and want to skip the `Option` check.
+ #[inline(always)]
+ pub fn delegate_unchecked(&self) -> &Pubkey {
+ &self.delegate
+ }
+
+ #[inline(always)]
+ pub fn state(&self) -> AccountState {
+ self.state.into()
+ }
+
+ #[inline(always)]
+ pub fn is_native(&self) -> bool {
+ self.is_native[0] == 1
+ }
+
+ pub fn native_amount(&self) -> Option {
+ if self.is_native() {
+ Some(self.native_amount_unchecked())
+ } else {
+ None
+ }
+ }
+
+ /// Return the native amount.
+ ///
+ /// This method should be used when the caller knows that the token is native since it
+ /// skips the `Option` check.
+ #[inline(always)]
+ pub fn native_amount_unchecked(&self) -> u64 {
+ unsafe { core::ptr::read_unaligned(self.native_amount.as_ptr() as *const u64) }
+ }
+
+ pub fn delegated_amount(&self) -> u64 {
+ unsafe { core::ptr::read_unaligned(self.delegated_amount.as_ptr() as *const u64) }
+ }
+
+ #[inline(always)]
+ pub fn has_close_authority(&self) -> bool {
+ self.close_authority_flag[0] == 1
+ }
+
+ pub fn close_authority(&self) -> Option<&Pubkey> {
+ if self.has_close_authority() {
+ Some(self.close_authority_unchecked())
+ } else {
+ None
+ }
+ }
+
+ /// Return the close authority.
+ ///
+ /// This method should be used when the caller knows that the token will have a close
+ /// authority set since it skips the `Option` check.
+ #[inline(always)]
+ pub fn close_authority_unchecked(&self) -> &Pubkey {
+ &self.close_authority
+ }
+
+ #[inline(always)]
+ pub fn is_initialized(&self) -> bool {
+ self.state != AccountState::Uninitialized as u8
+ }
+
+ #[inline(always)]
+ pub fn is_frozen(&self) -> bool {
+ self.state == AccountState::Frozen as u8
+ }
+}
]