DEV Community

Ycrypto-3
Ycrypto-3

Posted on

Mint APL Token guide

🛠️ Local Validator Setup

Set up a complete local development environment with Bitcoin Core, Titan indexer, and Arch validator running locally.

🎯 What This Setup Provides

  • Bitcoin Core in regtest mode (local Bitcoin network)
  • Titan Indexer for Bitcoin data indexing
  • Arch Validator connected to your local infrastructure
  • Complete isolation from external networks
  • Fast block times and instant transactions

📋 Prerequisites

Before starting, ensure you have:


🚀 Step-by-Step Setup

Step 1: Start Bitcoin Core in Regtest Mode

Start your local Bitcoin network:

# Start Bitcoin Core in regtest mode
bitcoind -regtest -port=18444 -rpcport=18443 \
    -rpcuser=bitcoin -rpcpassword=bitcoinpass \
    -fallbackfee=0.001
Enter fullscreen mode Exit fullscreen mode

What this does:

  • Creates a local Bitcoin network (regtest)
  • Sets up RPC on port 18443
  • Configures basic authentication
  • Sets a fallback fee for transactions

💡 Note: Keep this terminal open - Bitcoin Core needs to keep running

Step 2: First-Time Wallet Setup

In a new terminal, set up your Bitcoin wallet:

# Create a wallet called "testwallet"
bitcoin-cli -regtest -rpcuser=bitcoin -rpcpassword=bitcoinpass createwallet testwallet

# Generate an address and mine the first 100 blocks to it
ADDRESS=$(bitcoin-cli -regtest -rpcuser=bitcoin -rpcpassword=bitcoinpass getnewaddress)
bitcoin-cli -regtest -rpcuser=bitcoin -rpcpassword=bitcoinpass generatetoaddress 100 $ADDRESS
Enter fullscreen mode Exit fullscreen mode

What this does:

  • Creates a wallet for managing Bitcoin
  • Generates the first 100 blocks (needed for Bitcoin to be spendable)
  • Funds the wallet with initial Bitcoin

⚠️ Important: This step is only needed the first time you set up Bitcoin Core

Step 3: Clone and Configure Titan Indexer

Set up the Titan indexer (Bitcoin data processor):

# Clone Titan indexer (if not already done)
git clone https://github.com/saturnbtc/Titan.git
cd Titan
Enter fullscreen mode Exit fullscreen mode

Step 4: Configure Titan for Regtest

Important: Configure Titan for proper regtest operation:

# Navigate to the cache configuration file
cd Titan/indexer/src/index/updater/

# Open cache.rs in your editor
# Find lines around 76-80 and ensure this code is commented out:
Enter fullscreen mode Exit fullscreen mode

Open Titan/indexer/src/index/updater/cache.rs and ensure these lines are commented out:

// In regtest, the first block is not accessible via RPC and for testing purposes we
// don't need to start at block 0.
// if block_count == 0 && settings.chain == Chain::Regtest {
//     block_count = 1;
// }
Enter fullscreen mode Exit fullscreen mode

Why this is needed:

  • This prevents Titan from skipping block 0 in regtest mode
  • Ensures proper synchronization with your local Bitcoin node
  • Avoids indexing issues in local development

Step 5: Build Titan Indexer

Build the Titan project:

# Return to Titan root directory
cd Titan

# Build the project (this may take a few minutes)
cargo build --release
Enter fullscreen mode Exit fullscreen mode

Step 6: Start Titan Indexer

In a new terminal, start the Titan indexer:

# Navigate to Titan directory
cd Titan

# Start Titan indexer pointing to your Bitcoin node
cargo run --bin titan -- \
    --bitcoin-rpc-url http://127.0.0.1:18443 \
    --bitcoin-rpc-username bitcoin \
    --bitcoin-rpc-password bitcoinpass \
    --chain regtest \
    --index-addresses \
    --index-bitcoin-transactions \
    --enable-tcp-subscriptions \
    --main-loop-interval 0 \
    --http-listen 127.0.0.1:3030
Enter fullscreen mode Exit fullscreen mode

What this does:

  • Connects to your local Bitcoin node
  • Indexes Bitcoin addresses and transactions
  • Provides HTTP API on port 3030
  • Enables real-time subscriptions

💡 Tip: You should see logs showing Titan connecting to Bitcoin and processing blocks

