or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

account-management.mdcryptographic-primitives.mderror-handling.mdindex.mdnetwork-sysvars.mdrpc-functionality.mdsystem-programs.mdtesting-infrastructure.mdtoken-operations.mdtransaction-construction.mdtransaction-status.md

token-operations.mddocs/

0

# Token Operations

1

2

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.

3

4

## Capabilities

5

6

### SPL Token Program

7

8

Core token program functionality for managing fungible tokens on Solana.

9

10

```python { .api }

11

# SPL Token Program ID

12

ID: Final[Pubkey] = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")

13

14

# SPL Token 2022 Program ID (extended token program)

15

TOKEN_2022_PROGRAM_ID: Final[Pubkey] = Pubkey.from_string("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb")

16

```

17

18

### Associated Token Addresses

19

20

Deterministic token account address derivation for user-friendly token management.

21

22

```python { .api }

23

def get_associated_token_address(owner: Pubkey, mint: Pubkey) -> Pubkey:

24

"""

25

Calculate associated token account address for owner and mint.

26

27

Parameters:

28

- owner: Pubkey, token account owner (user's wallet)

29

- mint: Pubkey, token mint address

30

31

Returns:

32

Pubkey, deterministic associated token account address

33

"""

34

35

def get_associated_token_address_with_program_id(

36

owner: Pubkey,

37

mint: Pubkey,

38

program_id: Pubkey

39

) -> Pubkey:

40

"""

41

Calculate associated token address for specific token program.

42

43

Parameters:

44

- owner: Pubkey, token account owner

45

- mint: Pubkey, token mint address

46

- program_id: Pubkey, token program ID (SPL Token or Token-2022)

47

48

Returns:

49

Pubkey, associated token account address

50

"""

51

52

def create_associated_token_account_instruction(

53

payer: Pubkey,

54

owner: Pubkey,

55

mint: Pubkey

56

) -> Instruction:

57

"""

58

Create instruction to initialize associated token account.

59

60

Parameters:

61

- payer: Pubkey, account that pays for creation (must sign)

62

- owner: Pubkey, token account owner

63

- mint: Pubkey, token mint

64

65

Returns:

66

Instruction for creating associated token account

67

"""

68

```

69

70

### Token Account State

71

72

Token account data structures and state management.

73

74

```python { .api }

75

class TokenAccount:

76

"""

77

SPL token account data structure containing balance and metadata.

78

"""

79

def __init__(

80

self,

81

mint: Pubkey,

82

owner: Pubkey,

83

amount: int,

84

delegate: Optional[Pubkey],

85

state: TokenAccountState,

86

is_native: Optional[int],

87

delegated_amount: int,

88

close_authority: Optional[Pubkey]

89

):

90

"""

91

Create token account with balance and permissions.

92

93

Parameters:

94

- mint: Pubkey, token mint this account holds

95

- owner: Pubkey, account owner (can transfer tokens)

96

- amount: int, token balance (raw amount, not adjusted for decimals)

97

- delegate: Optional[Pubkey], delegated authority for spending

98

- state: TokenAccountState, account state (initialized/frozen/etc)

99

- is_native: Optional[int], native SOL amount if wrapped SOL account

100

- delegated_amount: int, amount delegated to delegate

101

- close_authority: Optional[Pubkey], authority that can close account

102

"""

103

104

@classmethod

105

def deserialize(cls, data: bytes) -> 'TokenAccount':

106

"""

107

Deserialize token account from account data.

108

109

Parameters:

110

- data: bytes, 165-byte token account data

111

112

Returns:

113

TokenAccount object

114

115

Raises:

116

- ValueError: if data is invalid or wrong size

117

"""

118

119

def serialize(self) -> bytes:

120

"""

121

Serialize token account to bytes.

122

123

Returns:

124

bytes, 165-byte serialized account data

125

"""

126

127

@property

128

def mint(self) -> Pubkey:

129

"""Token mint address."""

130

131

@property

132

def owner(self) -> Pubkey:

133

"""Account owner."""

134

135

@property

136

def amount(self) -> int:

137

"""Token balance (raw amount)."""

138

139

@property

140

def delegate(self) -> Optional[Pubkey]:

141

"""Delegate authority."""

142

143

@property

144

def state(self) -> TokenAccountState:

145

"""Account state."""

146

147

@property

148

def is_native(self) -> Optional[int]:

149

"""Native SOL amount for wrapped SOL."""

150

151

@property

152

def delegated_amount(self) -> int:

153

"""Amount available to delegate."""

154

155

@property

156

def close_authority(self) -> Optional[Pubkey]:

157

"""Close authority."""

158

159

def is_frozen(self) -> bool:

160

"""

161

Check if account is frozen.

162

163

Returns:

164

bool, True if account is frozen

165

"""

166

167

def is_initialized(self) -> bool:

168

"""

169

Check if account is initialized.

170

171

Returns:

172

bool, True if account is initialized

173

"""

174

175

def ui_amount(self, decimals: int) -> float:

176

"""

177

Convert raw amount to UI amount with decimals.

178

179

Parameters:

180

- decimals: int, number of decimal places for this token

181

182

Returns:

183

float, human-readable token amount

184

"""

185

```

