0
# Transaction Construction
1
2
Transaction and message building with support for both legacy and versioned formats. This includes instruction compilation, message construction, address lookup table integration, and transaction signing for efficient Solana blockchain interactions.
3
4
## Capabilities
5
6
### Transaction Types
7
8
Core transaction structures supporting both legacy and modern versioned formats with address lookup table compression.
9
10
```python { .api }
11
class Transaction:
12
"""
13
Legacy transaction format containing instructions and signatures.
14
"""
15
def __init__(self, instructions: List[Instruction], payer: Optional[Pubkey]):
16
"""
17
Create transaction with instructions and payer.
18
19
Parameters:
20
- instructions: List[Instruction], instructions to execute
21
- payer: Optional[Pubkey], fee payer account
22
"""
23
24
@staticmethod
25
def new_with_payer(instructions: List[Instruction], payer: Optional[Pubkey]) -> 'Transaction':
26
"""
27
Create new transaction with specified payer.
28
29
Parameters:
30
- instructions: List[Instruction], instructions to include
31
- payer: Optional[Pubkey], account that pays transaction fees
32
33
Returns:
34
Transaction object
35
"""
36
37
@staticmethod
38
def new_unsigned(instructions: List[Instruction]) -> 'Transaction':
39
"""
40
Create unsigned transaction without payer.
41
42
Parameters:
43
- instructions: List[Instruction], instructions to include
44
45
Returns:
46
Unsigned Transaction object
47
"""
48
49
@staticmethod
50
def new_with_compiled_instructions(
51
num_required_signatures: int,
52
num_readonly_signed_accounts: int,
53
num_readonly_unsigned_accounts: int,
54
account_keys: List[Pubkey],
55
recent_blockhash: Hash,
56
instructions: List[CompiledInstruction]
57
) -> 'Transaction':
58
"""
59
Create transaction from pre-compiled instructions.
60
61
Parameters:
62
- num_required_signatures: int, number of signatures required
63
- num_readonly_signed_accounts: int, readonly accounts that must sign
64
- num_readonly_unsigned_accounts: int, readonly accounts (no signing)
65
- account_keys: List[Pubkey], all accounts referenced in transaction
66
- recent_blockhash: Hash, recent network blockhash
67
- instructions: List[CompiledInstruction], compiled instructions
68
69
Returns:
70
Transaction object
71
"""
72
73
def sign(self, signers: List[Union[Keypair, Presigner]], recent_blockhash: Hash) -> None:
74
"""
75
Sign transaction with provided signers.
76
77
Parameters:
78
- signers: List of keypairs or presigners
79
- recent_blockhash: Hash, recent network blockhash for replay protection
80
"""
81
82
def partial_sign(self, signers: List[Union[Keypair, Presigner]], recent_blockhash: Hash) -> None:
83
"""
84
Partially sign transaction (allows additional signatures later).
85
86
Parameters:
87
- signers: List of available signers (subset of required)
88
- recent_blockhash: Hash, recent network blockhash
89
"""
90
91
def verify(self) -> bool:
92
"""
93
Verify all signatures in the transaction.
94
95
Returns:
96
bool, True if all signatures are valid
97
"""
98
99
def serialize(self) -> bytes:
100
"""
101
Serialize transaction to bytes for network transmission.
102
103
Returns:
104
bytes, serialized transaction
105
"""
106
107
@classmethod
108
def deserialize(cls, data: bytes) -> 'Transaction':
109
"""
110
Deserialize transaction from bytes.
111
112
Parameters:
113
- data: bytes, serialized transaction data
114
115
Returns:
116
Transaction object
117
"""
118
119
@property
120
def message(self) -> Message:
121
"""Get the transaction message."""
122
123
@property
124
def signatures(self) -> List[Signature]:
125
"""Get transaction signatures."""
126
```
127
128
```python { .api }
129
class VersionedTransaction:
130
"""
131
Versioned transaction supporting address lookup tables for compression.
132
"""
133
def __init__(self, message: VersionedMessage, signatures: List[Signature]):
134
"""
135
Create versioned transaction with message and signatures.
136
137
Parameters:
138
- message: VersionedMessage, transaction message (legacy or v0)
139
- signatures: List[Signature], transaction signatures
140
"""
141
142
@staticmethod
143
def new_unsigned(message: VersionedMessage) -> 'VersionedTransaction':
144
"""
145
Create unsigned versioned transaction.
146
147
Parameters:
148
- message: VersionedMessage, transaction message
149
150
Returns:
151
Unsigned VersionedTransaction
152
"""
153
154
def sign(self, signers: List[Union[Keypair, Presigner]]) -> None:
155
"""
156
Sign versioned transaction.
157
158
Parameters:
159
- signers: List of keypairs or presigners
160
"""
161
162
def serialize(self) -> bytes:
163
"""
164
Serialize versioned transaction to bytes.
165
166
Returns:
167
bytes, serialized transaction
168
"""
169
170
@classmethod
171
def deserialize(cls, data: bytes) -> 'VersionedTransaction':
172
"""
173
Deserialize versioned transaction from bytes.
174
175
Parameters:
176
- data: bytes, serialized transaction data
177
178
Returns:
179
VersionedTransaction object
180
"""
181
182
@property
183
def message(self) -> VersionedMessage:
184
"""Get the transaction message."""
185
186
@property
187
def signatures(self) -> List[Signature]:
188
"""Get transaction signatures."""
189
```
190
191
### Message Construction
192
193
Message structures that contain instructions and account metadata for transaction execution.
194
195
```python { .api }
196
class Message:
197
"""
198
Legacy message format containing instructions and account metadata.
199
"""
200
def __init__(
201
self,
202
header: MessageHeader,
203
account_keys: List[Pubkey],
204
recent_blockhash: Hash,
205
instructions: List[CompiledInstruction]
206
):
207
"""
208
Create message with header, accounts, blockhash, and instructions.
209
210
Parameters:
211
- header: MessageHeader, message metadata
212
- account_keys: List[Pubkey], accounts referenced in message
213
- recent_blockhash: Hash, recent network blockhash
214
- instructions: List[CompiledInstruction], compiled instructions
215
"""
216
217
@staticmethod
218
def new_with_compiled_instructions(
219
num_required_signatures: int,
220
num_readonly_signed_accounts: int,
221
num_readonly_unsigned_accounts: int,
222
account_keys: List[Pubkey],
223
recent_blockhash: Hash,
224
instructions: List[CompiledInstruction]
225
) -> 'Message':
226
"""
227
Create message from compiled instructions and metadata.
228
229
Returns:
230
Message object
231
"""
232
233
@staticmethod
234
def new_with_blockhash(instructions: List[Instruction], payer: Optional[Pubkey], blockhash: Hash) -> 'Message':
235
"""
236
Create message from instructions with specified blockhash.
237
238
Parameters:
239
- instructions: List[Instruction], instructions to compile
240
- payer: Optional[Pubkey], fee payer
241
- blockhash: Hash, recent blockhash
242
243
Returns:
244
Message object
245
"""
246
247
def serialize(self) -> bytes:
248
"""Serialize message to bytes."""
249
250
@classmethod
251
def deserialize(cls, data: bytes) -> 'Message':
252
"""Deserialize message from bytes."""
253
254
def is_key_passed_to_program(self, key_index: int, program_id_index: int) -> bool:
255
"""
256
Check if account key is passed to specific program.
257
258
Parameters:
259
- key_index: int, index of account key
260
- program_id_index: int, index of program ID
261
262
Returns:
263
bool, True if key is passed to program
264
"""
265
266
def is_key_called_as_program(self, key_index: int) -> bool:
267
"""
268
Check if account key is called as program.
269
270
Parameters:
271
- key_index: int, index of account key
272
273
Returns:
274
bool, True if key is invoked as program
275
"""
276
277
@property
278
def header(self) -> MessageHeader:
279
"""Get message header."""
280
281
@property
282
def account_keys(self) -> List[Pubkey]:
283
"""Get account keys."""
284
285
@property
286
def recent_blockhash(self) -> Hash:
287
"""Get recent blockhash."""
288
289
@property
290
def instructions(self) -> List[CompiledInstruction]:
291
"""Get compiled instructions."""
292
```
293
294
```python { .api }
295
class MessageV0:
296
"""
297
Version 0 message with address lookup table support for compression.
298
"""
299
def __init__(
300
self,
301
header: MessageHeader,
302
account_keys: List[Pubkey],
303
recent_blockhash: Hash,
304
instructions: List[CompiledInstruction],
305
address_table_lookups: List[MessageAddressTableLookup]
306
):
307
"""
308
Create v0 message with address lookup tables.
309
310
Parameters:
311
- header: MessageHeader, message metadata
312
- account_keys: List[Pubkey], static account keys
313
- recent_blockhash: Hash, recent network blockhash
314
- instructions: List[CompiledInstruction], compiled instructions
315
- address_table_lookups: List[MessageAddressTableLookup], lookup table references
316
"""
317
318
@staticmethod
319
def try_compile(
320
payer: Pubkey,
321
instructions: List[Instruction],
322
address_lookup_table_accounts: List[AddressLookupTableAccount],
323
recent_blockhash: Hash
324
) -> 'MessageV0':
325
"""
326
Compile instructions into v0 message using lookup tables.
327
328
Parameters:
329
- payer: Pubkey, fee payer account
330
- instructions: List[Instruction], instructions to compile
331
- address_lookup_table_accounts: List[AddressLookupTableAccount], available lookup tables
332
- recent_blockhash: Hash, recent blockhash
333
334
Returns:
335
MessageV0 object
336
337
Raises:
338
- CompileError: if compilation fails
339
"""
340
341
def serialize(self) -> bytes:
342
"""Serialize v0 message to bytes."""
343
344
@classmethod
345
def deserialize(cls, data: bytes) -> 'MessageV0':
346
"""Deserialize v0 message from bytes."""
347
348
@property
349
def address_table_lookups(self) -> List[MessageAddressTableLookup]:
350
"""Get address table lookups."""
351
```
352
353
### Message Components
354
355
Supporting structures for message construction and metadata.
356
357
```python { .api }
358
class MessageHeader:
359
"""
360
Message header containing account signature and readonly counts.
361
"""
362
def __init__(
363
self,
364
num_required_signatures: int,
365
num_readonly_signed_accounts: int,
366
num_readonly_unsigned_accounts: int
367
):
368
"""
369
Create message header with account counts.
370
371
Parameters:
372
- num_required_signatures: int, total signatures required
373
- num_readonly_signed_accounts: int, readonly accounts that must sign
374
- num_readonly_unsigned_accounts: int, readonly accounts (no signing)
375
"""
376
377
@property
378
def num_required_signatures(self) -> int:
379
"""Number of signatures required."""
380
381
@property
382
def num_readonly_signed_accounts(self) -> int:
383
"""Number of readonly accounts that must sign."""
384
385
@property
386
def num_readonly_unsigned_accounts(self) -> int:
387
"""Number of readonly unsigned accounts."""
388
```
389
390
```python { .api }
391
class MessageAddressTableLookup:
392
"""
393
Reference to addresses from an address lookup table.
394
"""
395
def __init__(
396
self,
397
account_key: Pubkey,
398
writable_indexes: List[int],
399
readonly_indexes: List[int]
400
):
401
"""
402
Create address table lookup.
403
404
Parameters:
405
- account_key: Pubkey, address of lookup table account
406
- writable_indexes: List[int], indexes of writable addresses
407
- readonly_indexes: List[int], indexes of readonly addresses
408
"""
409
410
@property
411
def account_key(self) -> Pubkey:
412
"""Lookup table account address."""
413
414
@property
415
def writable_indexes(self) -> List[int]:
416
"""Writable address indexes."""
417
418
@property
419
def readonly_indexes(self) -> List[int]:
420
"""Readonly address indexes."""
421
```
422
423
### Instruction Building
424
425
Individual instruction construction with program invocation and account specification.
426
427
```python { .api }
428
class Instruction:
429
"""
430
Transaction instruction containing program ID, accounts, and data.
431
"""
432
def __init__(self, program_id: Pubkey, accounts: List[AccountMeta], data: bytes):
433
"""
434
Create instruction with program, accounts, and data.
435
436
Parameters:
437
- program_id: Pubkey, program to invoke
438
- accounts: List[AccountMeta], accounts required by instruction
439
- data: bytes, instruction-specific data
440
"""
441
442
@property
443
def program_id(self) -> Pubkey:
444
"""Program to invoke."""
445
446
@property
447
def accounts(self) -> List[AccountMeta]:
448
"""Accounts required by instruction."""
449
450
@property
451
def data(self) -> bytes:
452
"""Instruction-specific data."""
453
```
454
455
```python { .api }
456
class AccountMeta:
457
"""
458
Account metadata specifying permissions and signing requirements.
459
"""
460
def __init__(self, pubkey: Pubkey, is_signer: bool, is_writable: bool):
461
"""
462
Create account metadata.
463
464
Parameters:
465
- pubkey: Pubkey, account address
466
- is_signer: bool, whether account must sign transaction
467
- is_writable: bool, whether account data may be modified
468
"""
469
470
@staticmethod
471
def new(pubkey: Pubkey, is_signer: bool) -> 'AccountMeta':
472
"""
473
Create writable account metadata.
474
475
Parameters:
476
- pubkey: Pubkey, account address
477
- is_signer: bool, whether account must sign
478
479
Returns:
480
Writable AccountMeta
481
"""
482
483
@staticmethod
484
def new_readonly(pubkey: Pubkey, is_signer: bool) -> 'AccountMeta':
485
"""
486
Create readonly account metadata.
487
488
Parameters:
489
- pubkey: Pubkey, account address
490
- is_signer: bool, whether account must sign
491
492
Returns:
493
Readonly AccountMeta
494
"""
495
496
@property
497
def pubkey(self) -> Pubkey:
498
"""Account address."""
499
500
@property
501
def is_signer(self) -> bool:
502
"""Whether account must sign."""
503
504
@property
505
def is_writable(self) -> bool:
506
"""Whether account may be modified."""
507
```
508
509
```python { .api }
510
class CompiledInstruction:
511
"""
512
Compiled instruction with account indexes instead of pubkeys.
513
"""
514
def __init__(self, program_id_index: int, accounts: List[int], data: bytes):
515
"""
516
Create compiled instruction.
517
518
Parameters:
519
- program_id_index: int, index of program ID in account keys
520
- accounts: List[int], indexes of accounts in account keys
521
- data: bytes, instruction data
522
"""
523
524
@property
525
def program_id_index(self) -> int:
526
"""Program ID index in account keys."""
527
528
@property
529
def accounts(self) -> List[int]:
530
"""Account indexes."""
531
532
@property
533
def data(self) -> bytes:
534
"""Instruction data."""
535
```
536
537
### Version Management
538
539
Transaction version handling and type definitions.
540
541
```python { .api }
542
class Legacy:
543
"""Transaction version marker for legacy format."""
544
545
# Type aliases for version handling
546
TransactionVersion = Union[Legacy, int]
547
VersionedMessage = Union[Message, MessageV0]
548
```
549
550
## Usage Examples
551
552
### Basic Transaction Creation
553
554
```python
555
from solders.keypair import Keypair
556
from solders.pubkey import Pubkey
557
from solders.transaction import Transaction
558
from solders.instruction import Instruction, AccountMeta
559
from solders.hash import Hash
560
561
# Create keypairs
562
payer = Keypair()
563
recipient = Keypair()
564
program_id = Pubkey.from_string("11111111111111111111111111111112")
565
566
# Create instruction
567
accounts = [
568
AccountMeta(payer.pubkey(), is_signer=True, is_writable=True),
569
AccountMeta(recipient.pubkey(), is_signer=False, is_writable=True)
570
]
571
instruction = Instruction(program_id, accounts, b"instruction_data")
572
573
# Create and sign transaction
574
transaction = Transaction.new_with_payer([instruction], payer.pubkey())
575
recent_blockhash = Hash.from_string("11111111111111111111111111111112")
576
transaction.sign([payer], recent_blockhash)
577
578
# Serialize for transmission
579
serialized = transaction.serialize()
580
```
581
582
### Versioned Transaction with Lookup Tables
583
584
```python
585
from solders.transaction import VersionedTransaction
586
from solders.message import MessageV0
587
from solders.address_lookup_table_account import AddressLookupTableAccount
588
589
# Assume we have lookup table accounts loaded
590
lookup_tables = [] # List of AddressLookupTableAccount objects
591
592
# Compile v0 message
593
message_v0 = MessageV0.try_compile(
594
payer=payer.pubkey(),
595
instructions=[instruction],
596
address_lookup_table_accounts=lookup_tables,
597
recent_blockhash=recent_blockhash
598
)
599
600
# Create versioned transaction
601
versioned_tx = VersionedTransaction.new_unsigned(message_v0)
602
versioned_tx.sign([payer])
603
```
604
605
### Multi-Instruction Transaction
606
607
```python
608
from solders.system_program import transfer, TransferParams
609
from solders.compute_budget import set_compute_unit_limit
610
611
# Create multiple instructions
612
transfer_ix = transfer(TransferParams(
613
from_pubkey=payer.pubkey(),
614
to_pubkey=recipient.pubkey(),
615
lamports=1000000
616
))
617
618
compute_budget_ix = set_compute_unit_limit(200000)
619
620
# Combine in transaction
621
multi_instruction_tx = Transaction.new_with_payer([
622
compute_budget_ix, # Always include compute budget first
623
transfer_ix
624
], payer.pubkey())
625
626
multi_instruction_tx.sign([payer], recent_blockhash)
627
```
628
629
### Partial Signing Workflow
630
631
```python
632
# Create transaction requiring multiple signers
633
multi_signer_accounts = [
634
AccountMeta(payer.pubkey(), is_signer=True, is_writable=True),
635
AccountMeta(recipient.pubkey(), is_signer=True, is_writable=True), # Also must sign
636
AccountMeta(Pubkey.from_string("11111111111111111111111111111112"), is_signer=False, is_writable=False)
637
]
638
639
multi_signer_ix = Instruction(program_id, multi_signer_accounts, b"multi_signer_data")
640
multi_signer_tx = Transaction.new_with_payer([multi_signer_ix], payer.pubkey())
641
642
# Partial sign with available signers
643
multi_signer_tx.partial_sign([payer], recent_blockhash)
644
645
# Later, add remaining signatures
646
multi_signer_tx.partial_sign([recipient], recent_blockhash)
647
648
# Verify all signatures are present
649
assert multi_signer_tx.verify()
650
```
651
652
### Message Serialization
653
654
```python
655
from solders.message import Message, to_bytes_versioned, from_bytes_versioned
656
657
# Create message directly
658
message = Message.new_with_blockhash([instruction], payer.pubkey(), recent_blockhash)
659
660
# Serialize legacy message
661
message_bytes = message.serialize()
662
663
# Serialize versioned message
664
versioned_bytes = to_bytes_versioned(message)
665
666
# Deserialize versioned message
667
restored_message = from_bytes_versioned(versioned_bytes)
668
```
669
670
## Signer Types
671
672
```python { .api }
673
# Union type for all signer implementations
674
Signer = Union[Keypair, Presigner, NullSigner]
675
```
676
677
Different signer types provide flexibility for various signing scenarios:
678
679
- **Keypair**: Standard Ed25519 keypair for direct signing
680
- **Presigner**: Container for pre-computed signatures
681
- **NullSigner**: No-op signer for simulation and readonly operations
682
683
## Error Handling
684
685
### Transaction Errors
686
687
```python { .api }
688
class SanitizeError(Exception):
689
"""Transaction sanitization errors during construction or validation."""
690
691
class TransactionError(Exception):
692
"""General transaction processing errors."""
693
694
class CompileError(Exception):
695
"""Message compilation errors, especially with address lookup tables."""
696
```
697
698
Common error scenarios:
699
700
```python
701
from solders.transaction import Transaction, SanitizeError
702
from solders.message import MessageV0, CompileError
703
704
try:
705
# Invalid transaction construction
706
invalid_tx = Transaction.new_with_payer([], None) # No payer
707
except SanitizeError as e:
708
print(f"Transaction sanitization failed: {e}")
709
710
try:
711
# Message compilation failure
712
message_v0 = MessageV0.try_compile(
713
payer=payer.pubkey(),
714
instructions=[], # Empty instructions
715
address_lookup_table_accounts=[],
716
recent_blockhash=recent_blockhash
717
)
718
except CompileError as e:
719
print(f"Message compilation failed: {e}")
720
```