Step 7: Start Arch Validator

In a new terminal, start the Arch validator:

# Start validator pointing to your local Titan
cli validator-start \
    --network-mode devnet \
    --data-dir ./.arch_data \
    --rpc-bind-ip 127.0.0.1 \
    --rpc-bind-port 9002 \
    --titan-endpoint http://127.0.0.1:3030 \
    --titan-socket-endpoint 127.0.0.1:3030
Enter fullscreen mode Exit fullscreen mode

What this does:

  • Starts Arch validator in devnet mode
  • Connects to your local Titan indexer
  • Provides RPC API on port 9002
  • Stores data in ./.arch_data directory

✅ Verify Your Setup

Check Bitcoin Core

# Check Bitcoin Core is running
bitcoin-cli -regtest -rpcuser=bitcoin -rpcpassword=bitcoinpass getblockchaininfo
Enter fullscreen mode Exit fullscreen mode

Check Titan Indexer

# Check Titan is running
curl http://127.0.0.1:3030/health
Enter fullscreen mode Exit fullscreen mode

Check Arch Validator

# Check validator is running
cli get-block-height
Enter fullscreen mode Exit fullscreen mode

🎮 Using Your Local Environment

Create and Fund Accounts

# Create a new account
cli account create --keypair-path ./dev-wallet.json --airdrop 1000000000

# Check account balance
cli show <ACCOUNT_ADDRESS>
Enter fullscreen mode Exit fullscreen mode

Mine New Bitcoin Blocks (When Needed)

# Generate 10 new blocks
bitcoin-cli -regtest -rpcuser=bitcoin -rpcpassword=bitcoinpass -generate 10
Enter fullscreen mode Exit fullscreen mode

🔧 Configuration Reference

Port Usage

Service Port Purpose
Bitcoin Core 18443 RPC API
Bitcoin Core 18444 P2P networking
Titan Indexer 3030 HTTP API
Arch Validator 9002 RPC API

🛑 Stopping the Environment

To cleanly shut down your local environment:

  1. Stop Arch Validator: Ctrl+C in validator terminal
  2. Stop Titan Indexer: Ctrl+C in Titan terminal
  3. Stop Bitcoin Core: Ctrl+C in Bitcoin terminal

Clean Reset (If Needed)

# Remove all data and start fresh
rm -rf ./.arch_data
rm -rf ~/.bitcoin/regtest

# Then repeat the setup process
Enter fullscreen mode Exit fullscreen mode

🔍 Troubleshooting

Bitcoin Core Won't Start

  • Check if port 18443 is already in use: lsof -i :18443
  • Try different ports or kill existing processes

Titan Can't Connect to Bitcoin

  • Ensure Bitcoin Core is running first
  • Check the RPC credentials match
  • Verify Bitcoin Core is accepting connections
  • Make sure you commented out the regtest block skip code in cache.rs

Validator Won't Start

  • Ensure Titan is running and responding on port 3030
  • Check that no other validator is using port 9002
  • Verify the data directory permissions

Titan Indexing Issues

  • Verify the regtest configuration in cache.rs is properly commented out
  • Check Bitcoin Core has generated at least 100 blocks
  • Restart Titan if you made configuration changes

Performance Issues

  • Allocate more CPU/RAM if running in a VM
  • Use SSD storage for better performance
  • Close unnecessary applications

🎯 Next Steps

Once your local environment is running:

  1. Deploy the test token program: Follow the Token Guide
  2. Try the examples: Explore the arch-examples repository
  3. Build your own program: Start with the Program Development Guide

💡 Pro Tips

  • Keep terminals organized: Use terminal tabs or tmux for easier management
  • Monitor logs: Watch for errors in all three services
  • Backup important data: Save keypairs and important addresses
  • Use scripts: Create shell scripts to automate the startup process
  • Double-check Titan config: Always verify the regtest configuration is properly set

Your local Arch Network development environment is now ready! 🚀

🪙 Creating Fungible Tokens on Arch Network

Build Arch Network tokens using the powerful APL Token Program - your gateway to tokenization on Arch Network!

🎯 What You'll Build

By following this guide, you'll create a complete fungible token system with:

  • Custom token mint with your own supply and decimals
  • Token accounts for holding and managing tokens
  • Minting capabilities to create new tokens
  • Transfer system between accounts
  • Advanced features like delegation, burning, and freezing