186

187

```python { .api }

188

class TokenAccountState:

189

"""

190

Token account state enumeration.

191

"""

192

Uninitialized: 'TokenAccountState' # Account not yet initialized

193

Initialized: 'TokenAccountState' # Account ready for use

194

Frozen: 'TokenAccountState' # Account frozen (cannot transfer)

195

196

def __str__(self) -> str:

197

"""String representation of state."""

198

199

def __eq__(self, other) -> bool:

200

"""Compare equality with another state."""

201

```

202

203

### Mint Account State

204

205

Token mint data structures defining token properties and supply.

206

207

```python { .api }

208

class Mint:

209

"""

210

SPL token mint account containing supply and authority information.

211

"""

212

def __init__(

213

self,

214

mint_authority: Optional[Pubkey],

215

supply: int,

216

decimals: int,

217

is_initialized: bool,

218

freeze_authority: Optional[Pubkey]

219

):

220

"""

221

Create mint account with supply and authorities.

222

223

Parameters:

224

- mint_authority: Optional[Pubkey], authority that can mint tokens (None = fixed supply)

225

- supply: int, total token supply (raw amount)

226

- decimals: int, number of decimal places for UI representation

227

- is_initialized: bool, whether mint is initialized

228

- freeze_authority: Optional[Pubkey], authority that can freeze accounts

229

"""

230

231

@classmethod

232

def deserialize(cls, data: bytes) -> 'Mint':

233

"""

234

Deserialize mint from account data.

235

236

Parameters:

237

- data: bytes, 82-byte mint account data

238

239

Returns:

240

Mint object

241

242

Raises:

243

- ValueError: if data is invalid or wrong size

244

"""

245

246

def serialize(self) -> bytes:

247

"""

248

Serialize mint to bytes.

249

250

Returns:

251

bytes, 82-byte serialized mint data

252

"""

253

254

@property

255

def mint_authority(self) -> Optional[Pubkey]:

256

"""Mint authority (can create new tokens)."""

257

258

@property

259

def supply(self) -> int:

260

"""Total token supply (raw amount)."""

261

262

@property

263

def decimals(self) -> int:

264

"""Decimal places for UI representation."""

265

266

@property

267

def is_initialized(self) -> bool:

268

"""Whether mint is initialized."""

269

270

@property

271

def freeze_authority(self) -> Optional[Pubkey]:

272

"""Freeze authority (can freeze token accounts)."""

273

274

def ui_supply(self) -> float:

275

"""

276

Convert raw supply to UI amount with decimals.

277

278

Returns:

279

float, human-readable total supply

280

"""

281

282

def has_mint_authority(self) -> bool:

283

"""

284

Check if mint has active mint authority.

285

286

Returns:

287

bool, True if tokens can still be minted

288

"""

289

290

def has_freeze_authority(self) -> bool:

291

"""

292

Check if mint has freeze authority.

293

294

Returns:

295

bool, True if accounts can be frozen

296

"""

297

```

298

299

### Multisig Account Support

300

301

Multi-signature account management for enhanced security.

302

303

