Solana has two kinds of transactions: "legacy" transactions represented by the Transaction
class and versioned transactions represented by the VersionedTransaction
class. These examples will focus on versioned transactions which are what you should use if you have a choice.
These examples do not demonstrate sending transactions or fetching the latest blockhash, which you can do with the send_transaction
and get_latest_blockhash
methods in solana-py.
Here we construct a transaction with one instruction - it sends SOL from one wallet to another via the System Program:
from solders.hash import Hash from solders.keypair import Keypair from solders.message import MessageV0 from solders.system_program import TransferParams, transfer from solders.transaction import VersionedTransaction
sender = Keypair() # let's pretend this account actually has SOL to send receiver = Keypair() ix = transfer( TransferParams( from_pubkey=sender.pubkey(), to_pubkey=receiver.pubkey(), lamports=1_000_000 ) ) blockhash = Hash.default() # replace with a real blockhash using get_latest_blockhash msg = MessageV0.try_compile( payer=sender.pubkey(), instructions=[ix], address_lookup_table_accounts=[], recent_blockhash=blockhash, ) tx = VersionedTransaction(msg, [sender])
Suppose you have a transaction that both Alice and Bob need to sign, and Bob doesn't want to give Alice his keypair because last time he did that all his apes got stolen.
One solution is for Alice to create a transaction containing her signature and a dummy signature using the NullSigner
class. She then serializes this transaction and sends it to Bob, who deserializes it and replaces the dummy signature with his own signature:
from solders.hash import Hash from solders.instruction import AccountMeta, Instruction from solders.keypair import Keypair from solders.message import MessageV0, to_bytes_versioned from solders.null_signer import NullSigner from solders.pubkey import Pubkey from solders.transaction import VersionedTransaction
keypair0 = Keypair() keypair1 = Keypair() ix = Instruction( Pubkey.new_unique(), b"", [AccountMeta(keypair1.pubkey(), True, False)] ) message = MessageV0.try_compile(keypair0.pubkey(), [ix], [], Hash.default()) # sign with a real signer and a null signer signers = (keypair0, NullSigner(keypair1.pubkey())) partially_signed = VersionedTransaction(message, signers) serialized = bytes(partially_signed) deserialized = VersionedTransaction.from_bytes(serialized) assert deserialized == partially_signed deserialized_message = deserialized.message # find the null signer in the deserialized transaction keypair1_sig_index = next( i for i, key in enumerate(deserialized_message.account_keys) if key == keypair1.pubkey() ) sigs = deserialized.signatures # replace the null signature with a real signature sigs[keypair1_sig_index] = keypair1.sign_message( to_bytes_versioned(deserialized_message) ) deserialized.signatures = sigs fully_signed = VersionedTransaction(message, [keypair0, keypair1]) assert deserialized.signatures == fully_signed.signatures assert deserialized == fully_signed assert bytes(deserialized) == bytes(fully_signed)