🌟 Why APL Tokens?

The Arch Program Library (APL) Token Program brings proven token standards to Bitcoin:

Feature Benefit
SPL Compatible Based on Solana's battle-tested token standard
Bitcoin Security All operations recorded on Bitcoin blockchain
Full-Featured Minting, transfers, burning, delegation, multisig
Developer Friendly Familiar APIs and comprehensive tooling

📋 Prerequisites

Ensure you have these tools installed:

APL Token Program ID

apl-token00000000000000000000000
Enter fullscreen mode Exit fullscreen mode

This is the fixed program ID for all APL token operations.


🚀 Project Setup

Step 1: Create Your Project

# Create project directory
mkdir my-arch-token
cd my-arch-token

# Initialize Rust project
cargo init --bin
Enter fullscreen mode Exit fullscreen mode

Step 2: Configure Dependencies

Update your Cargo.toml:

[package]
name = "my_arch_token"
version = "0.1.0"
edition = "2021"

[dependencies]
# Core Arch Network dependencies
arch_sdk = "0.5.2"
arch_program = "0.5.2"
arch_test_sdk = "0.5.2"

# APL Token Programs
apl-token = { version = "0.5.2", features = ["no-entrypoint"] }


# Serialization and utilities
borsh = { version = "1.5.1", features = ["derive"] }
bitcoincore-rpc = "0.18.0"
bitcoin = { version = "0.32.3", features = ["serde", "rand"] }
hex = "0.4.3"
log = "0.4"
env_logger = "0.10"

[dev-dependencies]
serial_test = "3.1.1"
Enter fullscreen mode Exit fullscreen mode

🏗️ Core Token Operations

🎯 Step 1: Create Your Token Mint

The mint is the master account that defines your token's properties:

use apl_token::state::{Mint, Account};
use arch_program::{program_pack::Pack, sanitized::ArchMessage};
use arch_sdk::{build_and_sign_transaction, generate_new_keypair, ArchRpcClient, Status};
use arch_test_sdk::{
    constants::{ BITCOIN_NETWORK,NODE1_ADDRESS},
    helper::{create_and_fund_account_with_faucet, read_account_info, send_transactions_and_wait},
};
use log::info;


fn create_token_mint(client: &ArchRpcClient) -> Result<(bitcoin::key::Keypair, arch_program::pubkey::Pubkey), Box<dyn std::error::Error>> {
    // 1. Create mint authority (you control the token supply)
    let (authority_keypair, authority_pubkey, _) = generate_new_keypair(BITCOIN_NETWORK);
    create_and_fund_account_with_faucet(&authority_keypair, BITCOIN_NETWORK);

    // 2. Create mint account
    let (token_mint_keypair, token_mint_pubkey, _) = generate_new_keypair(BITCOIN_NETWORK);

    // 3. Create the mint account on-chain
    let create_account_ix = arch_program::system_instruction::create_account(
        &authority_pubkey,       // Payer
        &token_mint_pubkey,      // New account
        arch_program::account::MIN_ACCOUNT_LAMPORTS, // Rent
        Mint::LEN as u64,        // Space needed
        &apl_token::id(),        // Owner program
    );

    // 4. Initialize the mint with your token parameters
    let initialize_mint_ix = apl_token::instruction::initialize_mint(
        &apl_token::id(),
        &token_mint_pubkey,
        &authority_pubkey,       // Mint authority (can create tokens)
        None,                   // No freeze authority (optional)
        9,                      // Decimals (9 = like USDC, 0 = whole numbers only)
    )?;

    // 5. Send transaction
    let transaction = build_and_sign_transaction(
        ArchMessage::new(
            &[create_account_ix, initialize_mint_ix],
            Some(authority_pubkey),
            client.get_best_block_hash()?,
        ),
        vec![authority_keypair, token_mint_keypair],
        BITCOIN_NETWORK,
    );

    let processed_txs = send_transactions_and_wait(vec![transaction]);
    if processed_txs[0].status != Status::Processed {
        return Err("Failed to create token mint".into());
    }

    println!("🎉 Token mint created: {}", token_mint_pubkey);

    Ok((authority_keypair, token_mint_pubkey))
}

Enter fullscreen mode Exit fullscreen mode

🏦 Step 2: Create Token Accounts

