diff --git a/src/solana/rpc/api.py b/src/solana/rpc/api.py index 5a130d0b..67f49cea 100644 --- a/src/solana/rpc/api.py +++ b/src/solana/rpc/api.py @@ -4,8 +4,6 @@ from time import sleep, time from typing import Dict, List, Optional, Sequence, Union -from solders.hash import Hash as Blockhash -from solders.keypair import Keypair from solders.message import VersionedMessage from solders.pubkey import Pubkey from solders.rpc.responses import ( @@ -64,9 +62,9 @@ from solders.transaction import VersionedTransaction from solana.rpc import types -from solana.transaction import Transaction +from solders.transaction import Transaction -from .commitment import Commitment, Finalized +from .commitment import Commitment from .core import ( _COMMITMENT_TO_SOLDERS, RPCException, @@ -407,13 +405,13 @@ def get_fee_for_message( Example: >>> from solders.keypair import Keypair >>> from solders.system_program import TransferParams, transfer - >>> from solana.transaction import Transaction + >>> from solders.message import Message >>> leading_zeros = [0] * 31 >>> sender, receiver = Keypair.from_seed(leading_zeros + [1]), Keypair.from_seed(leading_zeros + [2]) - >>> txn = Transaction().add(transfer(TransferParams( - ... from_pubkey=sender.pubkey(), to_pubkey=receiver.pubkey(), lamports=1000))) + >>> msg = Message([transfer(TransferParams( + ... from_pubkey=sender.pubkey(), to_pubkey=receiver.pubkey(), lamports=1000))]) >>> solana_client = Client("http://localhost:8899") - >>> solana_client.get_fee_for_message(txn.compile_message()).value # doctest: +SKIP + >>> solana_client.get_fee_for_message(msg).value # doctest: +SKIP 5000 """ body = self._get_fee_for_message_body(message, commitment) @@ -999,65 +997,30 @@ def send_raw_transaction(self, txn: bytes, opts: Optional[types.TxOpts] = None) def send_transaction( self, txn: Union[VersionedTransaction, Transaction], - *signers: Keypair, opts: Optional[types.TxOpts] = None, - recent_blockhash: Optional[Blockhash] = None, ) -> SendTransactionResp: """Send a transaction. Args: txn: transaction object. - signers: Signers to sign the transaction. Only supported for legacy Transaction. opts: (optional) Transaction options. - recent_blockhash: (optional) Pass a valid recent blockhash here if you want to - skip fetching the recent blockhash or relying on the cache. - Only supported for legacy Transaction. Example: >>> from solders.keypair import Keypair >>> from solders.pubkey import Pubkey >>> from solana.rpc.api import Client >>> from solders.system_program import TransferParams, transfer - >>> from solana.transaction import Transaction + >>> from solders.message import Message >>> leading_zeros = [0] * 31 >>> sender, receiver = Keypair.from_seed(leading_zeros + [1]), Keypair.from_seed(leading_zeros + [2]) - >>> txn = Transaction().add(transfer(TransferParams( - ... from_pubkey=sender.pubkey(), to_pubkey=receiver.pubkey(), lamports=1000))) - >>> solana_client = Client("http://localhost:8899") - >>> solana_client.send_transaction(txn, sender).value # doctest: +SKIP - Signature( - 1111111111111111111111111111111111111111111111111111111111111111, - ) - """ - if isinstance(txn, VersionedTransaction): - if signers: - msg = "*signers args are not used when sending VersionedTransaction." - raise ValueError(msg) - if recent_blockhash is not None: - msg = "recent_blockhash arg is not used when sending VersionedTransaction." - raise ValueError(msg) - versioned_tx_opts = types.TxOpts(preflight_commitment=self._commitment) if opts is None else opts - return self.send_raw_transaction(bytes(txn), opts=versioned_tx_opts) - last_valid_block_height = None - if recent_blockhash is None: - blockhash_resp = self.get_latest_blockhash(Finalized) - recent_blockhash = self.parse_recent_blockhash(blockhash_resp) - last_valid_block_height = blockhash_resp.value.last_valid_block_height - - txn.recent_blockhash = recent_blockhash - - txn.sign(*signers) - opts_to_use = ( - types.TxOpts( - preflight_commitment=self._commitment, - last_valid_block_height=last_valid_block_height, - ) - if opts is None - else opts - ) - - txn_resp = self.send_raw_transaction(txn.serialize(), opts=opts_to_use) - return txn_resp + >>> ixns = [transfer(TransferParams( + ... from_pubkey=sender.pubkey(), to_pubkey=receiver.pubkey(), lamports=1000))] + >>> msg = Message(ixns, sender.pubkey()) + >>> client = Client("http://localhost:8899") + >>> client.send_transaction(Transaction([sender], msg, client.get_latest_blockhash()).value.blockhash) # doctest: +SKIP + """ # noqa: E501 + tx_opts = types.TxOpts(preflight_commitment=self._commitment) if opts is None else opts + return self.send_raw_transaction(bytes(txn), opts=tx_opts) def simulate_transaction( self, @@ -1074,6 +1037,7 @@ def simulate_transaction( commitment: Bank state to query. It can be either "finalized", "confirmed" or "processed". Example: + >>> from solders.transaction import Transaction >>> solana_client = Client("http://localhost:8899") >>> full_signed_tx_hex = ( ... '01b3795ccfaac3eee838bb05c3b8284122c18acedcd645c914fe8e178c3b62640d8616d061cc818b26cab8ecf3855ecc' @@ -1082,7 +1046,7 @@ def simulate_transaction( ... '000000000000000000000000000000000000000000839618f701ba7e9ba27ae59825dd6d6bb66d14f6d5d0eae215161d7' ... '1851a106901020200010c0200000040420f0000000000' ... ) - >>> tx = Transaction.deserialize(bytes.fromhex(full_signed_tx_hex)) + >>> tx = Transaction.from_bytes(bytes.fromhex(full_signed_tx_hex)) >>> solana_client.simulate_transaction(tx).value.logs # doctest: +SKIP ['BPF program 83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri success'] """ diff --git a/src/solana/rpc/async_api.py b/src/solana/rpc/async_api.py index 62ad9c49..c4aa4bba 100644 --- a/src/solana/rpc/async_api.py +++ b/src/solana/rpc/async_api.py @@ -3,8 +3,6 @@ from time import time from typing import Dict, List, Optional, Sequence, Union -from solders.hash import Hash as Blockhash -from solders.keypair import Keypair from solders.message import VersionedMessage from solders.pubkey import Pubkey from solders.rpc.responses import ( @@ -62,9 +60,9 @@ from solders.transaction import VersionedTransaction from solana.rpc import types -from solana.transaction import Transaction +from solders.transaction import Transaction -from .commitment import Commitment, Finalized +from .commitment import Commitment from .core import ( _COMMITMENT_TO_SOLDERS, TransactionExpiredBlockheightExceededError, @@ -419,13 +417,13 @@ async def get_fee_for_message( Example: >>> from solders.keypair import Keypair >>> from solders.system_program import TransferParams, transfer - >>> from solana.transaction import Transaction + >>> from solders.message import Message >>> leading_zeros = [0] * 31 >>> sender, receiver = Keypair.from_seed(leading_zeros + [1]), Keypair.from_seed(leading_zeros + [2]) - >>> txn = Transaction().add(transfer(TransferParams( - ... from_pubkey=sender.pubkey(), to_pubkey=receiver.pubkey(), lamports=1000))) + >>> msg = Message([transfer(TransferParams( + ... from_pubkey=sender.pubkey(), to_pubkey=receiver.pubkey(), lamports=1000))]) >>> solana_client = AsyncClient("http://localhost:8899") - >>> (await solana_client.get_fee_for_message(txn.compile_message())).value # doctest: +SKIP + >>> (await solana_client.get_fee_for_message(msg)).value # doctest: +SKIP 5000 """ body = self._get_fee_for_message_body(message, commitment) @@ -1012,62 +1010,28 @@ async def send_raw_transaction(self, txn: bytes, opts: Optional[types.TxOpts] = async def send_transaction( self, txn: Union[VersionedTransaction, Transaction], - *signers: Keypair, opts: Optional[types.TxOpts] = None, - recent_blockhash: Optional[Blockhash] = None, ) -> SendTransactionResp: """Send a transaction. Args: txn: transaction object. - signers: Signers to sign the transaction. Only supported for legacy Transaction. opts: (optional) Transaction options. - recent_blockhash: (optional) Pass a valid recent blockhash here if you want to - skip fetching the recent blockhash or relying on the cache. - Only supported for legacy Transaction. Example: >>> from solders.keypair import Keypair >>> from solders.system_program import TransferParams, transfer - >>> from solana.transaction import Transaction + >>> from solders.message import Message + >>> from solders.transaction import Transaction >>> leading_zeros = [0] * 31 >>> sender, receiver = Keypair.from_seed(leading_zeros + [1]), Keypair.from_seed(leading_zeros + [2]) - >>> txn = Transaction().add(transfer(TransferParams( - ... from_pubkey=sender.pubkey(), to_pubkey=receiver.pubkey(), lamports=1000))) - >>> solana_client = AsyncClient("http://localhost:8899") - >>> (await solana_client.send_transaction(txn, sender)).value # doctest: +SKIP - Signature( - 1111111111111111111111111111111111111111111111111111111111111111, - ) - """ - if isinstance(txn, VersionedTransaction): - if signers: - msg = "*signers args are not used when sending VersionedTransaction." - raise ValueError(msg) - if recent_blockhash is not None: - msg = "recent_blockhash arg is not used when sending VersionedTransaction." - raise ValueError(msg) - versioned_tx_opts = types.TxOpts(preflight_commitment=self._commitment) if opts is None else opts - return await self.send_raw_transaction(bytes(txn), opts=versioned_tx_opts) - last_valid_block_height = None - if recent_blockhash is None: - blockhash_resp = await self.get_latest_blockhash(Finalized) - recent_blockhash = self.parse_recent_blockhash(blockhash_resp) - last_valid_block_height = blockhash_resp.value.last_valid_block_height - - txn.recent_blockhash = recent_blockhash - - txn.sign(*signers) - opts_to_use = ( - types.TxOpts( - preflight_commitment=self._commitment, - last_valid_block_height=last_valid_block_height, - ) - if opts is None - else opts - ) - txn_resp = await self.send_raw_transaction(txn.serialize(), opts=opts_to_use) - return txn_resp + >>> ixns = [transfer(TransferParams( + ... from_pubkey=sender.pubkey(), to_pubkey=receiver.pubkey(), lamports=1000))] + >>> msg = Message(ixns, sender.pubkey()) + >>> client = AsyncClient("http://localhost:8899") + >>> (await client.send_transaction(Transaction([sender], msg, (await client.get_latest_blockhash()).value.blockhash))) # doctest: +SKIP + """ # noqa: E501 + return await self.send_raw_transaction(bytes(txn), opts=opts) async def simulate_transaction( self, @@ -1092,7 +1056,7 @@ async def simulate_transaction( ... '000000000000000000000000000000000000000000839618f701ba7e9ba27ae59825dd6d6bb66d14f6d5d0eae215161d7' ... '1851a106901020200010c0200000040420f0000000000' ... ) - >>> tx = Transaction.deserialize(bytes.fromhex(full_signed_tx_hex)) + >>> tx = Transaction.from_bytes(bytes.fromhex(full_signed_tx_hex)) >>> (await solana_client.simulate_transaction(tx)).value.logs # doctest: +SKIP ['BPF program 83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri success'] """ diff --git a/src/solana/rpc/core.py b/src/solana/rpc/core.py index 1f42d795..c84d810b 100644 --- a/src/solana/rpc/core.py +++ b/src/solana/rpc/core.py @@ -81,7 +81,7 @@ from solders.transaction_status import UiTransactionEncoding from solana.rpc import types -from solana.transaction import Transaction +from solders.transaction import Transaction from .commitment import Commitment, Confirmed, Finalized, Processed @@ -499,9 +499,7 @@ def _simulate_transaction_body( commitment_to_use = _COMMITMENT_TO_SOLDERS[commitment or self._commitment] config = RpcSimulateTransactionConfig(sig_verify=sig_verify, commitment=commitment_to_use) if isinstance(txn, Transaction): - if txn.recent_blockhash is None: - raise ValueError("transaction must have a valid blockhash") - return SimulateLegacyTransaction(txn.to_solders(), config) + return SimulateLegacyTransaction(txn, config) return SimulateVersionedTransaction(txn, config) @staticmethod diff --git a/src/solana/transaction.py b/src/solana/transaction.py deleted file mode 100644 index b7525262..00000000 --- a/src/solana/transaction.py +++ /dev/null @@ -1,347 +0,0 @@ -"""Library to package an atomic sequence of instructions to a transaction.""" -from __future__ import annotations - -from typing import Any, List, NamedTuple, Optional, Sequence, Tuple, Union - -from solders.hash import Hash as Blockhash -from solders.instruction import AccountMeta, Instruction -from solders.keypair import Keypair -from solders.message import Message -from solders.message import Message as SoldersMessage -from solders.presigner import Presigner -from solders.pubkey import Pubkey -from solders.signature import Signature -from solders.transaction import Transaction as SoldersTx -from solders.transaction import TransactionError - -PACKET_DATA_SIZE = 1280 - 40 - 8 -"""Constant for maximum over-the-wire size of a Transaction.""" - - -class NonceInformation(NamedTuple): - """NonceInformation to be used to build a Transaction.""" - - nonce: Blockhash - """The current Nonce blockhash.""" - nonce_instruction: Instruction - """AdvanceNonceAccount Instruction.""" - - -def _build_solders_tx( - recent_blockhash: Optional[Blockhash] = None, - nonce_info: Optional[NonceInformation] = None, - fee_payer: Optional[Pubkey] = None, - instructions: Optional[Sequence[Instruction]] = None, -) -> SoldersTx: - core_instructions = [] if instructions is None else instructions - underlying_instructions = ( - core_instructions if nonce_info is None else [nonce_info.nonce_instruction, *core_instructions] - ) - underlying_blockhash: Optional[Blockhash] - if nonce_info is not None: - underlying_blockhash = nonce_info.nonce - elif recent_blockhash is not None: - underlying_blockhash = recent_blockhash - else: - underlying_blockhash = None - underlying_fee_payer = None if fee_payer is None else fee_payer - underlying_blockhash = Blockhash.default() if underlying_blockhash is None else underlying_blockhash - msg = SoldersMessage.new_with_blockhash(underlying_instructions, underlying_fee_payer, underlying_blockhash) - return SoldersTx.new_unsigned(msg) - - -def _decompile_instructions(msg: SoldersMessage) -> List[Instruction]: - account_keys = msg.account_keys - decompiled_instructions: List[Instruction] = [] - for compiled_ix in msg.instructions: - program_id = account_keys[compiled_ix.program_id_index] - account_metas = [ - AccountMeta( - account_keys[idx], - is_signer=msg.is_signer(idx), - is_writable=msg.is_writable(idx), - ) - for idx in compiled_ix.accounts - ] - decompiled_instructions.append(Instruction(program_id, compiled_ix.data, account_metas)) - return decompiled_instructions - - -class Transaction: - """Transaction class to represent an atomic transaction. - - Args: - recent_blockhash: A recent transaction id. - nonce_info: Nonce information. - If populated, transaction will use a durable Nonce hash instead of a `recent_blockhash`. - fee_payer: The transaction fee payer. - instructions: The instructions to be executed in this transaction. - """ - - # Default (empty) signature - __DEFAULT_SIG = bytes(64) - - def __init__( - self, - recent_blockhash: Optional[Blockhash] = None, - nonce_info: Optional[NonceInformation] = None, - fee_payer: Optional[Pubkey] = None, - instructions: Optional[Sequence[Instruction]] = None, - ) -> None: - """Init transaction object.""" - self._solders = _build_solders_tx( - recent_blockhash=recent_blockhash, - nonce_info=nonce_info, - fee_payer=fee_payer, - instructions=instructions, - ) - - @classmethod - def from_solders(cls, txn: SoldersTx) -> Transaction: - """Convert from a `solders` transaction. - - Args: - txn: The `solders` transaction. - - Returns: - The `solana-py` transaction. - """ - new_tx = cls() - new_tx._solders = txn - return new_tx - - def to_solders(self) -> SoldersTx: - """Convert to a `solders` transaction. - - Returns: - The `solders` transaction. - """ - return self._solders - - def __eq__(self, other: Any) -> bool: - """Equality defintion for Transactions.""" - if not isinstance(other, Transaction): - return False - return self.to_solders() == other.to_solders() - - @property - def recent_blockhash(self) -> Optional[Blockhash]: - """Optional[Blockhash]: The blockhash assigned to this transaction.""" - return self._solders.message.recent_blockhash - - @recent_blockhash.setter - def recent_blockhash(self, blockhash: Optional[Blockhash]) -> None: # noqa: D102 - self._solders = _build_solders_tx( - recent_blockhash=blockhash, - nonce_info=None, - fee_payer=self.fee_payer, - instructions=self.instructions, - ) - - @property - def fee_payer(self) -> Optional[Pubkey]: - """Optional[Pubkey]: The transaction fee payer.""" - account_keys = self._solders.message.account_keys - return account_keys[0] if account_keys else None - - @fee_payer.setter - def fee_payer(self, payer: Optional[Pubkey]) -> None: # noqa: D102 - self._solders = _build_solders_tx( - recent_blockhash=self.recent_blockhash, - nonce_info=None, - fee_payer=payer, - instructions=self.instructions, - ) - - @property - def instructions(self) -> Tuple[Instruction, ...]: - """Tuple[Instruction]: The instructions contained in this transaction.""" - msg = self._solders.message - return tuple(_decompile_instructions(msg)) - - @instructions.setter - def instructions(self, ixns: Sequence[Instruction]) -> None: # noqa: D102 - self._solders = _build_solders_tx( - recent_blockhash=self.recent_blockhash, - nonce_info=None, - fee_payer=self.fee_payer, - instructions=ixns, - ) - - @property - def signatures(self) -> Tuple[Signature, ...]: - """Tuple[Signature]: Signatures for the transaction.""" - return tuple(self._solders.signatures) - - def signature(self) -> Signature: - """The first (payer) Transaction signature. - - Returns: - The payer signature. - """ - return self._solders.signatures[0] - - def add(self, *args: Union[Transaction, Instruction]) -> Transaction: - """Add one or more instructions to this Transaction. - - Args: - *args: The instructions to add to this Transaction. - If a `Transaction` is passsed, the instructions will be extracted from it. - - Returns: - The transaction with the added instructions. - """ - for arg in args: - if isinstance(arg, Transaction): - self.instructions = self.instructions + arg.instructions - elif isinstance(arg, Instruction): - self.instructions = (*self.instructions, arg) - else: - raise ValueError("invalid instruction:", arg) - - return self - - def compile_message(self) -> Message: # pylint: disable=too-many-locals - """Compile transaction data. - - Returns: - The compiled message. - """ - return self._solders.message - - def serialize_message(self) -> bytes: - """Get raw transaction data that need to be covered by signatures. - - Returns: - The serialized message. - """ - return bytes(self.compile_message()) - - def sign_partial(self, *partial_signers: Keypair) -> None: - """Partially sign a Transaction with the specified keypairs. - - All the caveats from the `sign` method apply to `sign_partial` - """ - self._solders.partial_sign(partial_signers, self._solders.message.recent_blockhash) - - def sign(self, *signers: Keypair) -> None: - """Sign the Transaction with the specified accounts. - - Multiple signatures may be applied to a Transaction. The first signature - is considered "primary" and is used when testing for Transaction confirmation. - - Transaction fields should not be modified after the first call to `sign`, - as doing so may invalidate the signature and cause the Transaction to be - rejected. - - The Transaction must be assigned a valid `recent_blockhash` before invoking this method. - """ - self._solders.sign(signers, self._solders.message.recent_blockhash) - - def add_signature(self, pubkey: Pubkey, signature: Signature) -> None: - """Add an externally created signature to a transaction. - - Args: - pubkey: The public key that created the signature. - signature: The signature to add. - """ - presigner = Presigner(pubkey, signature) - self._solders.partial_sign([presigner], self._solders.message.recent_blockhash) - - def verify_signatures(self) -> bool: - """Verify signatures of a complete, signed Transaction. - - Returns: - a bool indicating if the signatures are correct or not. - """ - try: - self._solders.verify() - except TransactionError: - return False - return True - - def serialize(self, verify_signatures: bool = True) -> bytes: - """Serialize the Transaction in the wire format. - - The Transaction must have a valid `signature` before invoking this method. - verify_signatures can be added if the signature does not require to be verified. - - Args: - verify_signatures: a bool indicating to verify the signature or not. Defaults to True - - Example: - >>> from solders.keypair import Keypair - >>> from solders.pubkey import Pubkey - >>> from solders.hash import Hash - >>> from solders.system_program import transfer, TransferParams - >>> leading_zeros = [0] * 31 - >>> seed = bytes(leading_zeros + [1]) - >>> sender, receiver = Keypair.from_seed(seed), Pubkey(leading_zeros + [2]) - >>> transfer_tx = Transaction().add(transfer(TransferParams(from_pubkey=sender.pubkey(), to_pubkey=receiver, lamports=1000))) - >>> transfer_tx.recent_blockhash = Hash(leading_zeros + [3]) - >>> transfer_tx.sign(sender) - >>> transfer_tx.serialize().hex() - '019d53be8af3a7c30f86c1092d2c3ea61d270c0cfa275a23ba504674c8fbbb724827b23b42dc8e08019e23120f1b6f40f9799355ce54185b4415be37ca2cee6e0e010001034cb5abf6ad79fbf5abbccafcc269d85cd2651ed4b885b5869f241aedf0a5ba2900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000301020200010c02000000e803000000000000' - - Returns: - The serialized transaction. - """ # noqa: E501 pylint: disable=line-too-long - if self.signatures == [Signature.default() for sig in self.signatures]: - raise AttributeError("transaction has not been signed") - - if verify_signatures and not self.verify_signatures(): - raise AttributeError("transaction has not been signed correctly") - - return bytes(self._solders) - - @classmethod - def deserialize(cls, raw_transaction: bytes) -> Transaction: - """Parse a wire transaction into a Transaction object. - - Example: - >>> raw_transaction = bytes.fromhex( - ... '019d53be8af3a7c30f86c1092d2c3ea61d270c0cfa2' - ... '75a23ba504674c8fbbb724827b23b42dc8e08019e23' - ... '120f1b6f40f9799355ce54185b4415be37ca2cee6e0' - ... 'e010001034cb5abf6ad79fbf5abbccafcc269d85cd2' - ... '651ed4b885b5869f241aedf0a5ba290000000000000' - ... '0000000000000000000000000000000000000000000' - ... '0000000200000000000000000000000000000000000' - ... '0000000000000000000000000000000000000000000' - ... '0000000000000000000000000000000000000000000' - ... '000000301020200010c02000000e803000000000000' - ... ) - >>> type(Transaction.deserialize(raw_transaction)) - - - Returns: - The deserialized transaction. - """ - return cls.from_solders(SoldersTx.from_bytes(raw_transaction)) - - @classmethod - def populate(cls, message: Message, signatures: List[Signature]) -> Transaction: - """Populate Transaction object from message and signatures. - - Example: - >>> raw_message = bytes.fromhex( - ... '0200030500000000000000000000000000000000000000000000' - ... '0000000000000000000100000000000000000000000000000000' - ... '0000000000000000000000000000000200000000000000000000' - ... '0000000000000000000000000000000000000000000300000000' - ... '0000000000000000000000000000000000000000000000000000' - ... '0004000000000000000000000000000000000000000000000000' - ... '0000000000000005c49ae77603782054f17a9decea43b444eba0' - ... 'edb12c6f1d31c6e0e4a84bf052eb010403010203050909090909' - ... ) - >>> from solders.message import Message - >>> from solders.signature import Signature - >>> msg = Message.from_bytes(raw_message) - >>> signatures = [Signature(bytes([1] * Signature.LENGTH)), Signature(bytes([2] * Signature.LENGTH))] - >>> type(Transaction.populate(msg, signatures)) - - - Returns: - The populated transaction. - """ - return cls.from_solders(SoldersTx.populate(message, signatures)) diff --git a/src/spl/token/async_client.py b/src/spl/token/async_client.py index 640d67a9..1ec09626 100644 --- a/src/spl/token/async_client.py +++ b/src/spl/token/async_client.py @@ -206,7 +206,10 @@ async def create_mint( # Allocate memory for the account balance_needed = await AsyncToken.get_min_balance_rent_for_exempt_for_mint(conn) # Construct transaction - token, txn, payer, mint_account, opts = _TokenCore._create_mint_args( + recent_blockhash_to_use = ( + (await conn.get_latest_blockhash()).value.blockhash if recent_blockhash is None else recent_blockhash + ) + token, txn, opts = _TokenCore._create_mint_args( conn, payer, mint_authority, @@ -217,9 +220,10 @@ async def create_mint( balance_needed, cls, conn.commitment, + recent_blockhash_to_use, ) # Send the two instructions - await conn.send_transaction(txn, payer, mint_account, opts=opts, recent_blockhash=recent_blockhash) + await conn.send_transaction(txn, opts=opts) return cast(AsyncToken, token) async def create_account( @@ -244,11 +248,14 @@ async def create_account( or until the transaction is confirmed. """ balance_needed = await AsyncToken.get_min_balance_rent_for_exempt_for_account(self._conn) - new_account_pk, txn, payer, new_account, opts = self._create_account_args( - owner, skip_confirmation, balance_needed, self._conn.commitment + recent_blockhash_to_use = ( + (await self._conn.get_latest_blockhash()).value.blockhash if recent_blockhash is None else recent_blockhash + ) + new_account_pk, txn, opts = self._create_account_args( + owner, skip_confirmation, balance_needed, self._conn.commitment, recent_blockhash_to_use ) # Send the two instructions - await self._conn.send_transaction(txn, payer, new_account, opts=opts, recent_blockhash=recent_blockhash) + await self._conn.send_transaction(txn, opts=opts) return new_account_pk async def create_associated_token_account( @@ -271,10 +278,13 @@ async def create_associated_token_account( or until the transaction is confirmed. """ # Construct transaction + recent_blockhash_to_use = ( + (await self._conn.get_latest_blockhash()).value.blockhash if recent_blockhash is None else recent_blockhash + ) public_key, txn, payer, opts = self._create_associated_token_account_args( - owner, skip_confirmation, self._conn.commitment + owner, skip_confirmation, self._conn.commitment, recent_blockhash_to_use ) - await self._conn.send_transaction(txn, payer, opts=opts, recent_blockhash=recent_blockhash) + await self._conn.send_transaction(txn, opts=opts) return public_key @staticmethod @@ -306,6 +316,9 @@ async def create_wrapped_native_account( """ # Allocate memory for the account balance_needed = await AsyncToken.get_min_balance_rent_for_exempt_for_account(conn) + recent_blockhash_to_use = ( + (await conn.get_latest_blockhash()).value.blockhash if recent_blockhash is None else recent_blockhash + ) (new_account_public_key, txn, payer, new_account, opts,) = _TokenCore._create_wrapped_native_account_args( program_id, owner, @@ -314,8 +327,9 @@ async def create_wrapped_native_account( skip_confirmation, balance_needed, conn.commitment, + recent_blockhash_to_use, ) - await conn.send_transaction(txn, payer, new_account, opts=opts, recent_blockhash=recent_blockhash) + await conn.send_transaction(txn, opts=opts) return new_account_public_key async def create_multisig( @@ -337,9 +351,12 @@ async def create_multisig( Public key of the new multisig account. """ balance_needed = await AsyncToken.get_min_balance_rent_for_exempt_for_multisig(self._conn) - txn, payer, multisig = self._create_multisig_args(m, multi_signers, balance_needed) + recent_blockhash_to_use = ( + (await self._conn.get_latest_blockhash()).value.blockhash if recent_blockhash is None else recent_blockhash + ) + txn, multisig = self._create_multisig_args(m, multi_signers, balance_needed, recent_blockhash_to_use) opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts - await self._conn.send_transaction(txn, payer, multisig, opts=opts_to_use, recent_blockhash=recent_blockhash) + await self._conn.send_transaction(txn, opts=opts_to_use) return multisig.pubkey() async def get_mint_info(self) -> MintInfo: @@ -374,8 +391,13 @@ async def transfer( recent_blockhash: (optional) a prefetched Blockhash for the transaction. """ opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts - txn, signers, opts = self._transfer_args(source, dest, owner, amount, multi_signers, opts_to_use) - return await self._conn.send_transaction(txn, *signers, opts=opts, recent_blockhash=recent_blockhash) + recent_blockhash_to_use = ( + (await self._conn.get_latest_blockhash()).value.blockhash if recent_blockhash is None else recent_blockhash + ) + txn, opts = self._transfer_args( + source, dest, owner, amount, multi_signers, opts_to_use, recent_blockhash_to_use + ) + return await self._conn.send_transaction(txn, opts=opts) async def approve( self, @@ -399,8 +421,13 @@ async def approve( recent_blockhash: (optional) a prefetched Blockhash for the transaction. """ opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts - txn, payer, signers, opts = self._approve_args(source, delegate, owner, amount, multi_signers, opts_to_use) - return await self._conn.send_transaction(txn, payer, *signers, opts=opts, recent_blockhash=recent_blockhash) + recent_blockhash_to_use = ( + (await self._conn.get_latest_blockhash()).value.blockhash if recent_blockhash is None else recent_blockhash + ) + txn, payer, signers, opts = self._approve_args( + source, delegate, owner, amount, multi_signers, opts_to_use, recent_blockhash_to_use + ) + return await self._conn.send_transaction(txn, opts=opts) async def revoke( self, @@ -420,8 +447,13 @@ async def revoke( recent_blockhash: (optional) a prefetched Blockhash for the transaction. """ opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts - txn, payer, signers, opts = self._revoke_args(account, owner, multi_signers, opts_to_use) - return await self._conn.send_transaction(txn, payer, *signers, opts=opts, recent_blockhash=recent_blockhash) + recent_blockhash_to_use = ( + (await self._conn.get_latest_blockhash()).value.blockhash if recent_blockhash is None else recent_blockhash + ) + txn, payer, signers, opts = self._revoke_args( + account, owner, multi_signers, opts_to_use, recent_blockhash_to_use + ) + return await self._conn.send_transaction(txn, opts=opts) async def set_authority( self, @@ -445,6 +477,9 @@ async def set_authority( recent_blockhash: (optional) a prefetched Blockhash for the transaction. """ opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts + recent_blockhash_to_use = ( + (await self._conn.get_latest_blockhash()).value.blockhash if recent_blockhash is None else recent_blockhash + ) txn, payer, signers, opts = self._set_authority_args( account, current_authority, @@ -452,8 +487,9 @@ async def set_authority( new_authority, multi_signers, opts_to_use, + recent_blockhash_to_use, ) - return await self._conn.send_transaction(txn, payer, *signers, opts=opts, recent_blockhash=recent_blockhash) + return await self._conn.send_transaction(txn, opts=opts) async def mint_to( self, @@ -478,8 +514,13 @@ async def mint_to( or until the transaction is confirmed. """ opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts - txn, signers, opts = self._mint_to_args(dest, mint_authority, amount, multi_signers, opts_to_use) - return await self._conn.send_transaction(txn, *signers, opts=opts, recent_blockhash=recent_blockhash) + recent_blockhash_to_use = ( + (await self._conn.get_latest_blockhash()).value.blockhash if recent_blockhash is None else recent_blockhash + ) + txn, opts = self._mint_to_args( + dest, mint_authority, amount, multi_signers, opts_to_use, recent_blockhash_to_use + ) + return await self._conn.send_transaction(txn, opts=opts) async def burn( self, @@ -501,8 +542,11 @@ async def burn( recent_blockhash: (optional) a prefetched Blockhash for the transaction. """ opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts - txn, signers, opts = self._burn_args(account, owner, amount, multi_signers, opts_to_use) - return await self._conn.send_transaction(txn, *signers, opts=opts, recent_blockhash=recent_blockhash) + recent_blockhash_to_use = ( + (await self._conn.get_latest_blockhash()).value.blockhash if recent_blockhash is None else recent_blockhash + ) + txn, opts = self._burn_args(account, owner, amount, multi_signers, opts_to_use, recent_blockhash_to_use) + return await self._conn.send_transaction(txn, opts=opts) async def close_account( self, @@ -524,8 +568,13 @@ async def close_account( recent_blockhash: (optional) a prefetched Blockhash for the transaction. """ opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts - txn, signers, opts = self._close_account_args(account, dest, authority, multi_signers, opts_to_use) - return await self._conn.send_transaction(txn, *signers, opts=opts, recent_blockhash=recent_blockhash) + recent_blockhash_to_use = ( + (await self._conn.get_latest_blockhash()).value.blockhash if recent_blockhash is None else recent_blockhash + ) + txn, opts = self._close_account_args( + account, dest, authority, multi_signers, opts_to_use, recent_blockhash_to_use + ) + return await self._conn.send_transaction(txn, opts=opts) async def freeze_account( self, @@ -545,8 +594,11 @@ async def freeze_account( recent_blockhash: (optional) a prefetched Blockhash for the transaction. """ opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts - txn, signers, opts = self._freeze_account_args(account, authority, multi_signers, opts_to_use) - return await self._conn.send_transaction(txn, *signers, opts=opts, recent_blockhash=recent_blockhash) + recent_blockhash_to_use = ( + (await self._conn.get_latest_blockhash()).value.blockhash if recent_blockhash is None else recent_blockhash + ) + txn, opts = self._freeze_account_args(account, authority, multi_signers, opts_to_use, recent_blockhash_to_use) + return await self._conn.send_transaction(txn, opts=opts) async def thaw_account( self, @@ -566,8 +618,11 @@ async def thaw_account( recent_blockhash: (optional) a prefetched Blockhash for the transaction. """ opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts - txn, signers, opts = self._thaw_account_args(account, authority, multi_signers, opts_to_use) - return await self._conn.send_transaction(txn, *signers, opts=opts, recent_blockhash=recent_blockhash) + recent_blockhash_to_use = ( + (await self._conn.get_latest_blockhash()).value.blockhash if recent_blockhash is None else recent_blockhash + ) + txn, opts = self._thaw_account_args(account, authority, multi_signers, opts_to_use, recent_blockhash_to_use) + return await self._conn.send_transaction(txn, opts=opts) async def transfer_checked( self, @@ -593,10 +648,13 @@ async def transfer_checked( recent_blockhash: (optional) a prefetched Blockhash for the transaction. """ opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts - txn, signers, opts = self._transfer_checked_args( - source, dest, owner, amount, decimals, multi_signers, opts_to_use + recent_blockhash_to_use = ( + (await self._conn.get_latest_blockhash()).value.blockhash if recent_blockhash is None else recent_blockhash ) - return await self._conn.send_transaction(txn, *signers, opts=opts, recent_blockhash=recent_blockhash) + txn, opts = self._transfer_checked_args( + source, dest, owner, amount, decimals, multi_signers, opts_to_use, recent_blockhash_to_use + ) + return await self._conn.send_transaction(txn, opts=opts) async def approve_checked( self, @@ -624,10 +682,13 @@ async def approve_checked( recent_blockhash (optional): A prefetched blockhash for the transaction. """ opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts - txn, payer, signers, opts = self._approve_checked_args( - source, delegate, owner, amount, decimals, multi_signers, opts_to_use + recent_blockhash_to_use = ( + (await self._conn.get_latest_blockhash()).value.blockhash if recent_blockhash is None else recent_blockhash + ) + txn, opts = self._approve_checked_args( + source, delegate, owner, amount, decimals, multi_signers, opts_to_use, recent_blockhash_to_use ) - return await self._conn.send_transaction(txn, payer, *signers, opts=opts, recent_blockhash=recent_blockhash) + return await self._conn.send_transaction(txn, opts=opts) async def mint_to_checked( self, @@ -651,10 +712,13 @@ async def mint_to_checked( recent_blockhash (optional): A prefetched blockhash for the transaction. """ opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts - txn, signers, opts = self._mint_to_checked_args( - dest, mint_authority, amount, decimals, multi_signers, opts_to_use + recent_blockhash_to_use = ( + (await self._conn.get_latest_blockhash()).value.blockhash if recent_blockhash is None else recent_blockhash ) - return await self._conn.send_transaction(txn, *signers, opts=opts, recent_blockhash=recent_blockhash) + txn, opts = self._mint_to_checked_args( + dest, mint_authority, amount, decimals, multi_signers, opts_to_use, recent_blockhash_to_use + ) + return await self._conn.send_transaction(txn, opts=opts) async def burn_checked( self, @@ -678,5 +742,16 @@ async def burn_checked( recent_blockhash: (optional) a prefetched Blockhash for the transaction. """ opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts - txn, signers, opts = self._burn_checked_args(account, owner, amount, decimals, multi_signers, opts_to_use) - return await self._conn.send_transaction(txn, *signers, opts=opts, recent_blockhash=recent_blockhash) + recent_blockhash_to_use = ( + (await self._conn.get_latest_blockhash()).value.blockhash if recent_blockhash is None else recent_blockhash + ) + txn, opts = self._burn_checked_args( + account, + owner, + amount, + decimals, + multi_signers, + opts_to_use, + recent_blockhash_to_use, + ) + return await self._conn.send_transaction(txn, opts=opts) diff --git a/src/spl/token/client.py b/src/spl/token/client.py index e44f5309..6b372587 100644 --- a/src/spl/token/client.py +++ b/src/spl/token/client.py @@ -205,7 +205,10 @@ def create_mint( # Allocate memory for the account balance_needed = Token.get_min_balance_rent_for_exempt_for_mint(conn) # Construct transaction - token, txn, payer, mint_account, opts = _TokenCore._create_mint_args( + recent_blockhash_to_use = ( + conn.get_latest_blockhash().value.blockhash if recent_blockhash is None else recent_blockhash + ) + token, txn, opts = _TokenCore._create_mint_args( conn, payer, mint_authority, @@ -216,9 +219,10 @@ def create_mint( balance_needed, cls, conn.commitment, + recent_blockhash_to_use, ) # Send the two instructions - conn.send_transaction(txn, payer, mint_account, opts=opts, recent_blockhash=recent_blockhash) + conn.send_transaction(txn, opts=opts) return cast(Token, token) def create_account( @@ -243,11 +247,14 @@ def create_account( or until the transaction is confirmed. """ balance_needed = Token.get_min_balance_rent_for_exempt_for_account(self._conn) - new_account_pk, txn, payer, new_account, opts = self._create_account_args( - owner, skip_confirmation, balance_needed, self._conn.commitment + recent_blockhash_to_use = ( + self._conn.get_latest_blockhash().value.blockhash if recent_blockhash is None else recent_blockhash + ) + new_account_pk, txn, opts = self._create_account_args( + owner, skip_confirmation, balance_needed, self._conn.commitment, recent_blockhash_to_use ) # Send the two instructions - self._conn.send_transaction(txn, payer, new_account, opts=opts, recent_blockhash=recent_blockhash) + self._conn.send_transaction(txn, opts=opts) return new_account_pk def create_associated_token_account( @@ -270,10 +277,13 @@ def create_associated_token_account( or until the transaction is confirmed. """ # Construct transaction + recent_blockhash_to_use = ( + self._conn.get_latest_blockhash().value.blockhash if recent_blockhash is None else recent_blockhash + ) public_key, txn, payer, opts = self._create_associated_token_account_args( - owner, skip_confirmation, self._conn.commitment + owner, skip_confirmation, self._conn.commitment, recent_blockhash_to_use ) - self._conn.send_transaction(txn, payer, opts=opts, recent_blockhash=recent_blockhash) + self._conn.send_transaction(txn, opts=opts) return public_key @staticmethod @@ -305,6 +315,9 @@ def create_wrapped_native_account( """ # Allocate memory for the account balance_needed = Token.get_min_balance_rent_for_exempt_for_account(conn) + recent_blockhash_to_use = ( + conn.get_latest_blockhash().value.blockhash if recent_blockhash is None else recent_blockhash + ) (new_account_public_key, txn, payer, new_account, opts,) = _TokenCore._create_wrapped_native_account_args( program_id, owner, @@ -313,8 +326,9 @@ def create_wrapped_native_account( skip_confirmation, balance_needed, conn.commitment, + recent_blockhash_to_use, ) - conn.send_transaction(txn, payer, new_account, opts=opts, recent_blockhash=recent_blockhash) + conn.send_transaction(txn, opts=opts) return new_account_public_key def create_multisig( @@ -336,9 +350,12 @@ def create_multisig( Public key of the new multisig account. """ balance_needed = Token.get_min_balance_rent_for_exempt_for_multisig(self._conn) - txn, payer, multisig = self._create_multisig_args(m, multi_signers, balance_needed) + recent_blockhash_to_use = ( + self._conn.get_latest_blockhash().value.blockhash if recent_blockhash is None else recent_blockhash + ) + txn, multisig = self._create_multisig_args(m, multi_signers, balance_needed, recent_blockhash_to_use) opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts - self._conn.send_transaction(txn, payer, multisig, opts=opts_to_use, recent_blockhash=recent_blockhash) + self._conn.send_transaction(txn, opts=opts_to_use) return multisig.pubkey() def get_mint_info(self) -> MintInfo: @@ -373,8 +390,13 @@ def transfer( recent_blockhash: (optional) a prefetched Blockhash for the transaction. """ opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts - txn, signers, opts = self._transfer_args(source, dest, owner, amount, multi_signers, opts_to_use) - return self._conn.send_transaction(txn, *signers, opts=opts, recent_blockhash=recent_blockhash) + recent_blockhash_to_use = ( + self._conn.get_latest_blockhash().value.blockhash if recent_blockhash is None else recent_blockhash + ) + txn, opts = self._transfer_args( + source, dest, owner, amount, multi_signers, opts_to_use, recent_blockhash_to_use + ) + return self._conn.send_transaction(txn, opts=opts) def approve( self, @@ -398,8 +420,13 @@ def approve( recent_blockhash: (optional) a prefetched Blockhash for the transaction. """ opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts - txn, payer, signers, opts = self._approve_args(source, delegate, owner, amount, multi_signers, opts_to_use) - return self._conn.send_transaction(txn, payer, *signers, opts=opts, recent_blockhash=recent_blockhash) + recent_blockhash_to_use = ( + self._conn.get_latest_blockhash().value.blockhash if recent_blockhash is None else recent_blockhash + ) + txn, payer, signers, opts = self._approve_args( + source, delegate, owner, amount, multi_signers, opts_to_use, recent_blockhash_to_use + ) + return self._conn.send_transaction(txn, opts=opts) def revoke( self, @@ -419,8 +446,13 @@ def revoke( recent_blockhash: (optional) a prefetched Blockhash for the transaction. """ opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts - txn, payer, signers, opts = self._revoke_args(account, owner, multi_signers, opts_to_use) - return self._conn.send_transaction(txn, payer, *signers, opts=opts, recent_blockhash=recent_blockhash) + recent_blockhash_to_use = ( + self._conn.get_latest_blockhash().value.blockhash if recent_blockhash is None else recent_blockhash + ) + txn, payer, signers, opts = self._revoke_args( + account, owner, multi_signers, opts_to_use, recent_blockhash_to_use + ) + return self._conn.send_transaction(txn, opts=opts) def set_authority( self, @@ -444,6 +476,9 @@ def set_authority( recent_blockhash: (optional) a prefetched Blockhash for the transaction. """ opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts + recent_blockhash_to_use = ( + self._conn.get_latest_blockhash().value.blockhash if recent_blockhash is None else recent_blockhash + ) txn, payer, signers, opts = self._set_authority_args( account, current_authority, @@ -451,8 +486,9 @@ def set_authority( new_authority, multi_signers, opts_to_use, + recent_blockhash_to_use, ) - return self._conn.send_transaction(txn, payer, *signers, opts=opts, recent_blockhash=recent_blockhash) + return self._conn.send_transaction(txn, opts=opts) def mint_to( self, @@ -477,8 +513,13 @@ def mint_to( or until the transaction is confirmed. """ opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts - txn, signers, opts = self._mint_to_args(dest, mint_authority, amount, multi_signers, opts_to_use) - return self._conn.send_transaction(txn, *signers, opts=opts, recent_blockhash=recent_blockhash) + recent_blockhash_to_use = ( + self._conn.get_latest_blockhash().value.blockhash if recent_blockhash is None else recent_blockhash + ) + txn, opts = self._mint_to_args( + dest, mint_authority, amount, multi_signers, opts_to_use, recent_blockhash_to_use + ) + return self._conn.send_transaction(txn, opts=opts) def burn( self, @@ -500,8 +541,11 @@ def burn( recent_blockhash: (optional) a prefetched Blockhash for the transaction. """ opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts - txn, signers, opts = self._burn_args(account, owner, amount, multi_signers, opts_to_use) - return self._conn.send_transaction(txn, *signers, opts=opts, recent_blockhash=recent_blockhash) + recent_blockhash_to_use = ( + self._conn.get_latest_blockhash().value.blockhash if recent_blockhash is None else recent_blockhash + ) + txn, opts = self._burn_args(account, owner, amount, multi_signers, opts_to_use, recent_blockhash_to_use) + return self._conn.send_transaction(txn, opts=opts) def close_account( self, @@ -523,8 +567,13 @@ def close_account( recent_blockhash: (optional) a prefetched Blockhash for the transaction. """ opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts - txn, signers, opts = self._close_account_args(account, dest, authority, multi_signers, opts_to_use) - return self._conn.send_transaction(txn, *signers, opts=opts, recent_blockhash=recent_blockhash) + recent_blockhash_to_use = ( + self._conn.get_latest_blockhash().value.blockhash if recent_blockhash is None else recent_blockhash + ) + txn, opts = self._close_account_args( + account, dest, authority, multi_signers, opts_to_use, recent_blockhash_to_use + ) + return self._conn.send_transaction(txn, opts=opts) def freeze_account( self, @@ -544,8 +593,11 @@ def freeze_account( recent_blockhash: (optional) a prefetched Blockhash for the transaction. """ opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts - txn, signers, opts = self._freeze_account_args(account, authority, multi_signers, opts_to_use) - return self._conn.send_transaction(txn, *signers, opts=opts, recent_blockhash=recent_blockhash) + recent_blockhash_to_use = ( + self._conn.get_latest_blockhash().value.blockhash if recent_blockhash is None else recent_blockhash + ) + txn, opts = self._freeze_account_args(account, authority, multi_signers, opts_to_use, recent_blockhash_to_use) + return self._conn.send_transaction(txn, opts=opts) def thaw_account( self, @@ -565,8 +617,11 @@ def thaw_account( recent_blockhash: (optional) a prefetched Blockhash for the transaction. """ opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts - txn, signers, opts = self._thaw_account_args(account, authority, multi_signers, opts_to_use) - return self._conn.send_transaction(txn, *signers, opts=opts, recent_blockhash=recent_blockhash) + recent_blockhash_to_use = ( + self._conn.get_latest_blockhash().value.blockhash if recent_blockhash is None else recent_blockhash + ) + txn, opts = self._thaw_account_args(account, authority, multi_signers, opts_to_use, recent_blockhash_to_use) + return self._conn.send_transaction(txn, opts=opts) def transfer_checked( self, @@ -592,10 +647,13 @@ def transfer_checked( recent_blockhash: (optional) a prefetched Blockhash for the transaction. """ opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts - txn, signers, opts = self._transfer_checked_args( - source, dest, owner, amount, decimals, multi_signers, opts_to_use + recent_blockhash_to_use = ( + self._conn.get_latest_blockhash().value.blockhash if recent_blockhash is None else recent_blockhash + ) + txn, opts = self._transfer_checked_args( + source, dest, owner, amount, decimals, multi_signers, opts_to_use, recent_blockhash_to_use ) - return self._conn.send_transaction(txn, *signers, opts=opts, recent_blockhash=recent_blockhash) + return self._conn.send_transaction(txn, opts=opts) def approve_checked( self, @@ -623,10 +681,13 @@ def approve_checked( recent_blockhash: (optional) a prefetched Blockhash for the transaction. """ opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts - txn, payer, signers, opts = self._approve_checked_args( - source, delegate, owner, amount, decimals, multi_signers, opts_to_use + recent_blockhash_to_use = ( + self._conn.get_latest_blockhash().value.blockhash if recent_blockhash is None else recent_blockhash + ) + txn, opts = self._approve_checked_args( + source, delegate, owner, amount, decimals, multi_signers, opts_to_use, recent_blockhash_to_use ) - return self._conn.send_transaction(txn, payer, *signers, opts=opts, recent_blockhash=recent_blockhash) + return self._conn.send_transaction(txn, opts=opts) def mint_to_checked( self, @@ -650,10 +711,13 @@ def mint_to_checked( recent_blockhash: (optional) a prefetched Blockhash for the transaction. """ opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts - txn, signers, opts = self._mint_to_checked_args( - dest, mint_authority, amount, decimals, multi_signers, opts_to_use + recent_blockhash_to_use = ( + self._conn.get_latest_blockhash().value.blockhash if recent_blockhash is None else recent_blockhash ) - return self._conn.send_transaction(txn, *signers, opts=opts, recent_blockhash=recent_blockhash) + txn, opts = self._mint_to_checked_args( + dest, mint_authority, amount, decimals, multi_signers, opts_to_use, recent_blockhash_to_use + ) + return self._conn.send_transaction(txn, opts=opts) def burn_checked( self, @@ -677,5 +741,10 @@ def burn_checked( recent_blockhash: (optional) a prefetched Blockhash for the transaction. """ opts_to_use = TxOpts(preflight_commitment=self._conn.commitment) if opts is None else opts - txn, signers, opts = self._burn_checked_args(account, owner, amount, decimals, multi_signers, opts_to_use) - return self._conn.send_transaction(txn, *signers, opts=opts, recent_blockhash=recent_blockhash) + recent_blockhash_to_use = ( + self._conn.get_latest_blockhash().value.blockhash if recent_blockhash is None else recent_blockhash + ) + txn, opts = self._burn_checked_args( + account, owner, amount, decimals, multi_signers, opts_to_use, recent_blockhash_to_use + ) + return self._conn.send_transaction(txn, opts=opts) diff --git a/src/spl/token/core.py b/src/spl/token/core.py index 1ff1b030..a4ebd81c 100644 --- a/src/spl/token/core.py +++ b/src/spl/token/core.py @@ -14,7 +14,9 @@ from solana.rpc.async_api import AsyncClient from solana.rpc.commitment import Commitment from solana.rpc.types import TokenAccountOpts, TxOpts -from solana.transaction import Transaction +from solders.hash import Hash as Blockhash +from solders.message import Message +from solders.transaction import Transaction from spl.token._layouts import ACCOUNT_LAYOUT, MINT_LAYOUT, MULTISIG_LAYOUT # type: ignore from spl.token.constants import WRAPPED_SOL_MINT @@ -113,12 +115,12 @@ def _create_mint_args( balance_needed: int, cls: Union[Type[Token], Type[AsyncToken]], commitment: Commitment, - ) -> Tuple[Union[Token, AsyncToken], Transaction, Keypair, Keypair, TxOpts]: + recent_blockhash: Blockhash, + ) -> Tuple[Union[Token, AsyncToken], Transaction, TxOpts]: mint_keypair = Keypair() token = cls(conn, mint_keypair.pubkey(), program_id, payer) # type: ignore # Construct transaction - txn = Transaction(fee_payer=payer.pubkey()) - txn.add( + ixs = [ sp.create_account( sp.CreateAccountParams( from_pubkey=payer.pubkey(), @@ -127,9 +129,7 @@ def _create_mint_args( space=MINT_LAYOUT.sizeof(), owner=program_id, ) - ) - ) - txn.add( + ), spl_token.initialize_mint( spl_token.InitializeMintParams( program_id=program_id, @@ -138,13 +138,13 @@ def _create_mint_args( mint_authority=mint_authority, freeze_authority=freeze_authority, ) - ) - ) + ), + ] + msg = Message.new_with_blockhash(ixs, payer.pubkey(), recent_blockhash) + txn = Transaction([payer, mint_keypair], msg, recent_blockhash) return ( token, txn, - payer, - mint_keypair, TxOpts(skip_confirmation=skip_confirmation, preflight_commitment=commitment), ) @@ -154,13 +154,13 @@ def _create_account_args( skip_confirmation: bool, balance_needed: int, commitment: Commitment, - ) -> Tuple[Pubkey, Transaction, Keypair, Keypair, TxOpts]: + recent_blockhash: Blockhash, + ) -> Tuple[Pubkey, Transaction, TxOpts]: new_keypair = Keypair() # Allocate memory for the account # Construct transaction - txn = Transaction(fee_payer=self.payer.pubkey()) - txn.add( + ixs = [ sp.create_account( sp.CreateAccountParams( from_pubkey=self.payer.pubkey(), @@ -169,9 +169,7 @@ def _create_account_args( space=ACCOUNT_LAYOUT.sizeof(), owner=self.program_id, ) - ) - ) - txn.add( + ), spl_token.initialize_account( spl_token.InitializeAccountParams( account=new_keypair.pubkey(), @@ -179,29 +177,26 @@ def _create_account_args( owner=owner, program_id=self.program_id, ) - ) - ) + ), + ] + msg = Message.new_with_blockhash(ixs, self.payer.pubkey(), recent_blockhash) + txn = Transaction([self.payer, new_keypair], msg, recent_blockhash) return ( new_keypair.pubkey(), txn, - self.payer, - new_keypair, TxOpts(skip_confirmation=skip_confirmation, preflight_commitment=commitment), ) def _create_associated_token_account_args( - self, - owner: Pubkey, - skip_confirmation: bool, - commitment: Commitment, + self, owner: Pubkey, skip_confirmation: bool, commitment: Commitment, recent_blockhash: Blockhash ) -> Tuple[Pubkey, Transaction, Keypair, TxOpts]: # Construct transaction - txn = Transaction(fee_payer=self.payer.pubkey()) - create_txn = spl_token.create_associated_token_account(payer=self.payer.pubkey(), owner=owner, mint=self.pubkey) - txn.add(create_txn) + ix = spl_token.create_associated_token_account(payer=self.payer.pubkey(), owner=owner, mint=self.pubkey) + msg = Message.new_with_blockhash([ix], self.payer.pubkey(), recent_blockhash) + txn = Transaction([self.payer], msg, recent_blockhash) return ( - create_txn.accounts[1].pubkey, + ix.accounts[1].pubkey, txn, self.payer, TxOpts(skip_confirmation=skip_confirmation, preflight_commitment=commitment), @@ -216,12 +211,12 @@ def _create_wrapped_native_account_args( skip_confirmation: bool, balance_needed: int, commitment: Commitment, + recent_blockhash: Blockhash, ) -> Tuple[Pubkey, Transaction, Keypair, Keypair, TxOpts]: new_keypair = Keypair() # Allocate memory for the account # Construct transaction - txn = Transaction(fee_payer=payer.pubkey()) - txn.add( + ixs = [ sp.create_account( sp.CreateAccountParams( from_pubkey=payer.pubkey(), @@ -230,20 +225,14 @@ def _create_wrapped_native_account_args( space=ACCOUNT_LAYOUT.sizeof(), owner=program_id, ) - ) - ) - - txn.add( + ), sp.transfer( sp.TransferParams( from_pubkey=payer.pubkey(), to_pubkey=new_keypair.pubkey(), lamports=amount, ) - ) - ) - - txn.add( + ), spl_token.initialize_account( spl_token.InitializeAccountParams( account=new_keypair.pubkey(), @@ -251,8 +240,10 @@ def _create_wrapped_native_account_args( owner=owner, program_id=program_id, ) - ) - ) + ), + ] + msg = Message.new_with_blockhash(ixs, payer.pubkey(), recent_blockhash) + txn = Transaction([payer], msg, recent_blockhash) return ( new_keypair.pubkey(), @@ -270,15 +261,15 @@ def _transfer_args( amount: int, multi_signers: Optional[List[Keypair]], opts: TxOpts, - ) -> Tuple[Transaction, List[Keypair], TxOpts]: + recent_blockhash: Blockhash, + ) -> Tuple[Transaction, TxOpts]: if isinstance(owner, Keypair): owner_pubkey = owner.pubkey() signers = [owner] else: owner_pubkey = owner signers = multi_signers if multi_signers else [] - - txn = Transaction(fee_payer=self.payer.pubkey()).add( + ixs = [ spl_token.transfer( spl_token.TransferParams( program_id=self.program_id, @@ -289,8 +280,10 @@ def _transfer_args( signers=[signer.pubkey() for signer in signers], ) ) - ) - return txn, signers, opts + ] + msg = Message.new_with_blockhash(ixs, self.payer.pubkey(), recent_blockhash) + txn = Transaction([self.payer], msg, recent_blockhash) + return txn, opts def _set_authority_args( self, @@ -300,6 +293,7 @@ def _set_authority_args( new_authority: Optional[Pubkey], multi_signers: Optional[List[Keypair]], opts: TxOpts, + recent_blockhash: Blockhash, ) -> Tuple[Transaction, Keypair, List[Keypair], TxOpts]: if isinstance(current_authority, Keypair): current_authority_pubkey = current_authority.pubkey() @@ -307,8 +301,7 @@ def _set_authority_args( else: current_authority_pubkey = current_authority signers = multi_signers if multi_signers else [] - - txn = Transaction(fee_payer=self.payer.pubkey()).add( + ixs = [ spl_token.set_authority( spl_token.SetAuthorityParams( program_id=self.program_id, @@ -319,7 +312,9 @@ def _set_authority_args( new_authority=new_authority, ) ) - ) + ] + msg = Message.new_with_blockhash(ixs, self.payer.pubkey(), recent_blockhash) + txn = Transaction([self.payer], msg, recent_blockhash) return txn, self.payer, signers, opts @@ -330,15 +325,15 @@ def _mint_to_args( amount: int, multi_signers: Optional[List[Keypair]], opts: TxOpts, - ) -> Tuple[Transaction, List[Keypair], TxOpts]: + recent_blockhash: Blockhash, + ) -> Tuple[Transaction, TxOpts]: if isinstance(mint_authority, Keypair): owner_pubkey = mint_authority.pubkey() signers = [mint_authority] else: owner_pubkey = mint_authority signers = multi_signers if multi_signers else [] - - txn = Transaction(fee_payer=self.payer.pubkey()).add( + ixs = [ spl_token.mint_to( spl_token.MintToParams( program_id=self.program_id, @@ -349,8 +344,10 @@ def _mint_to_args( signers=[signer.pubkey() for signer in signers], ) ) - ) - return txn, signers, opts + ] + msg = Message.new_with_blockhash(ixs, self.payer.pubkey(), recent_blockhash) + txn = Transaction([self.payer], msg, recent_blockhash) + return txn, opts def _create_mint_info(self, info: GetAccountInfoResp) -> MintInfo: value = info.value @@ -436,6 +433,7 @@ def _approve_args( amount: int, multi_signers: Optional[List[Keypair]], opts: TxOpts, + recent_blockhash: Blockhash, ) -> Tuple[Transaction, Keypair, List[Keypair], TxOpts]: if isinstance(owner, Keypair): owner_pubkey = owner.pubkey() @@ -443,8 +441,7 @@ def _approve_args( else: owner_pubkey = owner signers = multi_signers if multi_signers else [] - - txn = Transaction(fee_payer=self.payer.pubkey()).add( + ixs = [ spl_token.approve( spl_token.ApproveParams( program_id=self.program_id, @@ -455,7 +452,9 @@ def _approve_args( signers=[signer.pubkey() for signer in signers], ) ) - ) + ] + msg = Message.new_with_blockhash(ixs, self.payer.pubkey(), recent_blockhash) + txn = Transaction([self.payer], msg, recent_blockhash) return txn, self.payer, signers, opts def _revoke_args( @@ -464,6 +463,7 @@ def _revoke_args( owner: Union[Keypair, Pubkey], multi_signers: Optional[List[Keypair]], opts: TxOpts, + recent_blockhash: Blockhash, ) -> Tuple[Transaction, Keypair, List[Keypair], TxOpts]: if isinstance(owner, Keypair): owner_pubkey = owner.pubkey() @@ -471,8 +471,7 @@ def _revoke_args( else: owner_pubkey = owner signers = multi_signers if multi_signers else [] - - txn = Transaction(fee_payer=self.payer.pubkey()).add( + ixs = [ spl_token.revoke( spl_token.RevokeParams( program_id=self.program_id, @@ -481,7 +480,9 @@ def _revoke_args( signers=[signer.pubkey() for signer in signers], ) ) - ) + ] + msg = Message.new_with_blockhash(ixs, self.payer.pubkey(), recent_blockhash) + txn = Transaction([self.payer], msg, recent_blockhash) return txn, self.payer, signers, opts def _freeze_account_args( @@ -490,7 +491,8 @@ def _freeze_account_args( authority: Union[Pubkey, Keypair], multi_signers: Optional[List[Keypair]], opts: TxOpts, - ) -> Tuple[Transaction, List[Keypair], TxOpts]: + recent_blockhash: Blockhash, + ) -> Tuple[Transaction, TxOpts]: if isinstance(authority, Keypair): authority_pubkey = authority.pubkey() base_signers = [authority] @@ -498,7 +500,7 @@ def _freeze_account_args( authority_pubkey = authority base_signers = multi_signers if multi_signers else [] fee_payer_keypair = self.payer - txn = Transaction(fee_payer=fee_payer_keypair.pubkey()).add( + ixs = [ spl_token.freeze_account( spl_token.FreezeAccountParams( program_id=self.program_id, @@ -508,9 +510,11 @@ def _freeze_account_args( multi_signers=[signer.pubkey() for signer in base_signers], ) ) - ) + ] + msg = Message.new_with_blockhash(ixs, self.payer.pubkey(), recent_blockhash) signers = list(set(base_signers) | {fee_payer_keypair}) - return txn, signers, opts + txn = Transaction(signers, msg, recent_blockhash) + return txn, opts def _thaw_account_args( self, @@ -518,7 +522,8 @@ def _thaw_account_args( authority: Union[Pubkey, Keypair], multi_signers: Optional[List[Keypair]], opts: TxOpts, - ) -> Tuple[Transaction, List[Keypair], TxOpts]: + recent_blockhash: Blockhash, + ) -> Tuple[Transaction, TxOpts]: if isinstance(authority, Keypair): authority_pubkey = authority.pubkey() base_signers = [authority] @@ -526,7 +531,7 @@ def _thaw_account_args( authority_pubkey = authority base_signers = multi_signers if multi_signers else [] fee_payer_keypair = self.payer - txn = Transaction(fee_payer=fee_payer_keypair.pubkey()).add( + ixs = [ spl_token.thaw_account( spl_token.ThawAccountParams( program_id=self.program_id, @@ -536,9 +541,11 @@ def _thaw_account_args( multi_signers=[signer.pubkey() for signer in base_signers], ) ) - ) + ] + msg = Message.new_with_blockhash(ixs, self.payer.pubkey(), recent_blockhash) signers = list(set(base_signers) | {fee_payer_keypair}) - return txn, signers, opts + txn = Transaction(signers, msg, recent_blockhash) + return txn, opts def _close_account_args( self, @@ -547,15 +554,15 @@ def _close_account_args( authority: Union[Pubkey, Keypair], multi_signers: Optional[List[Keypair]], opts: TxOpts, - ) -> Tuple[Transaction, List[Keypair], TxOpts]: + recent_blockhash: Blockhash, + ) -> Tuple[Transaction, TxOpts]: if isinstance(authority, Keypair): authority_pubkey = authority.pubkey() signers = [authority] else: authority_pubkey = authority signers = multi_signers if multi_signers else [] - - txn = Transaction(fee_payer=self.payer.pubkey()).add( + ixs = [ spl_token.close_account( spl_token.CloseAccountParams( program_id=self.program_id, @@ -565,8 +572,10 @@ def _close_account_args( signers=[signer.pubkey() for signer in signers], ) ) - ) - return txn, signers, opts + ] + msg = Message.new_with_blockhash(ixs, self.payer.pubkey(), recent_blockhash) + txn = Transaction([self.payer], msg, recent_blockhash) + return txn, opts def _burn_args( self, @@ -575,15 +584,15 @@ def _burn_args( amount: int, multi_signers: Optional[List[Keypair]], opts: TxOpts, - ) -> Tuple[Transaction, List[Keypair], TxOpts]: + recent_blockhash: Blockhash, + ) -> Tuple[Transaction, TxOpts]: if isinstance(owner, Keypair): owner_pubkey = owner.pubkey() signers = [owner] else: owner_pubkey = owner signers = multi_signers if multi_signers else [] - - txn = Transaction(fee_payer=self.payer.pubkey()).add( + ixs = [ spl_token.burn( spl_token.BurnParams( program_id=self.program_id, @@ -594,19 +603,16 @@ def _burn_args( signers=[signer.pubkey() for signer in signers], ) ) - ) - return txn, signers, opts + ] + msg = Message.new_with_blockhash(ixs, self.payer.pubkey(), recent_blockhash) + txn = Transaction([self.payer], msg, recent_blockhash) + return txn, opts def _create_multisig_args( - self, - m: int, - signers: List[Pubkey], - balance_needed: int, - ) -> Tuple[Transaction, Keypair, Keypair]: + self, m: int, signers: List[Pubkey], balance_needed: int, recent_blockhash: Blockhash + ) -> Tuple[Transaction, Keypair]: multisig_keypair = Keypair() - - txn = Transaction(fee_payer=self.payer.pubkey()) - txn.add( + ixs = [ sp.create_account( sp.CreateAccountParams( from_pubkey=self.payer.pubkey(), @@ -615,9 +621,7 @@ def _create_multisig_args( space=MULTISIG_LAYOUT.sizeof(), owner=self.program_id, ) - ) - ) - txn.add( + ), spl_token.initialize_multisig( spl_token.InitializeMultisigParams( program_id=self.program_id, @@ -625,10 +629,11 @@ def _create_multisig_args( m=m, signers=signers, ) - ) - ) - - return txn, self.payer, multisig_keypair + ), + ] + msg = Message.new_with_blockhash(ixs, self.payer.pubkey(), recent_blockhash) + txn = Transaction([self.payer, multisig_keypair], msg, recent_blockhash) + return txn, multisig_keypair def _transfer_checked_args( self, @@ -639,15 +644,15 @@ def _transfer_checked_args( decimals: int, multi_signers: Optional[List[Keypair]], opts: TxOpts, - ) -> Tuple[Transaction, List[Keypair], TxOpts]: + recent_blockhash: Blockhash, + ) -> Tuple[Transaction, TxOpts]: if isinstance(owner, Keypair): owner_pubkey = owner.pubkey() signers = [owner] else: owner_pubkey = owner signers = multi_signers if multi_signers else [] - - txn = Transaction(fee_payer=self.payer.pubkey()).add( + ixs = [ spl_token.transfer_checked( spl_token.TransferCheckedParams( program_id=self.program_id, @@ -660,8 +665,10 @@ def _transfer_checked_args( signers=[signer.pubkey() for signer in signers], ) ) - ) - return txn, signers, opts + ] + msg = Message.new_with_blockhash(ixs, self.payer.pubkey(), recent_blockhash) + txn = Transaction([self.payer], msg, recent_blockhash) + return txn, opts def _mint_to_checked_args( self, @@ -671,15 +678,15 @@ def _mint_to_checked_args( decimals: int, multi_signers: Optional[List[Keypair]], opts: TxOpts, - ) -> Tuple[Transaction, List[Keypair], TxOpts]: + recent_blockhash: Blockhash, + ) -> Tuple[Transaction, TxOpts]: if isinstance(mint_authority, Keypair): owner_pubkey = mint_authority.pubkey() signers = [mint_authority] else: owner_pubkey = mint_authority signers = multi_signers if multi_signers else [] - - txn = Transaction(fee_payer=self.payer.pubkey()).add( + ixs = [ spl_token.mint_to_checked( spl_token.MintToCheckedParams( program_id=self.program_id, @@ -691,8 +698,10 @@ def _mint_to_checked_args( signers=[signer.pubkey() for signer in signers], ) ) - ) - return txn, signers, opts + ] + msg = Message.new_with_blockhash(ixs, self.payer.pubkey(), recent_blockhash) + txn = Transaction([self.payer], msg, recent_blockhash) + return txn, opts def _burn_checked_args( self, @@ -702,28 +711,28 @@ def _burn_checked_args( decimals: int, multi_signers: Optional[List[Keypair]], opts: TxOpts, - ) -> Tuple[Transaction, List[Keypair], TxOpts]: + recent_blockhash: Blockhash, + ) -> Tuple[Transaction, TxOpts]: if isinstance(owner, Keypair): owner_pubkey = owner.pubkey() signers = [owner] else: owner_pubkey = owner signers = multi_signers if multi_signers else [] - - txn = Transaction(fee_payer=self.payer.pubkey()).add( - spl_token.burn_checked( - spl_token.BurnCheckedParams( - program_id=self.program_id, - mint=self.pubkey, - account=account, - owner=owner_pubkey, - amount=amount, - decimals=decimals, - signers=[signer.pubkey() for signer in signers], - ) + ix = spl_token.burn_checked( + spl_token.BurnCheckedParams( + program_id=self.program_id, + mint=self.pubkey, + account=account, + owner=owner_pubkey, + amount=amount, + decimals=decimals, + signers=[signer.pubkey() for signer in signers], ) ) - return txn, signers, opts + msg = Message.new_with_blockhash([ix], self.payer.pubkey(), recent_blockhash) + txn = Transaction(signers, msg, recent_blockhash) + return txn, opts def _approve_checked_args( self, @@ -734,26 +743,26 @@ def _approve_checked_args( decimals: int, multi_signers: Optional[List[Keypair]], opts: TxOpts, - ) -> Tuple[Transaction, Keypair, List[Keypair], TxOpts]: + recent_blockhash: Blockhash, + ) -> Tuple[Transaction, TxOpts]: if isinstance(owner, Keypair): owner_pubkey = owner.pubkey() signers = [owner] else: owner_pubkey = owner signers = multi_signers if multi_signers else [] - - txn = Transaction(fee_payer=self.payer.pubkey()).add( - spl_token.approve_checked( - spl_token.ApproveCheckedParams( - program_id=self.program_id, - source=source, - mint=self.pubkey, - delegate=delegate, - owner=owner_pubkey, - amount=amount, - decimals=decimals, - signers=[signer.pubkey() for signer in signers], - ) + ix = spl_token.approve_checked( + spl_token.ApproveCheckedParams( + program_id=self.program_id, + source=source, + mint=self.pubkey, + delegate=delegate, + owner=owner_pubkey, + amount=amount, + decimals=decimals, + signers=[signer.pubkey() for signer in signers], ) ) - return txn, self.payer, signers, opts + msg = Message.new_with_blockhash([ix], self.payer.pubkey(), recent_blockhash) + txn = Transaction([self.payer], msg, recent_blockhash) + return txn, opts diff --git a/tests/integration/test_async_http_client.py b/tests/integration/test_async_http_client.py index e77d6ec1..6c04ac5a 100644 --- a/tests/integration/test_async_http_client.py +++ b/tests/integration/test_async_http_client.py @@ -4,7 +4,7 @@ import pytest import solders.system_program as sp from solders.keypair import Keypair -from solders.message import MessageV0 +from solders.message import MessageV0, Message from solders.pubkey import Pubkey from solders.rpc.errors import SendTransactionPreflightFailureMessage from solders.rpc.requests import GetBlockHeight, GetFirstAvailableBlock @@ -17,7 +17,7 @@ from solana.rpc.commitment import Confirmed, Finalized, Processed from solana.rpc.core import RPCException, TransactionExpiredBlockheightExceededError from solana.rpc.types import DataSliceOpts, TxOpts -from solana.transaction import Transaction +from solders.transaction import Transaction from ..utils import AIRDROP_AMOUNT, assert_valid_response @@ -68,14 +68,17 @@ async def test_send_transaction_and_get_balance( ): """Test sending a transaction to localnet.""" # Create transfer tx to transfer lamports from stubbed sender to async_stubbed_receiver - transfer_tx = Transaction().add( + ixs = [ sp.transfer( sp.TransferParams( from_pubkey=async_stubbed_sender.pubkey(), to_pubkey=async_stubbed_receiver, lamports=1000 ) ) - ) - resp = await test_http_client_async.send_transaction(transfer_tx, async_stubbed_sender) + ] + blockhash = (await test_http_client_async.get_latest_blockhash()).value.blockhash + msg = Message.new_with_blockhash(ixs, async_stubbed_sender.pubkey(), blockhash) + transfer_tx = Transaction([async_stubbed_sender], msg, blockhash) + resp = await test_http_client_async.send_transaction(transfer_tx) assert_valid_response(resp) # Confirm transaction await test_http_client_async.confirm_transaction(resp.value) @@ -132,15 +135,18 @@ async def test_send_bad_transaction(stubbed_receiver: Pubkey, test_http_client_a balance = await test_http_client_async.get_balance(poor_account.pubkey()) assert balance.value == airdrop_amount # Create transfer tx to transfer lamports from stubbed sender to stubbed_receiver - transfer_tx = Transaction().add( + blockhash = (await test_http_client_async.get_latest_blockhash()).value.blockhash + ixs = [ sp.transfer( sp.TransferParams( from_pubkey=poor_account.pubkey(), to_pubkey=stubbed_receiver, lamports=airdrop_amount + 1 ) ) - ) + ] + msg = Message.new_with_blockhash(ixs, poor_account.pubkey(), blockhash) + transfer_tx = Transaction([poor_account], msg, blockhash) with pytest.raises(RPCException) as exc_info: - await test_http_client_async.send_transaction(transfer_tx, poor_account) + await test_http_client_async.send_transaction(transfer_tx) err = exc_info.value.args[0] assert isinstance(err, SendTransactionPreflightFailureMessage) assert err.data.logs @@ -152,7 +158,8 @@ async def test_send_transaction_prefetched_blockhash( ): """Test sending a transaction to localnet.""" # Create transfer tx to transfer lamports from stubbed sender to async_stubbed_receiver - transfer_tx = Transaction().add( + blockhash = (await test_http_client_async.get_latest_blockhash()).value.blockhash + ixs = [ sp.transfer( sp.TransferParams( from_pubkey=async_stubbed_sender_prefetched_blockhash.pubkey(), @@ -160,8 +167,10 @@ async def test_send_transaction_prefetched_blockhash( lamports=1000, ) ) - ) - resp = await test_http_client_async.send_transaction(transfer_tx, async_stubbed_sender_prefetched_blockhash) + ] + msg = Message.new_with_blockhash(ixs, async_stubbed_sender_prefetched_blockhash.pubkey(), blockhash) + transfer_tx = Transaction([async_stubbed_sender_prefetched_blockhash], msg, blockhash) + resp = await test_http_client_async.send_transaction(transfer_tx) assert_valid_response(resp) # Confirm transaction await test_http_client_async.confirm_transaction(resp.value) @@ -185,17 +194,18 @@ async def test_send_raw_transaction_and_get_balance( recent_blockhash = resp.value.blockhash assert recent_blockhash is not None # Create transfer tx transfer lamports from stubbed sender to async_stubbed_receiver - transfer_tx = Transaction(recent_blockhash=recent_blockhash).add( + blockhash = (await test_http_client_async.get_latest_blockhash()).value.blockhash + ixs = [ sp.transfer( sp.TransferParams( from_pubkey=async_stubbed_sender.pubkey(), to_pubkey=async_stubbed_receiver, lamports=1000 ) ) - ) - # Sign transaction - transfer_tx.sign(async_stubbed_sender) + ] + msg = Message.new_with_blockhash(ixs, async_stubbed_sender.pubkey(), blockhash) + transfer_tx = Transaction([async_stubbed_sender], msg, blockhash) # Send raw transaction - resp = await test_http_client_async.send_raw_transaction(transfer_tx.serialize()) + resp = await test_http_client_async.send_raw_transaction(bytes(transfer_tx)) assert_valid_response(resp) # Confirm transaction resp = await test_http_client_async.confirm_transaction(resp.value) @@ -220,18 +230,19 @@ async def test_send_raw_transaction_and_get_balance_using_latest_blockheight( assert recent_blockhash is not None last_valid_block_height = resp.value.last_valid_block_height # Create transfer tx transfer lamports from stubbed sender to async_stubbed_receiver - transfer_tx = Transaction(recent_blockhash=recent_blockhash).add( + blockhash = (await test_http_client_async.get_latest_blockhash()).value.blockhash + ixs = [ sp.transfer( sp.TransferParams( from_pubkey=async_stubbed_sender.pubkey(), to_pubkey=async_stubbed_receiver, lamports=1000 ) ) - ) - # Sign transaction - transfer_tx.sign(async_stubbed_sender) + ] + msg = Message.new_with_blockhash(ixs, async_stubbed_sender.pubkey(), blockhash) + transfer_tx = Transaction([async_stubbed_sender], msg, blockhash) # Send raw transaction resp = await test_http_client_async.send_raw_transaction( - transfer_tx.serialize(), + bytes(transfer_tx), opts=TxOpts(preflight_commitment=Processed, last_valid_block_height=last_valid_block_height), ) assert_valid_response(resp) @@ -255,14 +266,15 @@ async def test_confirm_expired_transaction(stubbed_sender, stubbed_receiver, tes assert recent_blockhash is not None last_valid_block_height = resp.value.last_valid_block_height - 330 # Create transfer tx transfer lamports from stubbed sender to stubbed_receiver - transfer_tx = Transaction(recent_blockhash=recent_blockhash).add( + blockhash = (await test_http_client_async.get_latest_blockhash()).value.blockhash + ixs = [ sp.transfer(sp.TransferParams(from_pubkey=stubbed_sender.pubkey(), to_pubkey=stubbed_receiver, lamports=1000)) - ) - # Sign transaction - transfer_tx.sign(stubbed_sender) + ] + msg = Message.new_with_blockhash(ixs, stubbed_sender.pubkey(), blockhash) + transfer_tx = Transaction([stubbed_sender], msg, blockhash) # Send raw transaction resp = await test_http_client_async.send_raw_transaction( - transfer_tx.serialize(), opts=TxOpts(skip_confirmation=True, skip_preflight=True) + bytes(transfer_tx), opts=TxOpts(skip_confirmation=True, skip_preflight=True) ) assert_valid_response(resp) # Confirm transaction @@ -282,11 +294,12 @@ async def test_get_fee_for_transaction_message(stubbed_sender, stubbed_receiver, recent_blockhash = resp.value.blockhash assert recent_blockhash is not None # Create transfer tx transfer lamports from stubbed sender to stubbed_receiver - transfer_tx = Transaction(recent_blockhash=recent_blockhash).add( + ixs = [ sp.transfer(sp.TransferParams(from_pubkey=stubbed_sender.pubkey(), to_pubkey=stubbed_receiver, lamports=1000)) - ) + ] + msg = Message.new_with_blockhash(ixs, stubbed_sender.pubkey(), recent_blockhash) # Get fee for transaction message - fee_resp = await test_http_client_async.get_fee_for_message(transfer_tx.compile_message()) + fee_resp = await test_http_client_async.get_fee_for_message(msg) assert_valid_response(fee_resp) assert fee_resp.value is not None diff --git a/tests/integration/test_http_client.py b/tests/integration/test_http_client.py index 57af1224..a9df5603 100644 --- a/tests/integration/test_http_client.py +++ b/tests/integration/test_http_client.py @@ -4,7 +4,7 @@ import pytest import solders.system_program as sp from solders.keypair import Keypair -from solders.message import MessageV0 +from solders.message import MessageV0, Message from solders.pubkey import Pubkey from solders.rpc.errors import SendTransactionPreflightFailureMessage from solders.rpc.requests import GetBlockHeight, GetFirstAvailableBlock @@ -16,7 +16,7 @@ from solana.rpc.commitment import Confirmed, Finalized, Processed from solana.rpc.core import RPCException, TransactionExpiredBlockheightExceededError from solana.rpc.types import DataSliceOpts, TxOpts -from solana.transaction import Transaction +from solders.transaction import Transaction from spl.token.constants import WRAPPED_SOL_MINT from ..utils import AIRDROP_AMOUNT, assert_valid_response @@ -62,12 +62,15 @@ def test_request_air_drop_prefetched_blockhash( def test_send_transaction_and_get_balance(stubbed_sender, stubbed_receiver, test_http_client: Client): """Test sending a transaction to localnet.""" # Create transfer tx to transfer lamports from stubbed sender to stubbed_receiver - transfer_tx = Transaction().add( + blockhash = test_http_client.get_latest_blockhash().value.blockhash + ixs = [ sp.transfer(sp.TransferParams(from_pubkey=stubbed_sender.pubkey(), to_pubkey=stubbed_receiver, lamports=1000)) - ) + ] + msg = Message.new_with_blockhash(ixs, stubbed_sender.pubkey(), blockhash) + transfer_tx = Transaction([stubbed_sender], msg, blockhash) sim_resp = test_http_client.simulate_transaction(transfer_tx) assert_valid_response(sim_resp) - resp = test_http_client.send_transaction(transfer_tx, stubbed_sender) + resp = test_http_client.send_transaction(transfer_tx) assert_valid_response(resp) # Confirm transaction test_http_client.confirm_transaction(resp.value) @@ -120,15 +123,18 @@ def test_send_bad_transaction(stubbed_receiver: Pubkey, test_http_client: Client balance = test_http_client.get_balance(poor_account.pubkey()) assert balance.value == airdrop_amount # Create transfer tx to transfer lamports from stubbed sender to stubbed_receiver - transfer_tx = Transaction().add( + blockhash = test_http_client.get_latest_blockhash().value.blockhash + ixs = [ sp.transfer( sp.TransferParams( from_pubkey=poor_account.pubkey(), to_pubkey=stubbed_receiver, lamports=airdrop_amount + 1 ) ) - ) + ] + msg = Message.new_with_blockhash(ixs, poor_account.pubkey(), blockhash) + transfer_tx = Transaction([poor_account], msg, blockhash) with pytest.raises(RPCException) as exc_info: - test_http_client.send_transaction(transfer_tx, poor_account) + test_http_client.send_transaction(transfer_tx) err = exc_info.value.args[0] assert isinstance(err, SendTransactionPreflightFailureMessage) assert err.data.logs @@ -140,7 +146,8 @@ def test_send_transaction_prefetched_blockhash( ): """Test sending a transaction to localnet.""" # Create transfer tx to transfer lamports from stubbed sender to stubbed_receiver - transfer_tx = Transaction().add( + recent_blockhash = test_http_client.parse_recent_blockhash(test_http_client.get_latest_blockhash()) + ixs = [ sp.transfer( sp.TransferParams( from_pubkey=stubbed_sender_prefetched_blockhash.pubkey(), @@ -148,11 +155,10 @@ def test_send_transaction_prefetched_blockhash( lamports=1000, ) ) - ) - recent_blockhash = test_http_client.parse_recent_blockhash(test_http_client.get_latest_blockhash()) - resp = test_http_client.send_transaction( - transfer_tx, stubbed_sender_prefetched_blockhash, recent_blockhash=recent_blockhash - ) + ] + msg = Message.new_with_blockhash(ixs, stubbed_sender_prefetched_blockhash.pubkey(), recent_blockhash) + transfer_tx = Transaction([stubbed_sender_prefetched_blockhash], msg, recent_blockhash) + resp = test_http_client.send_transaction(transfer_tx) assert_valid_response(resp) # Confirm transaction test_http_client.confirm_transaction(resp.value) @@ -174,13 +180,14 @@ def test_send_raw_transaction_and_get_balance(stubbed_sender, stubbed_receiver, recent_blockhash = resp.value.blockhash assert recent_blockhash is not None # Create transfer tx transfer lamports from stubbed sender to stubbed_receiver - transfer_tx = Transaction(recent_blockhash=recent_blockhash).add( + blockhash = test_http_client.get_latest_blockhash().value.blockhash + ixs = [ sp.transfer(sp.TransferParams(from_pubkey=stubbed_sender.pubkey(), to_pubkey=stubbed_receiver, lamports=1000)) - ) - # Sign transaction - transfer_tx.sign(stubbed_sender) + ] + msg = Message.new_with_blockhash(ixs, stubbed_sender.pubkey(), blockhash) + transfer_tx = Transaction([stubbed_sender], msg, blockhash) # Send raw transaction - tx_resp = test_http_client.send_raw_transaction(transfer_tx.serialize()) + tx_resp = test_http_client.send_raw_transaction(bytes(transfer_tx)) assert_valid_response(tx_resp) # Confirm transaction test_http_client.confirm_transaction(tx_resp.value) @@ -205,14 +212,15 @@ def test_send_raw_transaction_and_get_balance_using_latest_blockheight( assert recent_blockhash is not None last_valid_block_height = resp.value.last_valid_block_height # Create transfer tx transfer lamports from stubbed sender to stubbed_receiver - transfer_tx = Transaction(recent_blockhash=recent_blockhash).add( + blockhash = test_http_client.get_latest_blockhash().value.blockhash + ixs = [ sp.transfer(sp.TransferParams(from_pubkey=stubbed_sender.pubkey(), to_pubkey=stubbed_receiver, lamports=1000)) - ) - # Sign transaction - transfer_tx.sign(stubbed_sender) + ] + msg = Message.new_with_blockhash(ixs, stubbed_sender.pubkey(), blockhash) + transfer_tx = Transaction([stubbed_sender], msg, blockhash) # Send raw transaction resp = test_http_client.send_raw_transaction( - transfer_tx.serialize(), + bytes(transfer_tx), opts=TxOpts(preflight_commitment=Processed, last_valid_block_height=last_valid_block_height), ) assert_valid_response(resp) @@ -236,14 +244,14 @@ def test_confirm_expired_transaction(stubbed_sender, stubbed_receiver, test_http assert recent_blockhash is not None last_valid_block_height = resp.value.last_valid_block_height - 330 # Create transfer tx transfer lamports from stubbed sender to stubbed_receiver - transfer_tx = Transaction(recent_blockhash=recent_blockhash).add( + ixs = [ sp.transfer(sp.TransferParams(from_pubkey=stubbed_sender.pubkey(), to_pubkey=stubbed_receiver, lamports=1000)) - ) - # Sign transaction - transfer_tx.sign(stubbed_sender) + ] + msg = Message.new_with_blockhash(ixs, stubbed_sender.pubkey(), recent_blockhash) + transfer_tx = Transaction([stubbed_sender], msg, recent_blockhash) # Send raw transaction tx_resp = test_http_client.send_raw_transaction( - transfer_tx.serialize(), opts=TxOpts(skip_confirmation=True, skip_preflight=True) + bytes(transfer_tx), opts=TxOpts(skip_confirmation=True, skip_preflight=True) ) assert_valid_response(tx_resp) # Confirm transaction @@ -261,11 +269,12 @@ def test_get_fee_for_transaction(stubbed_sender, stubbed_receiver, test_http_cli recent_blockhash = resp.value.blockhash assert recent_blockhash is not None # Create transfer tx transfer lamports from stubbed sender to stubbed_receiver - transfer_tx = Transaction(recent_blockhash=recent_blockhash).add( + ixs = [ sp.transfer(sp.TransferParams(from_pubkey=stubbed_sender.pubkey(), to_pubkey=stubbed_receiver, lamports=1000)) - ) + ] + msg = Message.new_with_blockhash(ixs, stubbed_sender.pubkey(), recent_blockhash) # get fee for transaction - fee_resp = test_http_client.get_fee_for_message(transfer_tx.compile_message()) + fee_resp = test_http_client.get_fee_for_message(msg) assert_valid_response(fee_resp) assert fee_resp.value is not None diff --git a/tests/integration/test_memo.py b/tests/integration/test_memo.py index 1c7b3446..2eabbc77 100644 --- a/tests/integration/test_memo.py +++ b/tests/integration/test_memo.py @@ -1,13 +1,14 @@ """Tests for the Memo program.""" import pytest from solders.keypair import Keypair +from solders.message import Message from solders.transaction_status import ParsedInstruction from spl.memo.constants import MEMO_PROGRAM_ID from spl.memo.instructions import MemoParams, create_memo from solana.rpc.api import Client from solana.rpc.commitment import Finalized -from solana.transaction import Transaction +from solders.transaction import Transaction from ..utils import AIRDROP_AMOUNT, assert_valid_response @@ -27,8 +28,11 @@ def test_send_memo_in_transaction(stubbed_sender: Keypair, test_http_client: Cli message=message, ) # Create transfer tx to add memo to transaction from stubbed sender - transfer_tx = Transaction().add(create_memo(memo_params)) - resp = test_http_client.send_transaction(transfer_tx, stubbed_sender) + blockhash = test_http_client.get_latest_blockhash().value.blockhash + ixs = [create_memo(memo_params)] + msg = Message.new_with_blockhash(ixs, stubbed_sender.pubkey(), blockhash) + transfer_tx = Transaction([stubbed_sender], msg, blockhash) + resp = test_http_client.send_transaction(transfer_tx) assert_valid_response(resp) txn_id = resp.value # Txn needs to be finalized in order to parse the logs. diff --git a/tests/integration/test_websockets.py b/tests/integration/test_websockets.py index b88ddc8f..db8bf7f0 100644 --- a/tests/integration/test_websockets.py +++ b/tests/integration/test_websockets.py @@ -6,6 +6,7 @@ import pytest from solders import system_program as sp from solders.keypair import Keypair +from solders.message import Message from solders.pubkey import Pubkey from solders.rpc.config import RpcTransactionLogsFilter, RpcTransactionLogsFilterMentions from solders.rpc.requests import AccountSubscribe, AccountUnsubscribe, Body, LogsSubscribe, LogsUnsubscribe @@ -27,7 +28,7 @@ from solana.rpc.async_api import AsyncClient from solana.rpc.commitment import Finalized from solana.rpc.websocket_api import SolanaWsClientProtocol, connect -from solana.transaction import Transaction +from solders.transaction import Transaction from ..utils import AIRDROP_AMOUNT @@ -283,10 +284,11 @@ async def test_program_subscribe( ): """Test program subscription.""" program, owned = program_subscribed - instruction = sp.assign(sp.AssignParams(pubkey=owned.pubkey(), owner=program.pubkey())) - transaction = Transaction() - transaction.add(instruction) - await test_http_client_async.send_transaction(transaction, owned) + ixs = [sp.assign(sp.AssignParams(pubkey=owned.pubkey(), owner=program.pubkey()))] + blockhash = (await test_http_client_async.get_latest_blockhash()).value.blockhash + msg = Message.new_with_blockhash(ixs, owned.pubkey(), blockhash) + transaction = Transaction([owned], msg, blockhash) + await test_http_client_async.send_transaction(transaction) main_resp = await websocket.recv() msg = main_resp[0] assert isinstance(msg, ProgramNotification) diff --git a/tests/unit/test_confirmed_block.py b/tests/unit/test_confirmed_block.py deleted file mode 100644 index f0e49de8..00000000 --- a/tests/unit/test_confirmed_block.py +++ /dev/null @@ -1,57 +0,0 @@ -"""Test get confirmed block.""" - -from solders.keypair import Keypair -from solders.signature import Signature -from solders.system_program import TransferParams, transfer - -import solana.transaction as txlib - - -def test_verify_confirmed_block(stubbed_blockhash): - """Test verifying signature in a confirmed block.""" - kp0, kp1, kp2, kp3 = (Keypair() for _ in range(4)) - # Create a couple signed transaction - txn1 = txlib.Transaction(recent_blockhash=stubbed_blockhash).add( - transfer(TransferParams(from_pubkey=kp0.pubkey(), to_pubkey=kp1.pubkey(), lamports=123)) - ) - txn1.sign(kp0) - txn2 = txlib.Transaction(recent_blockhash=stubbed_blockhash).add( - transfer(TransferParams(from_pubkey=kp2.pubkey(), to_pubkey=kp3.pubkey(), lamports=456)) - ) - txn2.sign(kp2) - # Build confirmed_block with dummy data for blockhases and balances - confirmed_block = { - "blockhash": stubbed_blockhash, - "previousBlockhash": stubbed_blockhash, - "transactions": [ - { - "transaction": txn1, - "meta": { - "fee": 0, - "preBalances": [100000, 100000, 1, 1, 1], - "postBalances": [99877, 100123, 1, 1, 1], - "status": {"Ok": None}, - "err": None, - }, - }, - { - "transaction": txn2, - "meta": { - "fee": 0, - "preBalances": [100000, 100000, 1, 1, 1], - "postBalances": [99544, 100456, 1, 1, 1], - "status": {"Ok": None}, - "err": None, - }, - }, - ], - "rewards": [], - } - # Verify signatures in confirmed_block - assert all(tx_with_meta["transaction"].verify_signatures() for tx_with_meta in confirmed_block["transactions"]) - # Test block with bogus signature - bogus_signature = Signature.default() - bogus_txn1 = txlib.Transaction.populate(txn1.compile_message(), [bogus_signature]) - bad_confirmed_block = confirmed_block - bad_confirmed_block["transactions"][0]["transaction"] = bogus_txn1 - assert not all(tx_with_meta["transaction"].verify_signatures() for tx_with_meta in confirmed_block["transactions"]) diff --git a/tests/unit/test_transaction.py b/tests/unit/test_transaction.py deleted file mode 100644 index 17bb82fa..00000000 --- a/tests/unit/test_transaction.py +++ /dev/null @@ -1,491 +0,0 @@ -"""Unit tests for solana.transaction.""" -from base64 import b64decode, b64encode - -import pytest -import solders.system_program as sp -from solders.hash import Hash as Blockhash -from solders.instruction import AccountMeta, CompiledInstruction -from solders.keypair import Keypair -from solders.message import Message -from solders.message import Message as SoldersMessage -from solders.pubkey import Pubkey -from solders.signature import Signature -from solders.transaction import Transaction as SoldersTx - -import solana.transaction as txlib - - -def example_tx(stubbed_blockhash, kp0: Keypair, kp1: Keypair, kp2: Keypair) -> txlib.Transaction: - """Example tx for testing.""" - ixn = txlib.Instruction( - program_id=Pubkey.default(), - data=bytes([0, 0, 0, 0]), - accounts=[ - AccountMeta(kp0.pubkey(), True, True), - AccountMeta(kp1.pubkey(), True, True), - AccountMeta(kp2.pubkey(), True, True), - ], - ) - return txlib.Transaction(fee_payer=kp0.pubkey(), instructions=[ixn], recent_blockhash=stubbed_blockhash) - - -def test_to_solders(stubbed_blockhash: Blockhash) -> None: - """Test converting a Transaction to solders.""" - kp1, kp2 = Keypair(), Keypair() - transfer = sp.transfer(sp.TransferParams(from_pubkey=kp1.pubkey(), to_pubkey=kp2.pubkey(), lamports=123)) - solders_transfer = sp.transfer(sp.TransferParams(from_pubkey=kp1.pubkey(), to_pubkey=kp2.pubkey(), lamports=123)) - assert transfer.data == solders_transfer.data - txn = txlib.Transaction(recent_blockhash=stubbed_blockhash).add(transfer) - solders_msg = SoldersMessage.new_with_blockhash([solders_transfer], None, stubbed_blockhash) - solders_txn = SoldersTx.new_unsigned(solders_msg) - assert txn.to_solders() == solders_txn - assert txlib.Transaction.from_solders(solders_txn) == txn - - -def test_sign_partial(stubbed_blockhash): - """Test partially sigining a transaction.""" - keypair0 = Keypair() - keypair1 = Keypair() - keypair2 = Keypair() - ixn = txlib.Instruction( - program_id=Pubkey.default(), - data=bytes([0, 0, 0, 0]), - accounts=[ - AccountMeta(keypair0.pubkey(), True, True), - AccountMeta(keypair1.pubkey(), True, True), - AccountMeta(keypair2.pubkey(), True, True), - ], - ) - txn = txlib.Transaction(fee_payer=keypair0.pubkey(), instructions=[ixn], recent_blockhash=stubbed_blockhash) - assert txn.to_solders().message.header.num_required_signatures == 3 - txn.sign_partial(keypair0, keypair2) - assert not txn.to_solders().is_signed() - txn.sign_partial(keypair1) - assert txn.to_solders().is_signed() - expected_tx = txlib.Transaction(fee_payer=keypair0.pubkey(), instructions=[ixn], recent_blockhash=stubbed_blockhash) - expected_tx.sign(keypair0, keypair1, keypair2) - assert txn == expected_tx - - -def test_recent_blockhash_setter(stubbed_blockhash): - """Test the recent_blockhash setter property works.""" - kp0, kp1, kp2 = Keypair(), Keypair(), Keypair() - tx0 = example_tx(stubbed_blockhash, kp0, kp1, kp2) - tx1 = example_tx(stubbed_blockhash, kp0, kp1, kp2) - tx1.recent_blockhash = tx0.recent_blockhash - assert tx0 == tx1 - - -def test_transfer_signatures(stubbed_blockhash): - """Test signing transfer transactions.""" - kp1, kp2 = Keypair(), Keypair() - transfer1 = sp.transfer(sp.TransferParams(from_pubkey=kp1.pubkey(), to_pubkey=kp2.pubkey(), lamports=123)) - transfer2 = sp.transfer(sp.TransferParams(from_pubkey=kp2.pubkey(), to_pubkey=kp1.pubkey(), lamports=123)) - txn = txlib.Transaction(recent_blockhash=stubbed_blockhash) - txn.add(transfer1, transfer2) - txn.sign(kp1, kp2) - - expected = txlib.Transaction.populate(txn.compile_message(), txn.signatures) - assert txn == expected - - -def test_dedup_signatures(stubbed_blockhash): - """Test signature deduplication.""" - kp1, kp2 = Keypair(), Keypair() - transfer1 = sp.transfer(sp.TransferParams(from_pubkey=kp1.pubkey(), to_pubkey=kp2.pubkey(), lamports=123)) - transfer2 = sp.transfer(sp.TransferParams(from_pubkey=kp1.pubkey(), to_pubkey=kp2.pubkey(), lamports=123)) - txn = txlib.Transaction(recent_blockhash=stubbed_blockhash).add(transfer1, transfer2) - txn.sign(kp1) - - -def test_wire_format_and_desrialize(stubbed_blockhash, stubbed_receiver, stubbed_sender): - """Test serialize/derialize transaction to/from wire format.""" - transfer = sp.transfer( - sp.TransferParams(from_pubkey=stubbed_sender.pubkey(), to_pubkey=stubbed_receiver, lamports=49) - ) - expected_txn = txlib.Transaction(recent_blockhash=stubbed_blockhash).add(transfer) - expected_txn.sign(stubbed_sender) - wire_txn = b64decode( - b"AVuErQHaXv0SG0/PchunfxHKt8wMRfMZzqV0tkC5qO6owYxWU2v871AoWywGoFQr4z+q/7mE8lIufNl/kxj+nQ0BAAEDE5j2" - b"LG0aRXxRumpLXz29L2n8qTIWIY3ImX5Ba9F9k8r9Q5/Mtmcn8onFxt47xKj+XdXXd3C8j/FcPu7csUrz/AAAAAAAAAAAAAAA" - b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAxJrndgN4IFTxep3s6kO0ROug7bEsbx0xxuDkqEvwUusBAgIAAQwCAAAAMQAAAAAAAAA=" - ) - txn = txlib.Transaction.deserialize(wire_txn) - assert txn == expected_txn - assert wire_txn == expected_txn.serialize() - - -def test_populate(): - """Test populating transaction with a message and two signatures.""" - account_keys = [Pubkey([0] * 31 + [i + 1]) for i in range(5)] - msg = Message.new_with_compiled_instructions( - num_required_signatures=2, - num_readonly_signed_accounts=0, - num_readonly_unsigned_accounts=3, - account_keys=account_keys, - instructions=[CompiledInstruction(accounts=bytes([1, 2, 3]), data=bytes([9] * 5), program_id_index=4)], - recent_blockhash=Blockhash.default(), - ) - signatures = [Signature(bytes([1] * Signature.LENGTH)), Signature(bytes([2] * Signature.LENGTH))] - transaction = txlib.Transaction.populate(msg, signatures) - assert len(transaction.instructions) == len(msg.instructions) - assert len(transaction.signatures) == len(signatures) - assert transaction.recent_blockhash == msg.recent_blockhash - - -def test_serialize_unsigned_transaction(stubbed_blockhash, stubbed_receiver, stubbed_sender): - """Test to serialize an unsigned transaction.""" - transfer = sp.transfer( - sp.TransferParams(from_pubkey=stubbed_sender.pubkey(), to_pubkey=stubbed_receiver, lamports=49) - ) - txn = txlib.Transaction(recent_blockhash=stubbed_blockhash).add(transfer) - assert txn.signatures == (Signature.default(),) - # Empty signature array fails - with pytest.raises(AttributeError): - txn.serialize() - assert txn.signatures == (Signature.default(),) - - # Set fee payer - txn.fee_payer = stubbed_sender.pubkey() - # Serialize message - assert b64encode(txn.serialize_message()) == ( - b"AQABAxOY9ixtGkV8UbpqS189vS9p/KkyFiGNyJl+QWvRfZPK/UOfzLZnJ/KJxcbeO8So/l3V13dwvI/xXD7u3LFK8/wAAAAAAAAA" - b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMSa53YDeCBU8Xqd7OpDtETroO2xLG8dMcbg5KhL8FLrAQICAAEMAgAAADEAAAAAAAAA" - ) - assert len(txn.instructions) == 1 - # Signature array populated with null signatures fails - with pytest.raises(AttributeError): - txn.serialize() - assert txn.signatures == (Signature.default(),) - # Properly signed transaction succeeds - txn.sign(stubbed_sender) - assert len(txn.instructions) == 1 - expected_serialization = b64decode( - b"AVuErQHaXv0SG0/PchunfxHKt8wMRfMZzqV0tkC5qO6owYxWU2v871AoWywGoFQr4z+q/7mE8lIufNl/kxj+nQ0BAAEDE5j2" - b"LG0aRXxRumpLXz29L2n8qTIWIY3ImX5Ba9F9k8r9Q5/Mtmcn8onFxt47xKj+XdXXd3C8j/FcPu7csUrz/AAAAAAAAAAAAAAA" - b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAxJrndgN4IFTxep3s6kO0ROug7bEsbx0xxuDkqEvwUusBAgIAAQwCAAAAMQAAAAAAAAA=" - ) - assert txn.serialize() == expected_serialization - assert len(txn.signatures) == 1 - assert txn.signatures != (Signature.default(),) - - -def test_serialize_unsigned_transaction_without_verifying_signatures( - stubbed_blockhash, stubbed_receiver, stubbed_sender -): - """Test to serialize an unsigned transaction without verifying the signatures.""" - transfer = sp.transfer( - sp.TransferParams(from_pubkey=stubbed_sender.pubkey(), to_pubkey=stubbed_receiver, lamports=49) - ) - txn = txlib.Transaction(recent_blockhash=stubbed_blockhash).add(transfer) - assert txn.signatures == (Signature.default(),) - - # empty signatures should not fail - txn.serialize(verify_signatures=False) - assert txn.signatures == (Signature.default(),) - - # Set fee payer - txn.fee_payer = stubbed_sender.pubkey() - # Serialize message - assert b64encode(txn.serialize_message()) == ( - b"AQABAxOY9ixtGkV8UbpqS189vS9p/KkyFiGNyJl+QWvRfZPK/UOfzLZnJ/KJxcbeO8So/l3V13dwvI/xXD7u3LFK8/wAAAAAAAAA" - b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMSa53YDeCBU8Xqd7OpDtETroO2xLG8dMcbg5KhL8FLrAQICAAEMAgAAADEAAAAAAAAA" - ) - assert len(txn.instructions) == 1 - # Signature array populated with null signatures should not fail - txn.serialize(verify_signatures=False) - assert txn.signatures == (Signature.default(),) - - -def test_sort_account_metas(stubbed_blockhash): - """Test AccountMeta sorting after calling Transaction.compile_message().""" - # S6EA7XsNyxg4yx4DJRMm7fP21jgZb1fuzBAUGhgVtkP - signer_one = Keypair.from_seed( - bytes( - [ - 216, - 214, - 184, - 213, - 199, - 75, - 129, - 160, - 237, - 96, - 96, - 228, - 46, - 251, - 146, - 3, - 71, - 162, - 37, - 117, - 121, - 70, - 143, - 16, - 128, - 78, - 53, - 189, - 222, - 230, - 165, - 249, - ] - ) - ) - - # BKdt9U6V922P17ui81dzLoqgSY2B5ds1UD13rpwFB2zi - receiver_one = Keypair.from_seed( - bytes( - [ - 3, - 140, - 94, - 243, - 0, - 38, - 92, - 138, - 52, - 79, - 153, - 83, - 42, - 236, - 220, - 82, - 227, - 187, - 101, - 104, - 126, - 159, - 103, - 100, - 29, - 183, - 242, - 68, - 144, - 184, - 114, - 211, - ] - ) - ) - - # DtDZCnXEN69n5W6rN5SdJFgedrWdK8NV9bsMiJekNRyu - signer_two = Keypair.from_seed( - bytes( - [ - 177, - 182, - 154, - 154, - 5, - 145, - 253, - 138, - 211, - 126, - 222, - 195, - 21, - 64, - 117, - 211, - 225, - 47, - 115, - 31, - 247, - 242, - 80, - 195, - 38, - 8, - 236, - 155, - 255, - 27, - 20, - 142, - ] - ) - ) - - # FXgds3n6SNCoVVV4oELSumv8nKzAfqSgmeu7cNPikKFT - receiver_two = Keypair.from_seed( - bytes( - [ - 180, - 204, - 139, - 131, - 244, - 6, - 180, - 121, - 191, - 193, - 45, - 109, - 198, - 50, - 163, - 140, - 34, - 4, - 172, - 76, - 129, - 45, - 194, - 83, - 192, - 112, - 76, - 58, - 32, - 174, - 49, - 248, - ] - ) - ) - - # C2UwQHqJ3BmEJHSMVmrtZDQGS2fGv8fZrWYGi18nHF5k - signer_three = Keypair.from_seed( - bytes( - [ - 29, - 79, - 73, - 16, - 137, - 117, - 183, - 2, - 131, - 0, - 209, - 142, - 134, - 100, - 190, - 35, - 95, - 220, - 200, - 163, - 247, - 237, - 161, - 70, - 226, - 223, - 100, - 148, - 49, - 202, - 154, - 180, - ] - ) - ) - - # 8YPqwYXZtWPd31puVLEUPamS4wTv6F89n8nXDA5Ce2Bg - receiver_three = Keypair.from_seed( - bytes( - [ - 167, - 102, - 49, - 166, - 202, - 0, - 132, - 182, - 239, - 182, - 252, - 59, - 25, - 103, - 76, - 217, - 65, - 215, - 210, - 159, - 168, - 50, - 10, - 229, - 144, - 231, - 221, - 74, - 182, - 161, - 52, - 193, - ] - ) - ) - - fee_payer = signer_one - sorted_signers = sorted([x.pubkey() for x in [signer_one, signer_two, signer_three]], key=str) - sorted_signers_excluding_fee_payer = [x for x in sorted_signers if str(x) != str(fee_payer.pubkey())] - sorted_receivers = sorted([x.pubkey() for x in [receiver_one, receiver_two, receiver_three]], key=str) - - txn = txlib.Transaction(recent_blockhash=stubbed_blockhash) - txn.fee_payer = fee_payer.pubkey() - - # Add three transfer transactions - txn.add( - sp.transfer( - sp.TransferParams( - from_pubkey=signer_one.pubkey(), - to_pubkey=receiver_one.pubkey(), - lamports=2_000_000, - ) - ) - ) - txn.add( - sp.transfer( - sp.TransferParams( - from_pubkey=signer_two.pubkey(), - to_pubkey=receiver_two.pubkey(), - lamports=2_000_000, - ) - ) - ) - txn.add( - sp.transfer( - sp.TransferParams( - from_pubkey=signer_three.pubkey(), - to_pubkey=receiver_three.pubkey(), - lamports=2_000_000, - ) - ) - ) - - tx_msg = txn.compile_message() - - js_msg_b64_check = b"AwABBwZtbiRMvgQjcE2kVx9yon8XqPSO5hwc2ApflnOZMu0Qo9G5/xbhB0sp8/03Rv9x4MKSkQ+k4LB6lNLvCgKZ/ju/aw+EyQpTObVa3Xm+NA1gSTzutgFCTfkDto/0KtuIHHAMpKRb92NImxKeWQJ2/291j6nTzFj1D6nW25p7TofHmVsGt8uFnTv7+8vsWZ0uN7azdxa+jCIIm4WzKK+4uKfX39t5UA7S1soBQaJkTGOQkSbBo39gIjDkbW0TrevslgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxJrndgN4IFTxep3s6kO0ROug7bEsbx0xxuDkqEvwUusDBgIABAwCAAAAgIQeAAAAAAAGAgIFDAIAAACAhB4AAAAAAAYCAQMMAgAAAICEHgAAAAAA" # noqa: E501 pylint: disable=line-too-long - - assert b64encode(bytes(tx_msg)) == js_msg_b64_check - - # Transaction should organize AccountMetas by pubkey - assert tx_msg.account_keys[0] == fee_payer.pubkey() - assert tx_msg.account_keys[1] == sorted_signers_excluding_fee_payer[0] - assert tx_msg.account_keys[2] == sorted_signers_excluding_fee_payer[1] - assert tx_msg.account_keys[3] == sorted_receivers[0] - assert tx_msg.account_keys[4] == sorted_receivers[1] - assert tx_msg.account_keys[5] == sorted_receivers[2] diff --git a/tests/unit/test_vote_program.py b/tests/unit/test_vote_program.py index ec8c1a48..f24a3ad3 100644 --- a/tests/unit/test_vote_program.py +++ b/tests/unit/test_vote_program.py @@ -3,9 +3,8 @@ from solders.hash import Hash from solders.keypair import Keypair +from solders.message import Message from solders.pubkey import Pubkey - -import solana.transaction as txlib import solana.vote_program as vp @@ -80,19 +79,20 @@ def test_withdraw_from_vote_account(): ) vote_account_pubkey = Pubkey.from_string("CWqJy1JpmBcx7awpeANfrPk6AsQKkmego8ujjaYPGFEk") receiver_account_pubkey = Pubkey.from_string("A1V5gsis39WY42djdTKUFsgE5oamk4nrtg16WnKTuzZK") - - txn = txlib.Transaction(fee_payer=withdrawer_keypair.pubkey()) - txn.recent_blockhash = Hash.from_string("Add1tV7kJgNHhTtx3Dgs6dhC7kyXrGJQZ2tJGW15tLDH") - - txn.add( - vp.withdraw_from_vote_account( - vp.WithdrawFromVoteAccountParams( - vote_account_from_pubkey=vote_account_pubkey, - to_pubkey=receiver_account_pubkey, - withdrawer=withdrawer_keypair.pubkey(), - lamports=2_000_000_000, + recent_blockhash = Hash.from_string("Add1tV7kJgNHhTtx3Dgs6dhC7kyXrGJQZ2tJGW15tLDH") + msg = Message.new_with_blockhash( + [ + vp.withdraw_from_vote_account( + vp.WithdrawFromVoteAccountParams( + vote_account_from_pubkey=vote_account_pubkey, + to_pubkey=receiver_account_pubkey, + withdrawer=withdrawer_keypair.pubkey(), + lamports=2_000_000_000, + ) ) - ) + ], + withdrawer_keypair.pubkey(), + recent_blockhash, ) # solana withdraw-from-vote-account --dump-transaction-message \ @@ -108,7 +108,7 @@ def test_withdraw_from_vote_account(): b"AQABBDqF5SfUR/5I9i2gnIHHEr01j2JItmpFHSaRd74NaZ1whdju9KDr87dR4CFbvp8kmkq1rSYitXg2nDzw1kcQsBarFQYO0flqHdOoQpaNxOZ8eSlkLWHns0kvxLHtDo6WbQdhSB01dHS7fE12JOvTvbPYNV5z0RBD/A2jU4AAAAAAjxrQaMS7FjmaR++mvFr3XE6XbzMUTMJUIpITrUWBzGwBAwMCAQAMAwAAAACUNXcAAAAA" # noqa: E501 pylint: disable=line-too-long ) - serialized_message = txn.serialize_message() + serialized_message = bytes(msg) assert serialized_message == js_wire_msg # XXX: Cli message serialization do not sort on account metas producing discrepency