```python { .api }

304

class Multisig:

305

"""

306

Multi-signature account requiring multiple signatures for operations.

307

"""

308

def __init__(

309

self,

310

m: int,

311

n: int,

312

is_initialized: bool,

313

signers: List[Pubkey]

314

):

315

"""

316

Create multisig account configuration.

317

318

Parameters:

319

- m: int, number of signatures required (threshold)

320

- n: int, total number of signers

321

- is_initialized: bool, whether multisig is initialized

322

- signers: List[Pubkey], list of authorized signers (up to 11)

323

"""

324

325

@classmethod

326

def deserialize(cls, data: bytes) -> 'Multisig':

327

"""

328

Deserialize multisig from account data.

329

330

Parameters:

331

- data: bytes, multisig account data

332

333

Returns:

334

Multisig object

335

"""

336

337

def serialize(self) -> bytes:

338

"""

339

Serialize multisig to bytes.

340

341

Returns:

342

bytes, serialized multisig data

343

"""

344

345

@property

346

def m(self) -> int:

347

"""Required signature threshold."""

348

349

@property

350

def n(self) -> int:

351

"""Total number of signers."""

352

353

@property

354

def is_initialized(self) -> bool:

355

"""Whether multisig is initialized."""

356

357

@property

358

def signers(self) -> List[Pubkey]:

359

"""List of authorized signers."""

360

361

def is_valid_signer(self, signer: Pubkey) -> bool:

362

"""

363

Check if pubkey is authorized signer.

364

365

Parameters:

366

- signer: Pubkey, potential signer

367

368

Returns:

369

bool, True if signer is authorized

370

"""

371

372

def has_sufficient_signatures(self, signatures: List[Pubkey]) -> bool:

373

"""

374

Check if signature list meets threshold.

375

376

Parameters:

377

- signatures: List[Pubkey], provided signatures

378

379

Returns:

380

bool, True if enough valid signatures

381

"""

382

```

383

384

## Usage Examples

385

386

### Basic Token Operations

387

388

```python

389

from solders.token.associated import get_associated_token_address

390

from solders.token.state import TokenAccount, Mint, TokenAccountState

391

from solders.pubkey import Pubkey

392

393

# Calculate associated token account address

394

owner = Pubkey.from_string("7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj")

395

mint = Pubkey.from_string("So11111111111111111111111111111111111111112") # Wrapped SOL

396

397

ata_address = get_associated_token_address(owner, mint)

398

print(f"Associated token account: {ata_address}")

399

```

400

401

### Token Account Analysis

402

403

```python

404

# Parse token account data from RPC response

405

token_account_data = bytes(165) # Retrieved from RPC call

406

407

try:

408

token_account = TokenAccount.deserialize(token_account_data)

409

410

print(f"Mint: {token_account.mint}")

411

print(f"Owner: {token_account.owner}")

412

print(f"Balance (raw): {token_account.amount}")

413

print(f"State: {token_account.state}")

414

print(f"Is frozen: {token_account.is_frozen()}")

415

416

if token_account.delegate:

417

print(f"Delegate: {token_account.delegate}")

418

print(f"Delegated amount: {token_account.delegated_amount}")

419

420

# Convert to UI amount (need decimals from mint)

421

decimals = 9 # SOL has 9 decimals

422

ui_amount = token_account.ui_amount(decimals)

423

print(f"Balance (UI): {ui_amount:.9f}")

424

425

except ValueError as e:

426

print(f"Failed to parse token account: {e}")

427

```

428

429

### Mint Account Analysis

430

431

```python

432

# Parse mint account data

433

mint_account_data = bytes(82) # Retrieved from RPC call

434

435

try:

436

mint = Mint.deserialize(mint_account_data)

437

438

print(f"Supply (raw): {mint.supply}")

439

print(f"Decimals: {mint.decimals}")

440

print(f"UI Supply: {mint.ui_supply()}")

441

print(f"Can mint more: {mint.has_mint_authority()}")

442

print(f"Can freeze accounts: {mint.has_freeze_authority()}")

443

444

if mint.mint_authority:

445

print(f"Mint authority: {mint.mint_authority}")

446

else:

447

print("Fixed supply (no mint authority)")

448

449

if mint.freeze_authority:

450

print(f"Freeze authority: {mint.freeze_authority}")

451

452

except ValueError as e:

453

print(f"Failed to parse mint: {e}")

454

```

455

456

### Creating Associated Token Account

457

458