Token accounts hold the actual token balances for users:

fn mint_tokens(
    client: &ArchRpcClient,
    mint_pubkey: &arch_program::pubkey::Pubkey,
    account_pubkey: &arch_program::pubkey::Pubkey,
    authority_keypair: bitcoin::key::Keypair,
    amount: u64,
) {
    let authority_pubkey = arch_program::pubkey::Pubkey::from_slice(
        &authority_keypair.x_only_public_key().0.serialize()
    );

    // Create mint instruction
    let mint_ix = apl_token::instruction::mint_to(
        &apl_token::id(),
        mint_pubkey,
        account_pubkey,
        &authority_pubkey,
        &[],                    // No additional signers for single authority
        amount,                 // Amount to mint (in smallest units)
    ).unwrap();

    // Send transaction
    let transaction = build_and_sign_transaction(
        ArchMessage::new(
            &[mint_ix],
            Some(authority_pubkey),
            client.get_best_block_hash().unwrap(),
        ),
        vec![authority_keypair],
        BITCOIN_NETWORK,
    );

    let processed_txs = send_transactions_and_wait(vec![transaction]);
    assert_eq!(processed_txs[0].status, Status::Processed);

    println!("🪙 Minted {} tokens", amount);
}
Enter fullscreen mode Exit fullscreen mode

💰 Step 3: Mint Tokens

Create new tokens and add them to an account:

fn mint_tokens(
    client: &ArchRpcClient,
    mint_pubkey: &arch_program::pubkey::Pubkey,
    account_pubkey: &arch_program::pubkey::Pubkey,
    authority_pubkey: &arch_program::pubkey::Pubkey,
    authority_keypair: bitcoin::key::Keypair,
    amount: u64,
) -> Result<(), Box<dyn std::error::Error>> {

    // Create mint instruction
    let mint_ix = apl_token::instruction::mint_to(
        &apl_token::id(),
        mint_pubkey,
        account_pubkey,
        authority_pubkey,
        &[],                    // No additional signers for single authority
        amount,                 // Amount to mint (in smallest units)
    )?;

    // Send transaction
    let transaction = build_and_sign_transaction(
        ArchMessage::new(
            &[mint_ix],
            Some(*authority_pubkey),
            client.get_best_block_hash()?,
        ),
        vec![authority_keypair],
        BITCOIN_NETWORK,
    );

    let processed_txs = send_transactions_and_wait(vec![transaction]);
    if processed_txs[0].status != Status::Processed {
        return Err("Failed to mint tokens".into());
    }

    println!("🪙 Minted {} tokens", amount);
    Ok(())
}
Enter fullscreen mode Exit fullscreen mode

💸 Step 4: Transfer Tokens

Send tokens between accounts:

fn transfer_tokens(
    client: &ArchRpcClient,
    from_account: &arch_program::pubkey::Pubkey,
    to_account: &arch_program::pubkey::Pubkey,
    owner_pubkey: &arch_program::pubkey::Pubkey,
    owner_keypair: bitcoin::key::Keypair,
    amount: u64,
) -> Result<(), Box<dyn std::error::Error>> {

    // Create transfer instruction
    let transfer_ix = apl_token::instruction::transfer(
        &apl_token::id(),
        from_account,
        to_account,
        owner_pubkey,
        &[],                    // No additional signers
        amount,
    )?;

    // Send transaction
    let transaction = build_and_sign_transaction(
        ArchMessage::new(
            &[transfer_ix],
            Some(*owner_pubkey),
            client.get_best_block_hash()?,
        ),
        vec![owner_keypair],
        BITCOIN_NETWORK,
    );

    let processed_txs = send_transactions_and_wait(vec![transaction]);
    if processed_txs[0].status != Status::Processed {
        return Err("Failed to transfer tokens".into());
    }

    println!("📤 Transferred {} tokens", amount);
    Ok(())
}

Enter fullscreen mode Exit fullscreen mode

🔥 Advanced Token Features

🤝 Delegate Token Spending

Allow another account to spend tokens on your behalf:

