Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions programs/token-2022/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 }
40 changes: 40 additions & 0 deletions programs/token-2022/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# <img width="229" alt="pinocchio-token" src="https://github.com/user-attachments/assets/12b0dc2a-94fb-4866-8e6a-60ac74e13b4f"/>

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)
65 changes: 65 additions & 0 deletions programs/token-2022/src/instructions/approve.rs
Original file line number Diff line number Diff line change
@@ -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,
)
}
}
74 changes: 74 additions & 0 deletions programs/token-2022/src/instructions/approve_checked.rs
Original file line number Diff line number Diff line change
@@ -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,
)
}
}
65 changes: 65 additions & 0 deletions programs/token-2022/src/instructions/burn.rs
Original file line number Diff line number Diff line change
@@ -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,
)
}
}
69 changes: 69 additions & 0 deletions programs/token-2022/src/instructions/burn_checked.rs
Original file line number Diff line number Diff line change
@@ -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,
)
}
}
49 changes: 49 additions & 0 deletions programs/token-2022/src/instructions/close_account.rs
Original file line number Diff line number Diff line change
@@ -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,
)
}
}
Loading
Loading