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
13 changes: 13 additions & 0 deletions tokens/transfer-tokens/steel/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[workspace]
members = ["api","program"]
resolver = "2"

[workspace.dependencies]
solana-program = "1.18.17"
steel = {version = "2.1", features = ["spl"]}
bytemuck = "1.4"
num_enum = "0.7"
spl-token = { version = "4.0.0", features = [ "no-entrypoint" ] }
spl-associated-token-account = { version = "3.0.4", features = [ "no-entrypoint" ] }
mpl-token-metadata = { version = "4.1.2" }
thiserror = "2.0.3"
17 changes: 17 additions & 0 deletions tokens/transfer-tokens/steel/api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "transfer-tokens-steel-api"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "lib"]

[dependencies]
solana-program.workspace = true
steel.workspace = true
bytemuck.workspace = true
num_enum.workspace = true
spl-token.workspace = true
spl-associated-token-account.workspace = true
mpl-token-metadata.workspace = true
thiserror.workspace = true
10 changes: 10 additions & 0 deletions tokens/transfer-tokens/steel/api/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use steel::*;

#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, IntoPrimitive)]
#[repr(u32)]
pub enum SteelError {
#[error("Failed to parse string from bytes")]
ParseError = 0,
}

error!(SteelError);
114 changes: 114 additions & 0 deletions tokens/transfer-tokens/steel/api/src/instruction/create.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use crate::{error::*, SteelInstruction};
use mpl_token_metadata::{instructions as mpl_instruction, types::DataV2};
use solana_program::{msg, program::invoke, program_pack::Pack, rent::Rent, system_instruction};
use spl_token::state::Mint;
use std::ffi::CStr;
use steel::*;

instruction!(SteelInstruction, Create);
// CreateToken instruction
#[repr(C)]
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
pub struct Create {
pub token_name: [u8; 32], // Metaplex metadata name: 32 bytes max
pub token_symbol: [u8; 10], // Metaplex metadata symbol: 10 bytes max
pub token_uri: [u8; 256], // Metaplex metadata uri: 200 bytes max
pub decimals: u8,
}

impl Create {
pub fn process(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult {
let args = Self::try_from_bytes(data)?;

let [mint_account, mint_authority, metadata_account, payer, rent, system_program, token_program, token_metadata_program] =
accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
};

// First create the account for the Mint
//
msg!("Creating mint account...");
msg!("Mint: {}", mint_account.key);
invoke(
&system_instruction::create_account(
payer.key,
mint_account.key,
(Rent::get()?).minimum_balance(Mint::LEN),
Mint::LEN as u64,
token_program.key,
),
&[
mint_account.clone(),
payer.clone(),
system_program.clone(),
token_program.clone(),
],
)?;

// Now initialize that account as a Mint (standard Mint)
//
msg!("Initializing mint account...");
msg!("Mint: {}", mint_account.key);

initialize_mint(
mint_account,
mint_authority,
Some(mint_authority),
token_program,
rent,
args.decimals,
)?;

// Now create the account for that Mint's metadata
//
msg!("Creating metadata account...");
msg!("Metadata account address: {}", metadata_account.key);

let name = Self::str_from_bytes(&mut args.token_name.to_vec())?.to_string();
let symbol = Self::str_from_bytes(&mut args.token_symbol.to_vec())?.to_string();
let uri = Self::str_from_bytes(&mut args.token_uri.to_vec())?.to_string();

mpl_instruction::CreateMetadataAccountV3Cpi {
__program: token_metadata_program,
metadata: metadata_account,
mint: mint_account,
mint_authority,
payer,
update_authority: (mint_authority, true),
system_program,
rent: Some(rent),
__args: mpl_instruction::CreateMetadataAccountV3InstructionArgs {
data: DataV2 {
name,
symbol,
uri,
seller_fee_basis_points: 0,
creators: None,
collection: None,
uses: None,
},
is_mutable: true,
collection_details: None,
},
}
.invoke()?;

msg!("Token mint created successfully.");

Ok(())
}

fn str_from_bytes(bytes: &mut Vec<u8>) -> Result<&str, ProgramError> {
// add an extra null byte, in the case every position is occupied with a non-null byte
bytes.push(0);

// remove excess null bytes
if let Ok(cstr) = CStr::from_bytes_until_nul(bytes) {
if let Ok(str) = cstr.to_str() {
return Ok(str);
}
}
Err(SteelError::ParseError.into())
}
}
82 changes: 82 additions & 0 deletions tokens/transfer-tokens/steel/api/src/instruction/mint_nft.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use crate::SteelInstruction;
use mpl_token_metadata::instructions as mpl_instruction;
use solana_program::{msg, program::invoke};
use spl_token::instruction::{self as token_instruction};
use steel::*;

instruction!(SteelInstruction, MintNft);

#[repr(C, packed)]
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
pub struct MintNft {}