fn approve_delegate(
    client: &ArchRpcClient,
    token_account: &arch_program::pubkey::Pubkey,
    delegate_pubkey: &arch_program::pubkey::Pubkey,
    owner_keypair: bitcoin::key::Keypair,
    amount: u64,
) {
    let owner_pubkey = arch_program::pubkey::Pubkey::from_slice(
        &owner_keypair.x_only_public_key().0.serialize()
    );

    let approve_ix = apl_token::instruction::approve(
        &apl_token::id(),
        token_account,
        delegate_pubkey,
        &owner_pubkey,
        &[&owner_pubkey],
        amount,
    ).unwrap();

    let transaction = build_and_sign_transaction(
        ArchMessage::new(
            &[approve_ix],
            Some(owner_pubkey),
            client.get_best_block_hash().unwrap(),
        ),
        vec![owner_keypair],
        BITCOIN_NETWORK,
    );

    let processed_txs = send_transactions_and_wait(vec![transaction]);
    assert_eq!(processed_txs[0].status, Status::Processed);

    println!("✅ Approved {} tokens for delegation", amount);
}
Enter fullscreen mode Exit fullscreen mode

🔥 Burn Tokens

Permanently remove tokens from circulation:

fn burn_tokens(
    client: &ArchRpcClient,
    token_account: &arch_program::pubkey::Pubkey,
    mint_pubkey: &arch_program::pubkey::Pubkey,
    owner_pubkey: &arch_program::pubkey::Pubkey,
    owner_keypair: bitcoin::key::Keypair,
    amount: u64,
) -> Result<(), Box<dyn std::error::Error>> {

    let burn_ix = apl_token::instruction::burn(
        &apl_token::id(),
        token_account,
        mint_pubkey,
        owner_pubkey,
        &[],
        amount,
    )?;

    let transaction = build_and_sign_transaction(
        ArchMessage::new(
            &[burn_ix],
            Some(*owner_pubkey),
            client.get_best_block_hash()?,
        ),
        vec![owner_keypair],
        BITCOIN_NETWORK,
    );

    let processed_txs = send_transactions_and_wait(vec![transaction]);
    if processed_txs[0].status != Status::Processed {
        return Err("Failed to burn tokens".into());
    }

    println!("🔥 Burned {} tokens", amount);
    Ok(())
}

Enter fullscreen mode Exit fullscreen mode

🎯 Complete Example

Here's a full implementation showing the complete token lifecycle:

💻 Click to view complete example

use apl_token::state::{Mint, Account};
use arch_program::{program_pack::Pack, sanitized::ArchMessage};
use arch_sdk::{build_and_sign_transaction, generate_new_keypair, ArchRpcClient, Status};
use arch_test_sdk::{
    constants::{ BITCOIN_NETWORK,NODE1_ADDRESS},
    helper::{create_and_fund_account_with_faucet, read_account_info, send_transactions_and_wait},
};
use log::info;
// const BITCOIN_NETWORK: Network = Network::Testnet;

fn main() -&gt; Result&lt;(), Box&gt; {
    // Initialize logging
    env_logger::init();
    info!("🚀 Starting test_token program...");

    let client = ArchRpcClient::new(NODE1_ADDRESS);
    println!("📡 Connected to node: {}", NODE1_ADDRESS);
    println!("🌐 Using network: {:?}", BITCOIN_NETWORK);

    // Run the complete token lifecycle
    run_token_lifecycle(&amp;client)?;

    println!("🎉 Token lifecycle completed successfully!");
    Ok(())
}