```python

459

from solders.token.associated import create_associated_token_account_instruction

460

from solders.transaction import Transaction

461

from solders.keypair import Keypair

462

463

# Create associated token account

464

payer = Keypair()

465

owner = Keypair()

466

mint = Pubkey.from_string("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") # USDC mint

467

468

# Check if ATA already exists (in real usage)

469

ata = get_associated_token_address(owner.pubkey(), mint)

470

471

# Create instruction to initialize ATA

472

create_ata_ix = create_associated_token_account_instruction(

473

payer=payer.pubkey(),

474

owner=owner.pubkey(),

475

mint=mint

476

)

477

478

# Add to transaction

479

tx = Transaction.new_with_payer([create_ata_ix], payer.pubkey())

480

```

481

482

### Token Transfer Setup

483

484

```python

485

from solders.instruction import Instruction, AccountMeta

486

from solders.token import ID as TOKEN_PROGRAM_ID

487

488

# Token transfer instruction data (instruction discriminator + amount)

489

def create_transfer_instruction(

490

source: Pubkey,

491

destination: Pubkey,

492

owner: Pubkey,

493

amount: int

494

) -> Instruction:

495

"""Create SPL token transfer instruction."""

496

497

# Transfer instruction data: [3, amount_bytes...]

498

instruction_data = bytearray([3]) # Transfer instruction discriminator

499

instruction_data.extend(amount.to_bytes(8, 'little'))

500

501

accounts = [

502

AccountMeta(source, is_signer=False, is_writable=True), # Source account

503

AccountMeta(destination, is_signer=False, is_writable=True), # Destination account

504

AccountMeta(owner, is_signer=True, is_writable=False), # Owner authority

505

]

506

507

return Instruction(TOKEN_PROGRAM_ID, accounts, bytes(instruction_data))

508

509

# Transfer 100 tokens (with 6 decimals = 100,000,000 raw amount)

510

source_ata = get_associated_token_address(sender.pubkey(), mint)

511

dest_ata = get_associated_token_address(recipient.pubkey(), mint)

512

513

transfer_ix = create_transfer_instruction(

514

source=source_ata,

515

destination=dest_ata,

516

owner=sender.pubkey(),

517

amount=100_000_000 # 100 tokens with 6 decimals

518

)

519

```

520

521

### Multisig Operations

522

523

```python

524

from solders.token.state import Multisig

525

526

# Parse multisig account

527

multisig_data = bytes(355) # Retrieved from RPC call

528

529

try:

530

multisig = Multisig.deserialize(multisig_data)

531

532

print(f"Required signatures: {multisig.m}")

533

print(f"Total signers: {multisig.n}")

534

print(f"Signers: {[str(s) for s in multisig.signers]}")

535

536

# Check if specific signer is authorized

537

potential_signer = Pubkey.from_string("...")

538

if multisig.is_valid_signer(potential_signer):

539

print("Authorized signer")

540

541

# Check if we have enough signatures

542

provided_signatures = [signer1.pubkey(), signer2.pubkey()]

543

if multisig.has_sufficient_signatures(provided_signatures):

544

print("Sufficient signatures for execution")

545

546

except ValueError as e:

547

print(f"Failed to parse multisig: {e}")

548

```

549

550

### Token Account State Management

551

552

```python

553

# Check token account state

554

if token_account.state == TokenAccountState.Initialized:

555

print("Account is ready for transfers")

556

elif token_account.state == TokenAccountState.Frozen:

557

print("Account is frozen - cannot transfer")

558

elif token_account.state == TokenAccountState.Uninitialized:

559

print("Account needs initialization")

560

561

# Check for delegation

562

if token_account.delegate:

563

print(f"Tokens delegated to: {token_account.delegate}")

564

print(f"Delegated amount: {token_account.delegated_amount}")

565

566

# Remaining balance available to owner

567

owner_available = token_account.amount - token_account.delegated_amount

568

print(f"Owner available: {owner_available}")

569

570

# Check for wrapped SOL (native token account)

571

if token_account.is_native:

572

print(f"Wrapped SOL account with {token_account.is_native} lamports")

573

```

574

575

### Token Metadata and Extensions

576

577

```python

578

# Working with Token-2022 extended tokens

579

from solders.token import TOKEN_2022_PROGRAM_ID

580

581

# Calculate ATA for Token-2022 program

582

token_2022_ata = get_associated_token_address_with_program_id(

583

owner=owner.pubkey(),

584

mint=mint,

585

program_id=TOKEN_2022_PROGRAM_ID

586

)

587

588

# Different program IDs create different ATAs for same owner+mint combination

589

spl_token_ata = get_associated_token_address(owner.pubkey(), mint)

590

print(f"SPL Token ATA: {spl_token_ata}")

591

print(f"Token-2022 ATA: {token_2022_ata}")

592

```

