diff --git a/docs/api/plugins/aea_ledger_solana/account.md b/docs/api/plugins/aea_ledger_solana/account.md index 83742f9a29..8be7d6b593 100644 --- a/docs/api/plugins/aea_ledger_solana/account.md +++ b/docs/api/plugins/aea_ledger_solana/account.md @@ -39,7 +39,7 @@ True if the `pubkey` can be loaded as a read-write account. ```python @classmethod -def from_solders(cls, meta: instruction.AccountMeta) +def from_solders(cls, meta: AccountMeta) ``` Convert from a `solders` AccountMeta. @@ -49,7 +49,7 @@ Convert from a `solders` AccountMeta. #### to`_`solders ```python -def to_solders() -> instruction.AccountMeta +def to_solders() -> AccountMeta ``` Convert to a `solders` AccountMeta. diff --git a/docs/api/plugins/aea_ledger_solana/crypto.md b/docs/api/plugins/aea_ledger_solana/crypto.md index a365608480..9490509430 100644 --- a/docs/api/plugins/aea_ledger_solana/crypto.md +++ b/docs/api/plugins/aea_ledger_solana/crypto.md @@ -32,6 +32,17 @@ Instantiate a solana crypto object. - `password`: the password to encrypt/decrypt the private key. - `extra_entropy`: add extra randomness to whatever randomness from OS. + + +#### pubkey + +```python +@property +def pubkey() -> Pubkey +``` + +Pubkey object. + #### private`_`key diff --git a/docs/api/plugins/aea_ledger_solana/solana.md b/docs/api/plugins/aea_ledger_solana/solana.md index 2b83273bec..81529dbaa4 100644 --- a/docs/api/plugins/aea_ledger_solana/solana.md +++ b/docs/api/plugins/aea_ledger_solana/solana.md @@ -28,6 +28,17 @@ Initialize the Solana ledger APIs. - `kwargs`: keyword arguments + + +#### api + +```python +@property +def api() -> SolanaApiClient +``` + +Get the underlying API object. + #### latest`_`hash @@ -39,6 +50,99 @@ def latest_hash() Get the latest hash. + + +#### system`_`program + +```python +@property +def system_program() -> Pubkey +``` + +System program. + + + +#### sol`_`to`_`lamp + +```python +@staticmethod +def sol_to_lamp(sol: float) -> int +``` + +Solana to lamport value. + + + +#### to`_`account`_`meta + +```python +@classmethod +def to_account_meta(cls, pubkey: Union[Pubkey, str], is_signer: bool, + is_writable: bool) -> AccountMeta +``` + +To account meta. + + + +#### to`_`pubkey + +```python +@staticmethod +def to_pubkey(key: Union[SolanaCrypto, Keypair, Pubkey, str]) -> Pubkey +``` + +To pubkey. + + + +#### to`_`keypair + +```python +@staticmethod +def to_keypair(key: Union[SolanaCrypto, Keypair, str]) -> Pubkey +``` + +To keypair object. + + + +#### pda + +```python +@staticmethod +def pda(seeds: Sequence[bytes], program_id: Pubkey) -> Pubkey +``` + +Create TX PDA + + + +#### create`_`pda + +```python +@staticmethod +def create_pda(from_address: str, new_account_address: str, base_address: str, + seed: str, lamports: int, space: int, program_id: str) +``` + +Build a create pda transaction. + +**Arguments**: + +- `from_address`: the sender public key +- `new_account_address`: the new account public key +- `base_address`: base address +- `seed`: seed +- `lamports`: the amount of lamports to send +- `space`: the space to allocate +- `program_id`: the program id + +**Returns**: + +the tx, if present + #### wait`_`get`_`receipt @@ -61,17 +165,6 @@ def construct_and_settle_tx(account1: SolanaCrypto, account2: SolanaCrypto, Construct and settle a transaction. - - -#### api - -```python -@property -def api() -> SolanaApiClient -``` - -Get the underlying API object. - #### update`_`with`_`gas`_`estimate @@ -259,32 +352,6 @@ Build a create account transaction. the tx, if present - - -#### create`_`pda - -```python -@staticmethod -def create_pda(from_address: str, new_account_address: str, base_address: str, - seed: str, lamports: int, space: int, program_id: str) -``` - -Build a create pda transaction. - -**Arguments**: - -- `from_address`: the sender public key -- `new_account_address`: the new account public key -- `base_address`: base address -- `seed`: seed -- `lamports`: the amount of lamports to send -- `space`: the space to allocate -- `program_id`: the program id - -**Returns**: - -the tx, if present - #### get`_`contract`_`instance @@ -379,6 +446,67 @@ Prepare a transaction the transaction + + +#### build`_`instruction + +```python +def build_instruction( + contract_instance: Program, + method_name: str, + data: List[Any], + accounts: Dict[str, Pubkey], + remaining_accounts: Optional[List[AccountMeta]] = None) -> JSONLike +``` + +Prepare an instruction + + + +#### serialize`_`tx + +```python +def serialize_tx( + tx: Union[Dict, SolanaTransaction, SoldersTransaction, + SoldersVersionedTransaction] +) -> Dict +``` + +Serialize transaction to solders transaction compatible json object. + + + +#### deserialize`_`tx + +```python +def deserialize_tx( + tx: Union[Dict, SolanaTransaction, SoldersTransaction, + SoldersVersionedTransaction] +) -> SolanaTransaction +``` + +Deserialize transaction to a solana transaction object. + + + +#### serialize`_`ix + +```python +def serialize_ix(ix: Instruction) -> Dict +``` + +Serialize instruction. + + + +#### deserialize`_`ix + +```python +def deserialize_ix(ix: Dict) -> Instruction +``` + +Deserialize instruction. + #### get`_`transaction`_`transfer`_`logs diff --git a/packages/fetchai/skills/erc1155_client/skill.yaml b/packages/fetchai/skills/erc1155_client/skill.yaml index 4d02a9cf39..f6b266cefc 100644 --- a/packages/fetchai/skills/erc1155_client/skill.yaml +++ b/packages/fetchai/skills/erc1155_client/skill.yaml @@ -21,7 +21,7 @@ fingerprint: tests/test_strategy.py: bafybeicbxie3v6vue3gcnru6vsvggcgy3shxwrldis5gppizbuhooslcqa fingerprint_ignore_patterns: [] connections: -- valory/ledger:0.19.0:bafybeigdckv3e6bz6kfloz4ucqrsufft6k4jp6bwkbbcvh4fxvgbmzq3dm +- valory/ledger:0.19.0:bafybeia47rr37ianvwsh77tjjpv3nwif5sywhhy2fbdshnz4a2icwln76a contracts: - fetchai/erc1155:0.22.0:bafybeiff7a6xncyad53o2r7lekpnhexcspze6ocy55xtpzqeuacnlpunm4 protocols: diff --git a/packages/fetchai/skills/erc1155_deploy/skill.yaml b/packages/fetchai/skills/erc1155_deploy/skill.yaml index 89884f61e9..8544f858e2 100644 --- a/packages/fetchai/skills/erc1155_deploy/skill.yaml +++ b/packages/fetchai/skills/erc1155_deploy/skill.yaml @@ -21,7 +21,7 @@ fingerprint: tests/test_strategy.py: bafybeigxtw2j2c7vl6xhdwos62jbtmx62xfgdyadptm5eewmkesmcooyea fingerprint_ignore_patterns: [] connections: -- valory/ledger:0.19.0:bafybeigdckv3e6bz6kfloz4ucqrsufft6k4jp6bwkbbcvh4fxvgbmzq3dm +- valory/ledger:0.19.0:bafybeia47rr37ianvwsh77tjjpv3nwif5sywhhy2fbdshnz4a2icwln76a contracts: - fetchai/erc1155:0.22.0:bafybeiff7a6xncyad53o2r7lekpnhexcspze6ocy55xtpzqeuacnlpunm4 protocols: diff --git a/packages/fetchai/skills/generic_buyer/skill.yaml b/packages/fetchai/skills/generic_buyer/skill.yaml index cf4928dd29..0615a422b9 100644 --- a/packages/fetchai/skills/generic_buyer/skill.yaml +++ b/packages/fetchai/skills/generic_buyer/skill.yaml @@ -19,7 +19,7 @@ fingerprint: tests/test_models.py: bafybeibh72j3n72yseqvmpppucpu5wtidf6ebxbxkfnmrnlh4zv5y5apei fingerprint_ignore_patterns: [] connections: -- valory/ledger:0.19.0:bafybeigdckv3e6bz6kfloz4ucqrsufft6k4jp6bwkbbcvh4fxvgbmzq3dm +- valory/ledger:0.19.0:bafybeia47rr37ianvwsh77tjjpv3nwif5sywhhy2fbdshnz4a2icwln76a contracts: [] protocols: - fetchai/default:1.0.0:bafybeibtqp56jkijwjsohk4z5vqp6pfkiexmnmk5uleteotbsgrypy6gxm diff --git a/packages/fetchai/skills/generic_seller/skill.yaml b/packages/fetchai/skills/generic_seller/skill.yaml index 2756536b7a..8e7c882023 100644 --- a/packages/fetchai/skills/generic_seller/skill.yaml +++ b/packages/fetchai/skills/generic_seller/skill.yaml @@ -20,7 +20,7 @@ fingerprint: tests/test_models.py: bafybeihabrc22zqssit3fmqhxptosy6qz6mx65ukhf5iayvirfv42xrhoq fingerprint_ignore_patterns: [] connections: -- valory/ledger:0.19.0:bafybeigdckv3e6bz6kfloz4ucqrsufft6k4jp6bwkbbcvh4fxvgbmzq3dm +- valory/ledger:0.19.0:bafybeia47rr37ianvwsh77tjjpv3nwif5sywhhy2fbdshnz4a2icwln76a contracts: [] protocols: - fetchai/default:1.0.0:bafybeibtqp56jkijwjsohk4z5vqp6pfkiexmnmk5uleteotbsgrypy6gxm diff --git a/packages/packages.json b/packages/packages.json index 25fda8a245..5b93c1bde5 100644 --- a/packages/packages.json +++ b/packages/packages.json @@ -8,7 +8,7 @@ "protocol/valory/http/1.0.0": "bafybeiejoqgv7finfxo3rcvvovrlj5ccrbgxodjq43uo26ylpowsa3llfe", "protocol/valory/ledger_api/1.0.0": "bafybeige5agrztgzfevyglf7mb4o7pzfttmq4f6zi765y4g2zvftbyowru", "connection/fetchai/stub/0.21.0": "bafybeictgpdqbpyppmoxn2g7jkaxvulihew7zaszv4xyhgvsntq7tqs7wi", - "connection/valory/ledger/0.19.0": "bafybeigdckv3e6bz6kfloz4ucqrsufft6k4jp6bwkbbcvh4fxvgbmzq3dm", + "connection/valory/ledger/0.19.0": "bafybeia47rr37ianvwsh77tjjpv3nwif5sywhhy2fbdshnz4a2icwln76a", "connection/valory/http_server/0.22.0": "bafybeid4nl6ruidpto3ynwjmc76nf42egcroqlhqq6krh2onwktu4ywpne", "connection/valory/p2p_libp2p/0.1.0": "bafybeiaykya7tvir7k5scovjzuagpfcftvptxoi2od5qqqvukwglsrrtzy", "connection/valory/p2p_libp2p_client/0.1.0": "bafybeihge56dn3xep2dzomu7rtvbgo4uc2qqh7ljl3fubqdi2lq44gs5lq", @@ -32,12 +32,12 @@ "connection/valory/http_client/0.23.0": "bafybeiddrfvomrmgvh5yuv2coq7ci72wcdf663stayi3m5aawnj4srggce", "connection/valory/test_libp2p/0.1.0": "bafybeidy7qyswtj2fnh2q3qnusevamllw2ozzu723sh52r4k4gna3ig4e4", "protocol/fetchai/tac/1.0.0": "bafybeiaukfwe7wbpikztprlmrfpphsxqpdzgamkbhvqyz54tl3k73kzsvi", - "skill/fetchai/erc1155_client/0.28.0": "bafybeid3npgiuvgjyocxtxl6ovihrnicd5ezlim4aq4ytl3atnm5yywxmu", - "skill/fetchai/erc1155_deploy/0.30.0": "bafybeie2lqwsqgpv35uy7nztohtukvogntsxqi74x37qsbsx7drgeajtau", + "skill/fetchai/erc1155_client/0.28.0": "bafybeiclxy64l364o7ek3imuwl4fnmtagy3j2coy6l7ga3gyt2lqzqifkq", + "skill/fetchai/erc1155_deploy/0.30.0": "bafybeiftychtjk7pz2ircduucnfpqpxduhk5ddvouzhkaq2vnfssozbgpa", "skill/fetchai/error/0.17.0": "bafybeignei6feootyjzrqdt5j5yx7r4nrzuy6tdgdgsmrncldt5bud2dri", "skill/fetchai/fipa_dummy_buyer/0.2.0": "bafybeid7rzqruvc3fkesueig2mbzy2qsfplieircyjzwbdl7c6q5eauiky", - "skill/fetchai/generic_buyer/0.26.0": "bafybeibiplanh6h2biy27wt26jnzwftfsji77ajp26hcule5q2acjq3fjy", - "skill/fetchai/generic_seller/0.27.0": "bafybeiboyk6q2sl6rdqvcksbg4gnfb766vpexrdcgjmxzm3kjmcsjnsvia", + "skill/fetchai/generic_buyer/0.26.0": "bafybeifyesl5ooduzfmjw4br2avinyxjbndiwsagwsjtxgrxtzchjyhcxy", + "skill/fetchai/generic_seller/0.27.0": "bafybeiablwq6oefmojsgj3yvedvo5b2bu4yosdr7k5cwn4xkcs4hxeb6fm", "skill/fetchai/task_test_skill/0.1.0": "bafybeidv77u2xl52mnxakwvh7fuh46aiwfpteyof4eaptfd4agoi6cdble" }, "third_party": {} diff --git a/packages/valory/connections/ledger/connection.yaml b/packages/valory/connections/ledger/connection.yaml index 972d97b552..f06122b914 100644 --- a/packages/valory/connections/ledger/connection.yaml +++ b/packages/valory/connections/ledger/connection.yaml @@ -16,7 +16,7 @@ fingerprint: tests/conftest.py: bafybeid7vo7e2m76ey5beeadtbxywxx5ukefd5slwbc362rwmhht6i45ou tests/test_contract_dispatcher.py: bafybeiag5lnpc7h25w23ash4hk4cowxsy5buxgpr474l3tfewnhf56eqyq tests/test_ledger.py: bafybeigcedfr3yv3jse3xwrerrgwbelgb56uhgrvdus527d3daekh6dx4m - tests/test_ledger_api.py: bafybeifwdpbds7ujd2uzxcapprdgdp5fn2gpbycsgbbefbsabrucnvrfiq + tests/test_ledger_api.py: bafybeifw5smawex5m2fm6rt4kmunc22kpabalmshh45qb3xnuap33sfgyi fingerprint_ignore_patterns: [] connections: [] protocols: diff --git a/packages/valory/connections/ledger/tests/test_ledger_api.py b/packages/valory/connections/ledger/tests/test_ledger_api.py index bf1c203ec2..c7b85f2dfd 100644 --- a/packages/valory/connections/ledger/tests/test_ledger_api.py +++ b/packages/valory/connections/ledger/tests/test_ledger_api.py @@ -84,11 +84,12 @@ (EthereumCrypto.identifier, EthereumCrypto(ETHEREUM_PRIVATE_KEY_PATH).address), ], ) +# TODO: uncomment gas station strategy config after the gasstation API start gas_strategies = pytest.mark.parametrize( "gas_strategies", [ {"gas_price_strategy": None}, - {"gas_price_strategy": "gas_station"}, + # {"gas_price_strategy": "gas_station"}, # noqa:E800 {"gas_price_strategy": "eip1559"}, { "max_fee_per_gas": 1_000_000_000, diff --git a/plugins/aea-ledger-solana/aea_ledger_solana/account.py b/plugins/aea-ledger-solana/aea_ledger_solana/account.py index 9df4b84af9..0b52a656a2 100644 --- a/plugins/aea-ledger-solana/aea_ledger_solana/account.py +++ b/plugins/aea-ledger-solana/aea_ledger_solana/account.py @@ -19,7 +19,7 @@ """Solana account implementation.""" from dataclasses import dataclass -from solders import instruction +from solders.instruction import AccountMeta from solders.pubkey import Pubkey as PublicKey @@ -35,7 +35,7 @@ class AccountMeta: """True if the `pubkey` can be loaded as a read-write account.""" @classmethod - def from_solders(cls, meta: instruction.AccountMeta): + def from_solders(cls, meta: AccountMeta): """Convert from a `solders` AccountMeta.""" return cls( pubkey=PublicKey.from_bytes(bytes(meta.pubkey)), @@ -43,8 +43,8 @@ def from_solders(cls, meta: instruction.AccountMeta): is_writable=meta.is_writable, ) - def to_solders(self) -> instruction.AccountMeta: + def to_solders(self) -> AccountMeta: """Convert to a `solders` AccountMeta.""" - return instruction.AccountMeta( + return AccountMeta( pubkey=self.pubkey, is_signer=self.is_signer, is_writable=self.is_writable ) diff --git a/plugins/aea-ledger-solana/aea_ledger_solana/crypto.py b/plugins/aea-ledger-solana/aea_ledger_solana/crypto.py index 0bce01ada7..ac57620211 100644 --- a/plugins/aea-ledger-solana/aea_ledger_solana/crypto.py +++ b/plugins/aea-ledger-solana/aea_ledger_solana/crypto.py @@ -27,8 +27,11 @@ import base58 from aea_ledger_solana.constants import _SOLANA from cryptography.fernet import Fernet # type: ignore +from solana.transaction import Transaction as SolanaTransaction from solders.hash import Hash +from solders.instruction import Instruction from solders.keypair import Keypair +from solders.pubkey import Pubkey from solders.transaction import Transaction from aea.common import JSONLike @@ -60,6 +63,11 @@ def __init__( extra_entropy=extra_entropy, ) + @property + def pubkey(self) -> Pubkey: + """Pubkey object.""" + return self.entity.pubkey() + @property def private_key(self) -> str: """ @@ -133,6 +141,32 @@ def sign_transaction( """ Sign a transaction in bytes string form. + :param transaction: the transaction to be signed + :param signers: list of signers + :return: signed transaction + """ + if "ixs" in transaction: + return self._sign_ixs(ix_container=transaction) + return self._sign_tx_legacy(transaction=transaction, signers=signers) + + def _sign_ixs(self, ix_container: JSONLike) -> JSONLike: + """Create a signed transaction from instructions.""" + tx = SolanaTransaction( + fee_payer=self.entity.pubkey(), + recent_blockhash=Hash.from_string(ix_container["recent_blockhash"]), + instructions=[ + Instruction.from_json(json.dumps(ix)) for ix in ix_container["ixs"] + ], + ) + tx.sign(self.entity) + return json.loads(tx.to_solders().to_json()) + + def _sign_tx_legacy( + self, transaction: JSONLike, signers: Optional[list] = None + ) -> JSONLike: + """ + Sign a transaction in bytes string form. + :param transaction: the transaction to be signed :param signers: list of signers :return: signed transaction diff --git a/plugins/aea-ledger-solana/aea_ledger_solana/solana.py b/plugins/aea-ledger-solana/aea_ledger_solana/solana.py index 07d389f7ed..76ef969906 100644 --- a/plugins/aea-ledger-solana/aea_ledger_solana/solana.py +++ b/plugins/aea-ledger-solana/aea_ledger_solana/solana.py @@ -20,7 +20,7 @@ import json import logging import time -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Dict, List, Optional, Sequence, Tuple, Union, cast from aea_ledger_solana.constants import ( DEFAULT_ADDRESS, @@ -32,14 +32,21 @@ from aea_ledger_solana.faucet import SolanaFaucetApi # noqa: F401 from aea_ledger_solana.helper import SolanaHelper from aea_ledger_solana.solana_api import SolanaApiClient -from aea_ledger_solana.transaction import SolanaTransaction +from aea_ledger_solana.transaction import ( + SolanaTransaction, + SoldersTransaction, + SoldersVersionedTransaction, +) from aea_ledger_solana.transaction_instruction import TransactionInstruction from anchorpy import Context, Idl, Program # type: ignore from solana.blockhash import BlockhashCache from solana.transaction import Transaction # type: ignore from solders import system_program as ssp # type: ignore -from solders.instruction import Instruction -from solders.pubkey import Pubkey as PublicKey # type: ignore +from solders.hash import Hash +from solders.instruction import AccountMeta, Instruction +from solders.keypair import Keypair +from solders.pubkey import Pubkey # type: ignore +from solders.pubkey import Pubkey as PublicKey from solders.signature import Signature # type: ignore from solders.system_program import ( # type: ignore; SYS_PROGRAM_ID, CreateAccountParams, @@ -88,12 +95,114 @@ def _get_latest_hash(self): self._hash = blockhash["blockhash"] self.BlockhashCache.set(blockhash=self._hash, slot=result.context.slot) + @property + def api(self) -> SolanaApiClient: + """Get the underlying API object.""" + return self._api + @property def latest_hash(self): """Get the latest hash.""" self._get_latest_hash() return self._hash + @property + def system_program(self) -> Pubkey: + """System program.""" + return SYS_PROGRAM_ID + + @staticmethod + def sol_to_lamp(sol: float) -> int: + """Solana to lamport value.""" + return int(sol * 1000000000) + + @classmethod + def to_account_meta( + cls, + pubkey: Union[Pubkey, str], + is_signer: bool, + is_writable: bool, + ) -> AccountMeta: + """To account meta.""" + return AccountMeta( + pubkey=cls.to_pubkey(pubkey), + is_signer=is_signer, + is_writable=is_writable, + ) + + @staticmethod + def to_pubkey(key: Union[SolanaCrypto, Keypair, Pubkey, str]) -> Pubkey: + """To pubkey.""" + if isinstance(key, Pubkey): + return key + if isinstance(key, Keypair): + return key.pubkey() + if isinstance(key, SolanaCrypto): + return key.entity.pubkey() + try: + return Pubkey.from_string(key) + except BaseException: + return Keypair.from_base58_string(key).pubkey() + + @staticmethod + def to_keypair(key: Union[SolanaCrypto, Keypair, str]) -> Pubkey: + """To keypair object.""" + if isinstance(key, Keypair): + return key + if isinstance(key, SolanaCrypto): + return key.entity + return Keypair.from_base58_string(key).pubkey() + + @staticmethod + def pda( + seeds: Sequence[bytes], + program_id: Pubkey, + ) -> Pubkey: + """Create TX PDA""" + pda, _ = Pubkey.find_program_address( + seeds=seeds, + program_id=program_id, + ) + return pda + + @staticmethod + def create_pda( + from_address: str, + new_account_address: str, + base_address: str, + seed: str, + lamports: int, + space: int, + program_id: str, + ): + """ + Build a create pda transaction. + + :param from_address: the sender public key + :param new_account_address: the new account public key + :param base_address: base address + :param seed: seed + :param lamports: the amount of lamports to send + :param space: the space to allocate + :param program_id: the program id + :return: the tx, if present + """ + params = CreateAccountWithSeedParams( + PublicKey(from_address), + PublicKey(new_account_address), + PublicKey(base_address), + seed, + lamports, + space, + PublicKey(program_id), + ) + createPDAInstruction = TransactionInstruction.from_solders( + ssp.create_account_with_seed(params.to_solders()) + ) + txn = Transaction().add(createPDAInstruction) + tx = txn._solders.to_json() # pylint: disable=protected-access + return json.loads(tx) + def wait_get_receipt( self, transaction_digest: str ) -> Tuple[Optional[JSONLike], bool]: @@ -140,11 +249,6 @@ def construct_and_settle_tx( return transaction_digest, transaction_receipt, is_settled - @property - def api(self) -> SolanaApiClient: - """Get the underlying API object.""" - return self._api - def update_with_gas_estimate(self, transaction: JSONLike) -> JSONLike: """ Attempts to update the transaction with a gas estimate @@ -295,8 +399,7 @@ def _try_send_signed_transaction( `raise_on_try`: bool flag specifying whether the method will raise or log on error (used by `try_decorator`) :return: tx_digest, if present """ - - stxn = SolanaTransaction.from_json(tx_signed) + stxn = self.deserialize_tx(tx=tx_signed) txn_resp = self._api.send_raw_transaction(bytes(stxn.serialize())) retries = 2 while True and retries > 0: @@ -426,44 +529,6 @@ def create_default_account( tx = txn._solders.to_json() # pylint: disable=protected-access return json.loads(tx) - @staticmethod - def create_pda( - from_address: str, - new_account_address: str, - base_address: str, - seed: str, - lamports: int, - space: int, - program_id: str, - ): - """ - Build a create pda transaction. - - :param from_address: the sender public key - :param new_account_address: the new account public key - :param base_address: base address - :param seed: seed - :param lamports: the amount of lamports to send - :param space: the space to allocate - :param program_id: the program id - :return: the tx, if present - """ - params = CreateAccountWithSeedParams( - PublicKey(from_address), - PublicKey(new_account_address), - PublicKey(base_address), - seed, - lamports, - space, - PublicKey(program_id), - ) - createPDAInstruction = TransactionInstruction.from_solders( - ssp.create_account_with_seed(params.to_solders()) - ) - txn = Transaction().add(createPDAInstruction) - tx = txn._solders.to_json() # pylint: disable=protected-access - return json.loads(tx) - def get_contract_instance( self, contract_interface: Dict[str, str], contract_address: Optional[str] = None ) -> Any: @@ -589,22 +654,95 @@ def build_transaction( # pylint: disable=too-many-arguments """ if method_args is None: raise ValueError("`method_args` can not be None") - if method_args["data"] is None: raise ValueError("Data is required") if method_args["accounts"] is None: raise ValueError("Accounts are required") - if "remaining_accounts" not in method_args: - method_args["remaining_accounts"] = None + if tx_args.get("instruction_only", False): + return self.build_instruction( + contract_instance=contract_instance, + method_name=method_name, + data=method_args["data"], + accounts=method_args["accounts"], + remaining_accounts=method_args.get("remaining_accounts"), + ) + tx = contract_instance.transaction[method_name]( + *method_args["data"], + ctx=Context( + accounts=method_args["accounts"], + remaining_accounts=method_args.get("remaining_accounts"), + ), + payer=tx_args.get("payer"), + blockhash=Hash.from_string(self.latest_hash), + ) + return self.serialize_tx(tx=tx) + + def build_instruction( # pylint: disable=too-many-arguments + self, + contract_instance: Program, + method_name: str, + data: List[Any], + accounts: Dict[str, Pubkey], + remaining_accounts: Optional[List[AccountMeta]] = None, + ) -> JSONLike: + """Prepare an instruction""" + return self.serialize_ix( + contract_instance.methods[method_name] + .args(arguments=data) + .accounts(accs=accounts) + .remaining_accounts(accounts=remaining_accounts or []) + .instruction() + ) - data = method_args["data"] - accounts = method_args["accounts"] - remaining_accounts = method_args["remaining_accounts"] + def serialize_tx( + self, + tx: Union[ + Dict, SolanaTransaction, SoldersTransaction, SoldersVersionedTransaction + ], + ) -> Dict: + """Serialize transaction to solders transaction compatible json object.""" + if isinstance(tx, Dict): + return tx + if isinstance(tx, SolanaTransaction): + return json.loads(cast(SolanaTransaction, tx).to_solders().to_json()) + if isinstance(tx, SoldersTransaction): + return json.loads(cast(SoldersTransaction, tx).to_json()) + if isinstance(tx, SoldersVersionedTransaction): + return json.loads( + cast(SoldersVersionedTransaction, tx) + .into_legacy_transaction() + .to_json() + ) + raise ValueError(f"Unknown transction type found `{type(tx)}`") - txn = contract_instance.transaction[method_name]( - *data, ctx=Context(accounts=accounts, remaining_accounts=remaining_accounts) + def deserialize_tx( + self, + tx: Union[ + Dict, SolanaTransaction, SoldersTransaction, SoldersVersionedTransaction + ], + ) -> SolanaTransaction: + """Deserialize transaction to a solana transaction object.""" + if isinstance(tx, SolanaTransaction): + return cast(SolanaTransaction, tx) + if isinstance(tx, SoldersTransaction): + return SolanaTransaction.from_solders(txn=tx) + if isinstance(tx, SoldersVersionedTransaction): + return SolanaTransaction.from_solders(txn=tx.into_legacy_transaction()) + if not isinstance(tx, Dict): + raise ValueError(f"Unknown transction type found `{type(tx)}`") + + # TODO: Safeguard for tx serialized to solders + return SolanaTransaction.from_solders( + SoldersTransaction.from_json(json.dumps(tx)) ) - return json.loads(txn.to_solders().to_json()) + + def serialize_ix(self, ix: Instruction) -> Dict: + """Serialize instruction.""" + return json.loads(ix.to_json()) + + def deserialize_ix(self, ix: Dict) -> Instruction: + """Deserialize instruction.""" + return Instruction.from_json(json.dumps(ix)) def get_transaction_transfer_logs( # pylint: disable=too-many-arguments,too-many-locals self, diff --git a/plugins/aea-ledger-solana/aea_ledger_solana/transaction.py b/plugins/aea-ledger-solana/aea_ledger_solana/transaction.py index 97a515dea5..0e4686d9da 100644 --- a/plugins/aea-ledger-solana/aea_ledger_solana/transaction.py +++ b/plugins/aea-ledger-solana/aea_ledger_solana/transaction.py @@ -21,6 +21,10 @@ from solana.transaction import Transaction from solders.transaction import Transaction as SoldersTransaction +from solders.transaction import VersionedTransaction as BaseSoldersVersionedTransaction + + +SoldersVersionedTransaction = BaseSoldersVersionedTransaction class SolanaTransaction(Transaction): diff --git a/tox.ini b/tox.ini index e53e5627d4..d9f49d814b 100644 --- a/tox.ini +++ b/tox.ini @@ -255,6 +255,7 @@ commands = python -m pip install --no-deps file://{toxinidir}/plugins/aea-cli-ipfs aea packages lock aea --registry-path=./tests/data/packages packages lock + python {toxinidir}/scripts/check_doc_ipfs_hashes.py --fix [testenv:package-version-checks] skipsdist = True