fn run_token_lifecycle(client: &amp;ArchRpcClient) -&gt; Result&lt;(), Box&gt; {
    // Step 1: Create token mint
    println!("\n📋 Step 1: Creating token mint...");
    let (authority_keypair, token_mint_pubkey) = create_token_mint(client)?;

    // Step 2: Create user accounts
    println!("\n👥 Step 2: Creating user accounts...");
    let (user1_keypair, user1_pubkey, _) = generate_new_keypair(BITCOIN_NETWORK);
    let (user2_keypair, user2_pubkey, _) = generate_new_keypair(BITCOIN_NETWORK);

    create_and_fund_account_with_faucet(&amp;user1_keypair, BITCOIN_NETWORK);
    create_and_fund_account_with_faucet(&amp;user2_keypair, BITCOIN_NETWORK);

    // Step 3: Create token accounts
    println!("\n💳 Step 3: Creating token accounts...");
    let user1_token_account = create_token_account(client, token_mint_pubkey, user1_keypair)?;
    let user2_token_account = create_token_account(client, token_mint_pubkey, user2_keypair)?;

    // Step 4: Mint initial tokens
    println!("\n🪙 Step 4: Minting initial supply...");
    let authority_pubkey = arch_program::pubkey::Pubkey::from_slice(
        &amp;authority_keypair.x_only_public_key().0.serialize()
    );
    mint_tokens(client, &amp;token_mint_pubkey, &amp;user1_token_account, &amp;authority_pubkey, authority_keypair, 1_000_000_000)?; // 1,000 tokens (9 decimals)

    // Step 5: Check balance
    println!("\n💰 Step 5: Checking balances...");
    let user1_balance = get_token_balance(user1_token_account)?;
    println!("User1 balance: {} tokens", user1_balance as f64 / 1_000_000_000.0);

    // Step 6: Transfer tokens
    println!("\n📤 Step 6: Transferring tokens...");
    transfer_tokens(client, &amp;user1_token_account, &amp;user2_token_account, &amp;user1_pubkey, user1_keypair, 500_000_000)?; // 500 tokens

    // Step 7: Check final balances
    println!("\n🏁 Step 7: Final balances...");
    let user1_final = get_token_balance(user1_token_account)?;
    let user2_final = get_token_balance(user2_token_account)?;

    println!("User1 final balance: {} tokens", user1_final as f64 / 1_000_000_000.0);
    println!("User2 final balance: {} tokens", user2_final as f64 / 1_000_000_000.0);

    // Step 8: Demonstrate burning tokens
    println!("\n🔥 Step 8: Burning some tokens...");
    burn_tokens(client, &amp;user2_token_account, &amp;token_mint_pubkey, &amp;user2_pubkey, user2_keypair, 100_000_000)?; // Burn 100 tokens

    let user2_after_burn = get_token_balance(user2_token_account)?;
    println!("User2 balance after burn: {} tokens", user2_after_burn as f64 / 1_000_000_000.0);

    Ok(())
}

fn create_token_mint(client: &amp;ArchRpcClient) -&gt; Result&lt;(bitcoin::key::Keypair, arch_program::pubkey::Pubkey), Box&gt; {
    // 1. Create mint authority (you control the token supply)
    let (authority_keypair, authority_pubkey, _) = generate_new_keypair(BITCOIN_NETWORK);
    create_and_fund_account_with_faucet(&amp;authority_keypair, BITCOIN_NETWORK);

    // 2. Create mint account
    let (token_mint_keypair, token_mint_pubkey, _) = generate_new_keypair(BITCOIN_NETWORK);

    // 3. Create the mint account on-chain
    let create_account_ix = arch_program::system_instruction::create_account(
        &amp;authority_pubkey,       // Payer
        &amp;token_mint_pubkey,      // New account
        arch_program::account::MIN_ACCOUNT_LAMPORTS, // Rent
        Mint::LEN as u64,        // Space needed
        &amp;apl_token::id(),        // Owner program
    );

    // 4. Initialize the mint with your token parameters
    let initialize_mint_ix = apl_token::instruction::initialize_mint(
        &amp;apl_token::id(),
        &amp;token_mint_pubkey,
        &amp;authority_pubkey,       // Mint authority (can create tokens)
        None,                   // No freeze authority (optional)
        9,                      // Decimals (9 = like USDC, 0 = whole numbers only)
    )?;

    // 5. Send transaction
    let transaction = build_and_sign_transaction(
        ArchMessage::new(
            &amp;[create_account_ix, initialize_mint_ix],
            Some(authority_pubkey),
            client.get_best_block_hash()?,
        ),
        vec![authority_keypair, token_mint_keypair],
        BITCOIN_NETWORK,
    );

    let processed_txs = send_transactions_and_wait(vec![transaction]);
    if processed_txs[0].status != Status::Processed {
        return Err("Failed to create token mint".into());
    }

    println!("🎉 Token mint created: {}", token_mint_pubkey);

    Ok((authority_keypair, token_mint_pubkey))
}

