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
```