impl MintNft {
pub fn process(accounts: &[AccountInfo<'_>]) -> ProgramResult {
let [mint_account, metadata_account, edition_account, mint_authority, associated_token_account, payer, _rent, system_program, token_program, associated_token_program, token_metadata_program] =
accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
};

// First create the token account for the user
//
if associated_token_account.lamports() == 0 {
msg!("Creating associated token account...");
create_associated_token_account(
payer,
payer,
associated_token_account,
mint_account,
system_program,
token_program,
associated_token_program,
)?;
} else {
msg!("Associated token account exists.");
}
msg!("Associated Token Address: {}", associated_token_account.key);

// Mint the NFT to the user's wallet
//
msg!("Minting NFT to associated token account...");
invoke(
&token_instruction::mint_to(
token_program.key,
mint_account.key,
associated_token_account.key,
mint_authority.key,
&[mint_authority.key],
1,
)?,
&[
mint_account.clone(),
mint_authority.clone(),
associated_token_account.clone(),
token_program.clone(),
],
)?;

// We can make this a Limited Edition NFT through Metaplex,
// which will disable minting by setting the Mint & Freeze Authorities to the
// Edition Account.
//
mpl_instruction::CreateMasterEditionV3Cpi {
__program: token_metadata_program,
__args: mpl_instruction::CreateMasterEditionV3InstructionArgs { max_supply: None },
edition: edition_account,
metadata: metadata_account,
mint: mint_account,
mint_authority,
payer,
rent: None,
system_program,
token_program,
update_authority: mint_authority,
}
.invoke()?;

msg!("NFT minted successfully.");

Ok(())
}
}
65 changes: 65 additions & 0 deletions tokens/transfer-tokens/steel/api/src/instruction/mint_spl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use crate::SteelInstruction;
use solana_program::{msg, program::invoke};
use spl_token::instruction::{self as token_instruction};
use steel::*;

instruction!(SteelInstruction, MintSpl);

#[repr(C, packed)]
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
pub struct MintSpl {
quantity: u64,
}

impl MintSpl {
pub fn process(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult {
let args = MintSpl::try_from_bytes(data)?;

let [mint_account, mint_authority, associated_token_account, payer, system_program, token_program, associated_token_program] =
accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
};

// First create the token account for the user
//
if associated_token_account.lamports() == 0 {
msg!("Creating associated token account...");
create_associated_token_account(
payer,
payer,
associated_token_account,
mint_account,
system_program,
token_program,
associated_token_program,
)?;
} else {
msg!("Associated token account exists.");
}
msg!("Associated Token Address: {}", associated_token_account.key);

let quantity = args.quantity;
msg!("Minting {} tokens to associated token account...", quantity);
invoke(
&token_instruction::mint_to(
token_program.key,
mint_account.key,
associated_token_account.key,
mint_authority.key,
&[mint_authority.key],
args.quantity,
)?,
&[
mint_account.clone(),
mint_authority.clone(),
associated_token_account.clone(),
token_program.clone(),
],
)?;

msg!("Tokens minted to wallet successfully.");

Ok(())
}
}
20 changes: 20 additions & 0 deletions tokens/transfer-tokens/steel/api/src/instruction/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
pub mod create;
pub mod mint_nft;
pub mod mint_spl;
pub mod transfer;

pub use create::*;
pub use mint_nft::*;
pub use mint_spl::*;
pub use transfer::*;

use steel::*;

#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)]
pub enum SteelInstruction {
Create = 0,
MintNft = 1,
MintSpl = 2,
TransferTokens = 3,
}
69 changes: 69 additions & 0 deletions tokens/transfer-tokens/steel/api/src/instruction/transfer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use crate::SteelInstruction;
use solana_program::msg;
use steel::*;

instruction!(SteelInstruction, TransferTokens);

#[repr(C, packed)]
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
pub struct TransferTokens {
quantity: u64,
}

impl TransferTokens {
pub fn process(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult {
let args = TransferTokens::try_from_bytes(data)?;

let [mint_account, from_associated_token_account, to_associated_token_account, owner, recipient, payer, system_program, token_program, associated_token_program] =
accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
};

// First create the token account for the user
//
if to_associated_token_account.lamports() == 0 {
msg!("Creating associated token account...");
create_associated_token_account(
payer,
recipient,
to_associated_token_account,
mint_account,
system_program,
token_program,
associated_token_program,
)?;
} else {
msg!("Associated token account exists.");
}
msg!(
"Associated Token Address: {}",
to_associated_token_account.key
);

msg!(
"Recipient Associated Token Address: {}",
to_associated_token_account.key
);

let quantity = args.quantity;
msg!("Transferring {} tokens...", quantity);
msg!("Mint: {}", mint_account.key);
msg!("Owner Token Address: {}", from_associated_token_account.key);
msg!(
"Recipient Token Address: {}",
to_associated_token_account.key
);
transfer(
owner,
from_associated_token_account,
to_associated_token_account,
token_program,
args.quantity,
)?;

msg!("Token mint created successfully.");

Ok(())
}
}
Loading