fn create_token_account(
    client: &amp;ArchRpcClient,
    token_mint_pubkey: arch_program::pubkey::Pubkey,
    owner_keypair: bitcoin::key::Keypair,
) -&gt; Result&gt; {

    let owner_pubkey = arch_program::pubkey::Pubkey::from_slice(
        &amp;owner_keypair.x_only_public_key().0.serialize()
    );

    // 1. Create account keypair
    let (token_account_keypair, token_account_pubkey, _) = generate_new_keypair(BITCOIN_NETWORK);

    // 2. Create account on-chain
    let create_account_ix = arch_program::system_instruction::create_account(
        &amp;owner_pubkey,
        &amp;token_account_pubkey,
        arch_program::account::MIN_ACCOUNT_LAMPORTS,
        apl_token::state::Account::LEN as u64,
        &amp;apl_token::id(),
    );

    // 3. Initialize token account
    let initialize_account_ix = apl_token::instruction::initialize_account(
        &amp;apl_token::id(),
        &amp;token_account_pubkey,
        &amp;token_mint_pubkey,      // Which token this account holds
        &amp;owner_pubkey,           // Who owns this account
    )?;

    // 4. Send transaction
    let transaction = build_and_sign_transaction(
        ArchMessage::new(
            &amp;[create_account_ix, initialize_account_ix],
            Some(owner_pubkey),
            client.get_best_block_hash()?,
        ),
        vec![owner_keypair, token_account_keypair],
        BITCOIN_NETWORK,
    );

    let processed_txs = send_transactions_and_wait(vec![transaction]);
    if processed_txs[0].status != Status::Processed {
        return Err("Failed to create token account".into());
    }

    println!("💳 Token account created: {}", token_account_pubkey);
    Ok(token_account_pubkey)
}

fn mint_tokens(
    client: &amp;ArchRpcClient,
    mint_pubkey: &amp;arch_program::pubkey::Pubkey,
    account_pubkey: &amp;arch_program::pubkey::Pubkey,
    authority_pubkey: &amp;arch_program::pubkey::Pubkey,
    authority_keypair: bitcoin::key::Keypair,
    amount: u64,
) -&gt; Result&lt;(), Box&gt; {

    // Create mint instruction
    let mint_ix = apl_token::instruction::mint_to(
        &amp;apl_token::id(),
        mint_pubkey,
        account_pubkey,
        authority_pubkey,
        &amp;[],                    // No additional signers for single authority
        amount,                 // Amount to mint (in smallest units)
    )?;

    // Send transaction
    let transaction = build_and_sign_transaction(
        ArchMessage::new(
            &amp;[mint_ix],
            Some(*authority_pubkey),
            client.get_best_block_hash()?,
        ),
        vec![authority_keypair],
        BITCOIN_NETWORK,
    );

    let processed_txs = send_transactions_and_wait(vec![transaction]);
    if processed_txs[0].status != Status::Processed {
        return Err("Failed to mint tokens".into());
    }

    println!("🪙 Minted {} tokens", amount);
    Ok(())
}

fn transfer_tokens(
    client: &amp;ArchRpcClient,
    from_account: &amp;arch_program::pubkey::Pubkey,
    to_account: &amp;arch_program::pubkey::Pubkey,
    owner_pubkey: &amp;arch_program::pubkey::Pubkey,
    owner_keypair: bitcoin::key::Keypair,
    amount: u64,
) -&gt; Result&lt;(), Box&gt; {

    // Create transfer instruction
    let transfer_ix = apl_token::instruction::transfer(
        &amp;apl_token::id(),
        from_account,
        to_account,
        owner_pubkey,
        &amp;[],                    // No additional signers
        amount,
    )?;

    // Send transaction
    let transaction = build_and_sign_transaction(
        ArchMessage::new(
            &amp;[transfer_ix],
            Some(*owner_pubkey),
            client.get_best_block_hash()?,
        ),
        vec![owner_keypair],
        BITCOIN_NETWORK,
    );

    let processed_txs = send_transactions_and_wait(vec![transaction]);
    if processed_txs[0].status != Status::Processed {
        return Err("Failed to transfer tokens".into());
    }

    println!("📤 Transferred {} tokens", amount);
    Ok(())
}

