Python bindings for Solana Rust tools providing high-performance blockchain development primitives, RPC functionality, and testing infrastructure.
—
SPL Token program support including token account management, associated token addresses, token state parsing, and mint operations. This provides comprehensive support for fungible and non-fungible tokens on Solana.
Core token program functionality for managing fungible tokens on Solana.
# SPL Token Program ID
ID: Final[Pubkey] = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
# SPL Token 2022 Program ID (extended token program)
TOKEN_2022_PROGRAM_ID: Final[Pubkey] = Pubkey.from_string("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb")Deterministic token account address derivation for user-friendly token management.
def get_associated_token_address(owner: Pubkey, mint: Pubkey) -> Pubkey:
"""
Calculate associated token account address for owner and mint.
Parameters:
- owner: Pubkey, token account owner (user's wallet)
- mint: Pubkey, token mint address
Returns:
Pubkey, deterministic associated token account address
"""
def get_associated_token_address_with_program_id(
owner: Pubkey,
mint: Pubkey,
program_id: Pubkey
) -> Pubkey:
"""
Calculate associated token address for specific token program.
Parameters:
- owner: Pubkey, token account owner
- mint: Pubkey, token mint address
- program_id: Pubkey, token program ID (SPL Token or Token-2022)
Returns:
Pubkey, associated token account address
"""
def create_associated_token_account_instruction(
payer: Pubkey,
owner: Pubkey,
mint: Pubkey
) -> Instruction:
"""
Create instruction to initialize associated token account.
Parameters:
- payer: Pubkey, account that pays for creation (must sign)
- owner: Pubkey, token account owner
- mint: Pubkey, token mint
Returns:
Instruction for creating associated token account
"""Token account data structures and state management.
class TokenAccount:
"""
SPL token account data structure containing balance and metadata.
"""
def __init__(
self,
mint: Pubkey,
owner: Pubkey,
amount: int,
delegate: Optional[Pubkey],
state: TokenAccountState,
is_native: Optional[int],
delegated_amount: int,
close_authority: Optional[Pubkey]
):
"""
Create token account with balance and permissions.
Parameters:
- mint: Pubkey, token mint this account holds
- owner: Pubkey, account owner (can transfer tokens)
- amount: int, token balance (raw amount, not adjusted for decimals)
- delegate: Optional[Pubkey], delegated authority for spending
- state: TokenAccountState, account state (initialized/frozen/etc)
- is_native: Optional[int], native SOL amount if wrapped SOL account
- delegated_amount: int, amount delegated to delegate
- close_authority: Optional[Pubkey], authority that can close account
"""
@classmethod
def deserialize(cls, data: bytes) -> 'TokenAccount':
"""
Deserialize token account from account data.
Parameters:
- data: bytes, 165-byte token account data
Returns:
TokenAccount object
Raises:
- ValueError: if data is invalid or wrong size
"""
def serialize(self) -> bytes:
"""
Serialize token account to bytes.
Returns:
bytes, 165-byte serialized account data
"""
@property
def mint(self) -> Pubkey:
"""Token mint address."""
@property
def owner(self) -> Pubkey:
"""Account owner."""
@property
def amount(self) -> int:
"""Token balance (raw amount)."""
@property
def delegate(self) -> Optional[Pubkey]:
"""Delegate authority."""
@property
def state(self) -> TokenAccountState:
"""Account state."""
@property
def is_native(self) -> Optional[int]:
"""Native SOL amount for wrapped SOL."""
@property
def delegated_amount(self) -> int:
"""Amount available to delegate."""
@property
def close_authority(self) -> Optional[Pubkey]:
"""Close authority."""
def is_frozen(self) -> bool:
"""
Check if account is frozen.
Returns:
bool, True if account is frozen
"""
def is_initialized(self) -> bool:
"""
Check if account is initialized.
Returns:
bool, True if account is initialized
"""
def ui_amount(self, decimals: int) -> float:
"""
Convert raw amount to UI amount with decimals.
Parameters:
- decimals: int, number of decimal places for this token
Returns:
float, human-readable token amount
"""class TokenAccountState:
"""
Token account state enumeration.
"""
Uninitialized: 'TokenAccountState' # Account not yet initialized
Initialized: 'TokenAccountState' # Account ready for use
Frozen: 'TokenAccountState' # Account frozen (cannot transfer)
def __str__(self) -> str:
"""String representation of state."""
def __eq__(self, other) -> bool:
"""Compare equality with another state."""Token mint data structures defining token properties and supply.
class Mint:
"""
SPL token mint account containing supply and authority information.
"""
def __init__(
self,
mint_authority: Optional[Pubkey],
supply: int,
decimals: int,
is_initialized: bool,
freeze_authority: Optional[Pubkey]
):
"""
Create mint account with supply and authorities.
Parameters:
- mint_authority: Optional[Pubkey], authority that can mint tokens (None = fixed supply)
- supply: int, total token supply (raw amount)
- decimals: int, number of decimal places for UI representation
- is_initialized: bool, whether mint is initialized
- freeze_authority: Optional[Pubkey], authority that can freeze accounts
"""
@classmethod
def deserialize(cls, data: bytes) -> 'Mint':
"""
Deserialize mint from account data.
Parameters:
- data: bytes, 82-byte mint account data
Returns:
Mint object
Raises:
- ValueError: if data is invalid or wrong size
"""
def serialize(self) -> bytes:
"""
Serialize mint to bytes.
Returns:
bytes, 82-byte serialized mint data
"""
@property
def mint_authority(self) -> Optional[Pubkey]:
"""Mint authority (can create new tokens)."""
@property
def supply(self) -> int:
"""Total token supply (raw amount)."""
@property
def decimals(self) -> int:
"""Decimal places for UI representation."""
@property
def is_initialized(self) -> bool:
"""Whether mint is initialized."""
@property
def freeze_authority(self) -> Optional[Pubkey]:
"""Freeze authority (can freeze token accounts)."""
def ui_supply(self) -> float:
"""
Convert raw supply to UI amount with decimals.
Returns:
float, human-readable total supply
"""
def has_mint_authority(self) -> bool:
"""
Check if mint has active mint authority.
Returns:
bool, True if tokens can still be minted
"""
def has_freeze_authority(self) -> bool:
"""
Check if mint has freeze authority.
Returns:
bool, True if accounts can be frozen
"""Multi-signature account management for enhanced security.
class Multisig:
"""
Multi-signature account requiring multiple signatures for operations.
"""
def __init__(
self,
m: int,
n: int,
is_initialized: bool,
signers: List[Pubkey]
):
"""
Create multisig account configuration.
Parameters:
- m: int, number of signatures required (threshold)
- n: int, total number of signers
- is_initialized: bool, whether multisig is initialized
- signers: List[Pubkey], list of authorized signers (up to 11)
"""
@classmethod
def deserialize(cls, data: bytes) -> 'Multisig':
"""
Deserialize multisig from account data.
Parameters:
- data: bytes, multisig account data
Returns:
Multisig object
"""
def serialize(self) -> bytes:
"""
Serialize multisig to bytes.
Returns:
bytes, serialized multisig data
"""
@property
def m(self) -> int:
"""Required signature threshold."""
@property
def n(self) -> int:
"""Total number of signers."""
@property
def is_initialized(self) -> bool:
"""Whether multisig is initialized."""
@property
def signers(self) -> List[Pubkey]:
"""List of authorized signers."""
def is_valid_signer(self, signer: Pubkey) -> bool:
"""
Check if pubkey is authorized signer.
Parameters:
- signer: Pubkey, potential signer
Returns:
bool, True if signer is authorized
"""
def has_sufficient_signatures(self, signatures: List[Pubkey]) -> bool:
"""
Check if signature list meets threshold.
Parameters:
- signatures: List[Pubkey], provided signatures
Returns:
bool, True if enough valid signatures
"""from solders.token.associated import get_associated_token_address
from solders.token.state import TokenAccount, Mint, TokenAccountState
from solders.pubkey import Pubkey
# Calculate associated token account address
owner = Pubkey.from_string("7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj")
mint = Pubkey.from_string("So11111111111111111111111111111111111111112") # Wrapped SOL
ata_address = get_associated_token_address(owner, mint)
print(f"Associated token account: {ata_address}")# Parse token account data from RPC response
token_account_data = bytes(165) # Retrieved from RPC call
try:
token_account = TokenAccount.deserialize(token_account_data)
print(f"Mint: {token_account.mint}")
print(f"Owner: {token_account.owner}")
print(f"Balance (raw): {token_account.amount}")
print(f"State: {token_account.state}")
print(f"Is frozen: {token_account.is_frozen()}")
if token_account.delegate:
print(f"Delegate: {token_account.delegate}")
print(f"Delegated amount: {token_account.delegated_amount}")
# Convert to UI amount (need decimals from mint)
decimals = 9 # SOL has 9 decimals
ui_amount = token_account.ui_amount(decimals)
print(f"Balance (UI): {ui_amount:.9f}")
except ValueError as e:
print(f"Failed to parse token account: {e}")# Parse mint account data
mint_account_data = bytes(82) # Retrieved from RPC call
try:
mint = Mint.deserialize(mint_account_data)
print(f"Supply (raw): {mint.supply}")
print(f"Decimals: {mint.decimals}")
print(f"UI Supply: {mint.ui_supply()}")
print(f"Can mint more: {mint.has_mint_authority()}")
print(f"Can freeze accounts: {mint.has_freeze_authority()}")
if mint.mint_authority:
print(f"Mint authority: {mint.mint_authority}")
else:
print("Fixed supply (no mint authority)")
if mint.freeze_authority:
print(f"Freeze authority: {mint.freeze_authority}")
except ValueError as e:
print(f"Failed to parse mint: {e}")from solders.token.associated import create_associated_token_account_instruction
from solders.transaction import Transaction
from solders.keypair import Keypair
# Create associated token account
payer = Keypair()
owner = Keypair()
mint = Pubkey.from_string("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") # USDC mint
# Check if ATA already exists (in real usage)
ata = get_associated_token_address(owner.pubkey(), mint)
# Create instruction to initialize ATA
create_ata_ix = create_associated_token_account_instruction(
payer=payer.pubkey(),
owner=owner.pubkey(),
mint=mint
)
# Add to transaction
tx = Transaction.new_with_payer([create_ata_ix], payer.pubkey())from solders.instruction import Instruction, AccountMeta
from solders.token import ID as TOKEN_PROGRAM_ID
# Token transfer instruction data (instruction discriminator + amount)
def create_transfer_instruction(
source: Pubkey,
destination: Pubkey,
owner: Pubkey,
amount: int
) -> Instruction:
"""Create SPL token transfer instruction."""
# Transfer instruction data: [3, amount_bytes...]
instruction_data = bytearray([3]) # Transfer instruction discriminator
instruction_data.extend(amount.to_bytes(8, 'little'))
accounts = [
AccountMeta(source, is_signer=False, is_writable=True), # Source account
AccountMeta(destination, is_signer=False, is_writable=True), # Destination account
AccountMeta(owner, is_signer=True, is_writable=False), # Owner authority
]
return Instruction(TOKEN_PROGRAM_ID, accounts, bytes(instruction_data))
# Transfer 100 tokens (with 6 decimals = 100,000,000 raw amount)
source_ata = get_associated_token_address(sender.pubkey(), mint)
dest_ata = get_associated_token_address(recipient.pubkey(), mint)
transfer_ix = create_transfer_instruction(
source=source_ata,
destination=dest_ata,
owner=sender.pubkey(),
amount=100_000_000 # 100 tokens with 6 decimals
)from solders.token.state import Multisig
# Parse multisig account
multisig_data = bytes(355) # Retrieved from RPC call
try:
multisig = Multisig.deserialize(multisig_data)
print(f"Required signatures: {multisig.m}")
print(f"Total signers: {multisig.n}")
print(f"Signers: {[str(s) for s in multisig.signers]}")
# Check if specific signer is authorized
potential_signer = Pubkey.from_string("...")
if multisig.is_valid_signer(potential_signer):
print("Authorized signer")
# Check if we have enough signatures
provided_signatures = [signer1.pubkey(), signer2.pubkey()]
if multisig.has_sufficient_signatures(provided_signatures):
print("Sufficient signatures for execution")
except ValueError as e:
print(f"Failed to parse multisig: {e}")# Check token account state
if token_account.state == TokenAccountState.Initialized:
print("Account is ready for transfers")
elif token_account.state == TokenAccountState.Frozen:
print("Account is frozen - cannot transfer")
elif token_account.state == TokenAccountState.Uninitialized:
print("Account needs initialization")
# Check for delegation
if token_account.delegate:
print(f"Tokens delegated to: {token_account.delegate}")
print(f"Delegated amount: {token_account.delegated_amount}")
# Remaining balance available to owner
owner_available = token_account.amount - token_account.delegated_amount
print(f"Owner available: {owner_available}")
# Check for wrapped SOL (native token account)
if token_account.is_native:
print(f"Wrapped SOL account with {token_account.is_native} lamports")# Working with Token-2022 extended tokens
from solders.token import TOKEN_2022_PROGRAM_ID
# Calculate ATA for Token-2022 program
token_2022_ata = get_associated_token_address_with_program_id(
owner=owner.pubkey(),
mint=mint,
program_id=TOKEN_2022_PROGRAM_ID
)
# Different program IDs create different ATAs for same owner+mint combination
spl_token_ata = get_associated_token_address(owner.pubkey(), mint)
print(f"SPL Token ATA: {spl_token_ata}")
print(f"Token-2022 ATA: {token_2022_ata}")# Wrapped SOL (native token)
NATIVE_MINT: Final[Pubkey] = Pubkey.from_string("So11111111111111111111111111111111111111112")
# Common stablecoins (examples)
USDC_MINT: Final[Pubkey] = Pubkey.from_string("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")
USDT_MINT: Final[Pubkey] = Pubkey.from_string("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB")# Account data sizes
TOKEN_ACCOUNT_SIZE: Final[int] = 165 # SPL token account
MINT_ACCOUNT_SIZE: Final[int] = 82 # SPL token mint
MULTISIG_ACCOUNT_SIZE: Final[int] = 355 # Multisig account (11 signers)
# Minimum rent-exempt balance calculation
def calculate_rent_exempt_balance(account_size: int) -> int:
"""Calculate minimum lamports for rent exemption."""
# This is a simplified calculation - use Rent sysvar in production
return 890880 + (account_size * 6960) # Approximate values# SPL Token instruction types
class TokenInstruction:
InitializeMint = 0
InitializeAccount = 1
InitializeMultisig = 2
Transfer = 3
Approve = 4
Revoke = 5
SetAuthority = 6
MintTo = 7
Burn = 8
CloseAccount = 9
FreezeAccount = 10
ThawAccount = 11
TransferChecked = 12
ApproveChecked = 13
MintToChecked = 14
BurnChecked = 15from solders.token.state import TokenAccount, TokenAccountState
def validate_token_transfer(
source_account: TokenAccount,
amount: int,
owner: Pubkey
) -> bool:
"""Validate token transfer before sending."""
# Check account state
if source_account.state != TokenAccountState.Initialized:
raise ValueError("Source account not initialized")
if source_account.state == TokenAccountState.Frozen:
raise ValueError("Source account is frozen")
# Check balance
if source_account.amount < amount:
raise ValueError(f"Insufficient balance: {source_account.amount} < {amount}")
# Check ownership or delegation
if source_account.owner != owner and source_account.delegate != owner:
raise ValueError("Not authorized to transfer from this account")
# Check delegate limits
if source_account.delegate == owner:
if source_account.delegated_amount < amount:
raise ValueError("Amount exceeds delegated limit")
return True
# Usage in transaction building
try:
validate_token_transfer(source_token_account, transfer_amount, sender.pubkey())
# Proceed with creating transfer instruction
except ValueError as e:
print(f"Transfer validation failed: {e}")def safe_parse_token_account(data: bytes) -> Optional[TokenAccount]:
"""Safely parse token account data with error handling."""
try:
if len(data) != TOKEN_ACCOUNT_SIZE:
print(f"Invalid token account size: {len(data)} bytes")
return None
return TokenAccount.deserialize(data)
except ValueError as e:
print(f"Token account parsing error: {e}")
return None
except Exception as e:
print(f"Unexpected error parsing token account: {e}")
return None
def safe_parse_mint(data: bytes) -> Optional[Mint]:
"""Safely parse mint account data with error handling."""
try:
if len(data) != MINT_ACCOUNT_SIZE:
print(f"Invalid mint size: {len(data)} bytes")
return None
return Mint.deserialize(data)
except ValueError as e:
print(f"Mint parsing error: {e}")
return None
except Exception as e:
print(f"Unexpected error parsing mint: {e}")
return None