593

594

## Token Program Constants

595

596

### Well-Known Mints

597

598

```python { .api }

599

# Wrapped SOL (native token)

600

NATIVE_MINT: Final[Pubkey] = Pubkey.from_string("So11111111111111111111111111111111111111112")

601

602

# Common stablecoins (examples)

603

USDC_MINT: Final[Pubkey] = Pubkey.from_string("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")

604

USDT_MINT: Final[Pubkey] = Pubkey.from_string("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB")

605

```

606

607

### Account Sizes

608

609

```python { .api }

610

# Account data sizes

611

TOKEN_ACCOUNT_SIZE: Final[int] = 165 # SPL token account

612

MINT_ACCOUNT_SIZE: Final[int] = 82 # SPL token mint

613

MULTISIG_ACCOUNT_SIZE: Final[int] = 355 # Multisig account (11 signers)

614

615

# Minimum rent-exempt balance calculation

616

def calculate_rent_exempt_balance(account_size: int) -> int:

617

"""Calculate minimum lamports for rent exemption."""

618

# This is a simplified calculation - use Rent sysvar in production

619

return 890880 + (account_size * 6960) # Approximate values

620

```

621

622

### Instruction Discriminators

623

624

```python { .api }

625

# SPL Token instruction types

626

class TokenInstruction:

627

InitializeMint = 0

628

InitializeAccount = 1

629

InitializeMultisig = 2

630

Transfer = 3

631

Approve = 4

632

Revoke = 5

633

SetAuthority = 6

634

MintTo = 7

635

Burn = 8

636

CloseAccount = 9

637

FreezeAccount = 10

638

ThawAccount = 11

639

TransferChecked = 12

640

ApproveChecked = 13

641

MintToChecked = 14

642

BurnChecked = 15

643

```

644

645

## Error Handling

646

647

### Token Operation Errors

648

649

```python

650

from solders.token.state import TokenAccount, TokenAccountState

651

652

def validate_token_transfer(

653

source_account: TokenAccount,

654

amount: int,

655

owner: Pubkey

656

) -> bool:

657

"""Validate token transfer before sending."""

658

659

# Check account state

660

if source_account.state != TokenAccountState.Initialized:

661

raise ValueError("Source account not initialized")

662

663

if source_account.state == TokenAccountState.Frozen:

664

raise ValueError("Source account is frozen")

665

666

# Check balance

667

if source_account.amount < amount:

668

raise ValueError(f"Insufficient balance: {source_account.amount} < {amount}")

669

670

# Check ownership or delegation

671

if source_account.owner != owner and source_account.delegate != owner:

672

raise ValueError("Not authorized to transfer from this account")

673

674

# Check delegate limits

675

if source_account.delegate == owner:

676

if source_account.delegated_amount < amount:

677

raise ValueError("Amount exceeds delegated limit")

678

679

return True

680

681

# Usage in transaction building

682

try:

683

validate_token_transfer(source_token_account, transfer_amount, sender.pubkey())

684

# Proceed with creating transfer instruction

685

except ValueError as e:

686

print(f"Transfer validation failed: {e}")

687

```

688

689

### Account Parsing Errors

690

691

```python

692

def safe_parse_token_account(data: bytes) -> Optional[TokenAccount]:

693

"""Safely parse token account data with error handling."""

694

try:

695

if len(data) != TOKEN_ACCOUNT_SIZE:

696

print(f"Invalid token account size: {len(data)} bytes")

697

return None

698

699

return TokenAccount.deserialize(data)

700

except ValueError as e:

701

print(f"Token account parsing error: {e}")

702

return None

703

except Exception as e:

704

print(f"Unexpected error parsing token account: {e}")

705

return None

706

707

def safe_parse_mint(data: bytes) -> Optional[Mint]:

708

"""Safely parse mint account data with error handling."""

709

try:

710

if len(data) != MINT_ACCOUNT_SIZE:

711

print(f"Invalid mint size: {len(data)} bytes")

712

return None

713

714

return Mint.deserialize(data)

715

except ValueError as e:

716

print(f"Mint parsing error: {e}")

717

return None

718

except Exception as e:

719

print(f"Unexpected error parsing mint: {e}")

720

return None

721

```