fn burn_tokens(
    client: &amp;ArchRpcClient,
    token_account: &amp;arch_program::pubkey::Pubkey,
    mint_pubkey: &amp;arch_program::pubkey::Pubkey,
    owner_pubkey: &amp;arch_program::pubkey::Pubkey,
    owner_keypair: bitcoin::key::Keypair,
    amount: u64,
) -&gt; Result&lt;(), Box&gt; {

    let burn_ix = apl_token::instruction::burn(
        &amp;apl_token::id(),
        token_account,
        mint_pubkey,
        owner_pubkey,
        &amp;[],
        amount,
    )?;

    let transaction = build_and_sign_transaction(
        ArchMessage::new(
            &amp;[burn_ix],
            Some(*owner_pubkey),
            client.get_best_block_hash()?,
        ),
        vec![owner_keypair],
        BITCOIN_NETWORK,
    );

    let processed_txs = send_transactions_and_wait(vec![transaction]);
    if processed_txs[0].status != Status::Processed {
        return Err("Failed to burn tokens".into());
    }

    println!("🔥 Burned {} tokens", amount);
    Ok(())
}

fn get_token_balance(token_account: arch_program::pubkey::Pubkey) -&gt; Result&gt; {
    let account_info = read_account_info(token_account);
    let account_data = Account::unpack(&amp;account_info.data)?;
    Ok(account_data.amount)
}
Enter fullscreen mode Exit fullscreen mode

📋 Running Your Token Program


# Run your token program
cargo run
Enter fullscreen mode Exit fullscreen mode
Compiling my_arch_token v0.1.0 
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.32s
     Running `target/debug/my_arch_token`
📡 Connected to node: http://localhost:9002/
🌐 Using network: Regtest

📋 Step 1: Creating token mint...
🎉 Token mint created: fd00247fdcf868d0df9663a898456094754ee77d64ebb73c5207fc11b7e0af98

👥 Step 2: Creating user accounts...

💳 Step 3: Creating token accounts...
💳 Token account created: 3b99cd1877aae73f14a7c544cbb205867be3be068a509cd38dff970e2ea7b10b
💳 Token account created: 447e9bdc9c4092be7ff9e23e6dd2e9a2803b4b4503197bbf1ca1e7cabed68960

🪙 Step 4: Minting initial supply...
🪙 Minted 1000000000 tokens

💰 Step 5: Checking balances...
User1 balance: 1 tokens

📤 Step 6: Transferring tokens...
📤 Transferred 500000000 tokens

🏁 Step 7: Final balances...
User1 final balance: 0.5 tokens
User2 final balance: 0.5 tokens

🔥 Step 8: Burning some tokens...
🔥 Burned 100000000 tokens
User2 balance after burn: 0.4 tokens
🎉 Token lifecycle completed successfully!
Enter fullscreen mode Exit fullscreen mode
# Confirm it works
cli show <mint_address>
Enter fullscreen mode Exit fullscreen mode
Welcome to the Arch Network CLI
Public Key: fd00247fdcf868d0df9663a898456094754ee77d64ebb73c5207fc11b7e0af98
Balance: 0.000001024 ARCH
Owner: 61706c2d746f6b656e3030303030303030303030303030303030303030303030
Executable: false
UTXO: 0000000000000000000000000000000000000000000000000000000000000000:0
Data:
  01 00 00 00 4b 86 8a 25 ab 80 05 eb 19 d9 ce bf
  06 54 43 f7 1d 5d 45 f8 c2 bc 2a a3 6e 20 09 81
  d1 3e 58 37 00 e9 a4 35 00 00 00 00 09 01 00 00
  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  00 00
Enter fullscreen mode Exit fullscreen mode

📚 Key Concepts Reference

🏗️ Account Types

Account Type Purpose Size
Mint Account Token metadata and authorities Mint::LEN
Token Account Holds token balances Account::LEN
Multisig Account Shared authority management Variable

🔑 Authority Types

Authority Powers Notes
Mint Authority Create new tokens Required for minting
Freeze Authority Freeze/thaw accounts Optional
Owner Control token account Can transfer, approve
Delegate Spend approved amounts Temporary permission

💡 Best Practices

  • Use appropriate decimals (9 for most tokens, 0 for NFTs)
  • Implement proper error handling for all operations
  • Test thoroughly before mainnet deployment
  • Consider multisig for important authorities
  • Document your token specification and usage

🎯 Next Steps

📖 Learn More

🔧 Advanced Features

🌐 Deploy to Production


🆘 Need Help?

Ready to tokenize on Bitcoin? Start building! 🚀


The APL Token Program provides a robust, battle-tested foundation for tokenization on Arch Network, leveraging Bitcoin's security while maintaining compatibility with proven token standards.

Top comments (0)