From 79b04d72a3b9977a45ee038a274f208aac2721cb Mon Sep 17 00:00:00 2001 From: gabrielkerekes Date: Sat, 2 Dec 2023 13:28:42 +0100 Subject: [PATCH] feat(solana): add additional info with token account --- common/protob/messages-solana.proto | 18 + core/src/all_modules.py | 4 + core/src/apps/solana/layout.py | 43 +- .../src/apps/solana/predefined_transaction.py | 191 +++++++ core/src/apps/solana/sign_tx.py | 53 +- core/src/apps/solana/token_account.py | 65 +++ core/src/trezor/messages.py | 36 ++ ...test_apps.solana.predefined_transaction.py | 206 +++++++ python/src/trezorlib/cli/solana.py | 28 +- python/src/trezorlib/messages.py | 40 ++ python/src/trezorlib/solana.py | 4 +- .../src/protos/generated/messages_solana.rs | 505 +++++++++++++++++- 12 files changed, 1109 insertions(+), 84 deletions(-) create mode 100644 core/src/apps/solana/predefined_transaction.py create mode 100644 core/src/apps/solana/token_account.py create mode 100644 core/tests/test_apps.solana.predefined_transaction.py diff --git a/common/protob/messages-solana.proto b/common/protob/messages-solana.proto index 6b35c14548..39bdfa43a2 100644 --- a/common/protob/messages-solana.proto +++ b/common/protob/messages-solana.proto @@ -42,6 +42,23 @@ message SolanaAddress { required string address = 1; // Solana address as Base58 encoded string } +/** + * @embed + */ +message SolanaTxTokenAccountInfo { + required string base_address = 1; + required string token_program = 2; + required string token_mint = 3; + required string token_account = 4; +} + +/** + * @embed + */ +message SolanaTxAdditionalInfo { + repeated SolanaTxTokenAccountInfo token_accounts_infos = 1; +} + /** * Request: Ask device to sign a Solana transaction * @start @@ -51,6 +68,7 @@ message SolanaAddress { message SolanaSignTx { repeated uint32 address_n = 1; // BIP-32 path to derive the key to sign with required bytes serialized_tx = 2; // serialized tx to be signed + optional SolanaTxAdditionalInfo additional_info = 3; } /** diff --git a/core/src/all_modules.py b/core/src/all_modules.py index 4c9748e504..aba9d6a111 100644 --- a/core/src/all_modules.py +++ b/core/src/all_modules.py @@ -693,8 +693,12 @@ if not utils.BITCOIN_ONLY: import apps.solana.get_public_key apps.solana.layout import apps.solana.layout + apps.solana.predefined_transaction + import apps.solana.predefined_transaction apps.solana.sign_tx import apps.solana.sign_tx + apps.solana.token_account + import apps.solana.token_account apps.solana.transaction import apps.solana.transaction apps.solana.transaction.instruction diff --git a/core/src/apps/solana/layout.py b/core/src/apps/solana/layout.py index 9550f4a79c..c083923d13 100644 --- a/core/src/apps/solana/layout.py +++ b/core/src/apps/solana/layout.py @@ -15,14 +15,7 @@ from apps.common.paths import address_n_to_str from .types import AddressType if TYPE_CHECKING: - from trezor.ui.layouts import PropertyType - - from .transaction.instructions import ( - AssociatedTokenAccountProgramCreateInstruction, - Instruction, - SystemProgramTransferInstruction, - TokenProgramTransferCheckedInstruction, - ) + from .transaction.instructions import Instruction, SystemProgramTransferInstruction from .types import AddressReference @@ -285,28 +278,30 @@ async def confirm_system_transfer( async def confirm_token_transfer( - create_token_account_instruction: AssociatedTokenAccountProgramCreateInstruction - | None, - transfer_token_instruction: TokenProgramTransferCheckedInstruction, + destination_account: bytes, + token_account: bytes, + token_mint: bytes, + amount: int, + decimals: int, fee: int, signer_path: list[int], blockhash: bytes, ): - recipient_props: list[PropertyType] = [ - ("", base58.encode(transfer_token_instruction.destination_account[0])) - ] - if create_token_account_instruction is not None: - recipient_props.append(("(account will be created)", "")) - - await confirm_properties( - "confirm_recipient", - "Recipient", - recipient_props, + await confirm_value( + title="Recipient", + value=base58.encode(destination_account), + description="", + br_type="confirm_recipient", + br_code=ButtonRequestType.ConfirmOutput, + verb="CONTINUE", + info_items=(("Associated token account:", base58.encode(token_account)),) + if token_account != destination_account + else None, ) await confirm_value( title="Token address", - value=base58.encode(transfer_token_instruction.token_mint[0]), + value=base58.encode(token_mint), description="", br_type="confirm_token_address", br_code=ButtonRequestType.ConfirmOutput, @@ -314,8 +309,8 @@ async def confirm_token_transfer( ) await confirm_custom_transaction( - transfer_token_instruction.amount, - transfer_token_instruction.decimals, + amount, + decimals, "[TOKEN]", fee, signer_path, diff --git a/core/src/apps/solana/predefined_transaction.py b/core/src/apps/solana/predefined_transaction.py new file mode 100644 index 0000000000..eb25c2e664 --- /dev/null +++ b/core/src/apps/solana/predefined_transaction.py @@ -0,0 +1,191 @@ +from typing import TYPE_CHECKING + +from trezor.crypto import base58 + +from .transaction import Transaction +from .transaction.instructions import ( + AssociatedTokenAccountProgramCreateInstruction, + Instruction, + Token2022ProgramTransferCheckedInstruction, + TokenProgramTransferCheckedInstruction, +) + +if TYPE_CHECKING: + from trezor.messages import SolanaTxAdditionalInfo + + TransferTokenInstruction = ( + TokenProgramTransferCheckedInstruction + | Token2022ProgramTransferCheckedInstruction + ) + + +def get_token_transfer_instructions( + instructions: list[Instruction], +) -> list[TransferTokenInstruction]: + return [ + instruction + for instruction in instructions + if TokenProgramTransferCheckedInstruction.is_type_of(instruction) + or Token2022ProgramTransferCheckedInstruction.is_type_of(instruction) + ] + + +def get_create_associated_token_account_instructions( + instructions: list[Instruction], +) -> list[AssociatedTokenAccountProgramCreateInstruction]: + return [ + instruction + for instruction in instructions + if AssociatedTokenAccountProgramCreateInstruction.is_type_of(instruction) + ] + + +def is_predefined_token_transfer( + instructions: list[Instruction], +): + """ + Checks that the transaction consists of one or zero create token account instructions + and one or more transfer token instructions. Also checks that the token program, token mint + and destination in the instructions are the same. I.e. valid instructions can be: + + [transfer] + [transfer, *transfer] + [create account, transfer] + [create account, transfer, *transfer] + """ + create_token_account_instructions = ( + get_create_associated_token_account_instructions(instructions) + ) + transfer_token_instructions = get_token_transfer_instructions(instructions) + + if len(create_token_account_instructions) + len(transfer_token_instructions) != len( + instructions + ): + # there are also other instructions + return False + + if len(create_token_account_instructions) > 1: + # there is more than one create token account instruction + return False + + if ( + len(create_token_account_instructions) == 1 + and instructions[0] != create_token_account_instructions[0] + ): + # create account instruction has to be the first instruction + return False + + if len(transfer_token_instructions) == 0: + # there are no transfer token instructions + return False + + token_program = transfer_token_instructions[0].program_id + token_mint = transfer_token_instructions[0].token_mint[0] + token_account = transfer_token_instructions[0].destination_account[0] + owner = transfer_token_instructions[0].owner[0] + + for transfer_token_instruction in transfer_token_instructions: + if ( + transfer_token_instruction.program_id != token_program + or transfer_token_instruction.token_mint[0] != token_mint + or transfer_token_instruction.destination_account[0] != token_account + or transfer_token_instruction.owner[0] != owner + ): + # there are different token accounts, don't handle as predefined + return False + + # at this point there can only be zero or one create token account instructions + create_token_account_instruction = ( + create_token_account_instructions[0] + if len(create_token_account_instructions) == 1 + else None + ) + + if create_token_account_instruction is not None and ( + create_token_account_instruction.spl_token[0] != base58.decode(token_program) + or create_token_account_instruction.token_mint[0] != token_mint + or create_token_account_instruction.associated_token_account[0] != token_account + ): + # there are different token accounts, don't handle as predefined + return False + + return True + + +async def try_confirm_token_transfer_transaction( + transaction: Transaction, + fee: int, + signer_path: list[int], + blockhash: bytes, + additional_info: SolanaTxAdditionalInfo | None = None, +) -> bool: + from .layout import confirm_token_transfer + from .token_account import try_get_token_account_base_address + + if not is_predefined_token_transfer( + transaction.instructions, + ): + return False + + transfer_token_instructions = get_token_transfer_instructions( + transaction.instructions + ) + + # in is_predefined_token_transfer we made sure that these values are the same + # for all the transfer token instructions + token_program = base58.decode(transfer_token_instructions[0].program_id) + token_mint = transfer_token_instructions[0].token_mint[0] + token_account = transfer_token_instructions[0].destination_account[0] + + base_address = ( + try_get_token_account_base_address( + token_account, + token_program, + token_mint, + additional_info.token_accounts_infos, + ) + if additional_info is not None + else None + ) + + total_token_amount = sum( + [ + transfer_token_instruction.amount + for transfer_token_instruction in transfer_token_instructions + ] + ) + + await confirm_token_transfer( + token_account if base_address is None else base_address, + token_account, + token_mint, + total_token_amount, + transfer_token_instructions[0].decimals, + fee, + signer_path, + blockhash, + ) + return True + + +async def try_confirm_predefined_transaction( + transaction: Transaction, + fee: int, + signer_path: list[int], + blockhash: bytes, + additional_info: SolanaTxAdditionalInfo | None = None, +) -> bool: + from .layout import confirm_system_transfer + from .transaction.instructions import SystemProgramTransferInstruction + + instructions = transaction.instructions + instructions_count = len(instructions) + + if instructions_count == 1: + if SystemProgramTransferInstruction.is_type_of(instructions[0]): + await confirm_system_transfer(instructions[0], fee, signer_path, blockhash) + return True + + return await try_confirm_token_transfer_transaction( + transaction, fee, signer_path, blockhash, additional_info + ) diff --git a/core/src/apps/solana/sign_tx.py b/core/src/apps/solana/sign_tx.py index 8bf8faf978..156baa8704 100644 --- a/core/src/apps/solana/sign_tx.py +++ b/core/src/apps/solana/sign_tx.py @@ -26,6 +26,7 @@ async def sign_tx( from apps.common import seed from .layout import confirm_transaction + from .predefined_transaction import try_confirm_predefined_transaction address_n = msg.address_n # local_cache_attribute serialized_tx = msg.serialized_tx # local_cache_attribute @@ -54,7 +55,7 @@ async def sign_tx( fee = calculate_fee(transaction) if not await try_confirm_predefined_transaction( - transaction, fee, address_n, transaction.blockhash + transaction, fee, address_n, transaction.blockhash, msg.additional_info ): await confirm_instructions(address_n, signer_public_key, transaction) await confirm_transaction( @@ -68,56 +69,6 @@ async def sign_tx( return SolanaTxSignature(signature=signature) -async def try_confirm_predefined_transaction( - transaction: Transaction, fee: int, signer_path: list[int], blockhash: bytes -) -> bool: - from .layout import confirm_system_transfer, confirm_token_transfer - from .transaction.instructions import ( - AssociatedTokenAccountProgramCreateInstruction, - SystemProgramTransferInstruction, - TokenProgramTransferCheckedInstruction, - ) - - instructions = transaction.instructions - instructions_count = len(instructions) - - if instructions_count == 1: - if SystemProgramTransferInstruction.is_type_of(instructions[0]): - await confirm_system_transfer(instructions[0], fee, signer_path, blockhash) - return True - - if TokenProgramTransferCheckedInstruction.is_type_of(instructions[0]): - await confirm_token_transfer( - None, instructions[0], fee, signer_path, blockhash - ) - return True - elif instructions_count == 2: - if AssociatedTokenAccountProgramCreateInstruction.is_type_of( - instructions[0] - ) and TokenProgramTransferCheckedInstruction.is_type_of(instructions[1]): - create_token_account_instruction = instructions[0] - transfer_token_instruction = instructions[1] - - # If the account being created is different from the recipient account we need - # to display all the instruction information. - if ( - create_token_account_instruction.associated_token_account[0] - != transfer_token_instruction.destination_account[0] - ): - return False - - await confirm_token_transfer( - instructions[0], - instructions[1], - fee, - signer_path, - blockhash, - ) - return True - - return False - - async def confirm_instructions( signer_path: list[int], signer_public_key: bytes, transaction: Transaction ) -> None: diff --git a/core/src/apps/solana/token_account.py b/core/src/apps/solana/token_account.py new file mode 100644 index 0000000000..ec8266101f --- /dev/null +++ b/core/src/apps/solana/token_account.py @@ -0,0 +1,65 @@ +from typing import TYPE_CHECKING + +from trezor.crypto import base58 + +if TYPE_CHECKING: + from trezor.messages import SolanaTxTokenAccountInfo + +ASSOCIATED_TOKEN_ACCOUNT_PROGRAM = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" + +SEED_CONSTANT = "ProgramDerivedAddress" + + +def assert_is_associated_token_account( + base_address: bytes, + token_account_address: bytes, + token_program: bytes, + token_mint: bytes, +) -> None: + from trezor.crypto.hashlib import sha256 + + # based on the following sources: + # https://spl.solana.com/associated-token-account#finding-the-associated-token-account-address + # https://github.com/solana-labs/solana/blob/8fbe033eaca693ed8c3e90b19bc3f61b32885e5e/sdk/program/src/pubkey.rs#L495 + for seed_bump in range(255, 0, -1): + seed = ( + base_address + + token_program + + token_mint + + bytes([seed_bump]) + + base58.decode(ASSOCIATED_TOKEN_ACCOUNT_PROGRAM) + + SEED_CONSTANT.encode("utf-8") + ) + + account = sha256(seed).digest() + + if account == token_account_address: + return + + raise ValueError + + +def try_get_token_account_base_address( + token_account_address: bytes, + token_program: bytes, + token_mint: bytes, + token_accounts_infos: list[SolanaTxTokenAccountInfo], +) -> bytes | None: + for token_account_info in token_accounts_infos: + if ( + base58.decode(token_account_info.token_account) == token_account_address + and base58.decode(token_account_info.token_program) == token_program + and base58.decode(token_account_info.token_mint) == token_mint + ): + base_address = base58.decode(token_account_info.base_address) + + assert_is_associated_token_account( + base_address, + token_account_address, + token_program, + token_mint, + ) + + return base_address + + return None diff --git a/core/src/trezor/messages.py b/core/src/trezor/messages.py index b53ac64e0d..456f10a9de 100644 --- a/core/src/trezor/messages.py +++ b/core/src/trezor/messages.py @@ -5257,15 +5257,51 @@ if TYPE_CHECKING: def is_type_of(cls, msg: Any) -> TypeGuard["SolanaAddress"]: return isinstance(msg, cls) + class SolanaTxTokenAccountInfo(protobuf.MessageType): + base_address: "str" + token_program: "str" + token_mint: "str" + token_account: "str" + + def __init__( + self, + *, + base_address: "str", + token_program: "str", + token_mint: "str", + token_account: "str", + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["SolanaTxTokenAccountInfo"]: + return isinstance(msg, cls) + + class SolanaTxAdditionalInfo(protobuf.MessageType): + token_accounts_infos: "list[SolanaTxTokenAccountInfo]" + + def __init__( + self, + *, + token_accounts_infos: "list[SolanaTxTokenAccountInfo] | None" = None, + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["SolanaTxAdditionalInfo"]: + return isinstance(msg, cls) + class SolanaSignTx(protobuf.MessageType): address_n: "list[int]" serialized_tx: "bytes" + additional_info: "SolanaTxAdditionalInfo | None" def __init__( self, *, serialized_tx: "bytes", address_n: "list[int] | None" = None, + additional_info: "SolanaTxAdditionalInfo | None" = None, ) -> None: pass diff --git a/core/tests/test_apps.solana.predefined_transaction.py b/core/tests/test_apps.solana.predefined_transaction.py new file mode 100644 index 0000000000..98742c193a --- /dev/null +++ b/core/tests/test_apps.solana.predefined_transaction.py @@ -0,0 +1,206 @@ +from common import * + +from trezor.crypto import base58 + +from apps.solana.predefined_transaction import is_predefined_token_transfer +from apps.solana.transaction.instruction import Instruction +from apps.solana.transaction.instructions import ( + ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID, + ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID_INS_CREATE, + SYSTEM_PROGRAM_ID, + SYSTEM_PROGRAM_ID_INS_TRANSFER, + TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID_INS_TRANSFER_CHECKED, + TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID_INS_TRANSFER_CHECKED, +) + + +def create_mock_instruction( + program_id: str, instruction_id: int, parsed_data: dict[str, Any] +): + instruction = Instruction( + instruction_data=b"", + program_id=program_id, + accounts=[], + instruction_id=instruction_id, + property_templates=[], + accounts_template=[], + ui_properties=[], + ui_name="", + is_program_supported=True, + is_instruction_supported=True, + supports_multisig=False, + is_deprecated_warning=None, + ) + + instruction.parsed_data = parsed_data + return instruction + + +def create_transfer_token_instruction( + program_id=TOKEN_PROGRAM_ID, + instruction_id=TOKEN_PROGRAM_ID_INS_TRANSFER_CHECKED, + token_mint="GHArwcWCuk9WkUG4XKUbt935rKfmBmywbEWyFxdH3mou", + destination_account="92YgwqTtTWB7qY92JT6mbL2WCmhAs7LPZL4jLcizNfwx", + owner="14CCvQzQzHCVgZM3j9soPnXuJXh1RmCfwLVUcdfbZVBS", +): + return create_mock_instruction( + program_id, + instruction_id, + { + "token_mint": (base58.decode(token_mint),), + "destination_account": (base58.decode(destination_account),), + "owner": (base58.decode(owner),), + }, + ) + + +def create_create_token_account_instruction( + token_mint="GHArwcWCuk9WkUG4XKUbt935rKfmBmywbEWyFxdH3mou", + associated_token_account="92YgwqTtTWB7qY92JT6mbL2WCmhAs7LPZL4jLcizNfwx", + spl_token=TOKEN_PROGRAM_ID, +): + return create_mock_instruction( + ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID, + ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID_INS_CREATE, + { + "token_mint": (base58.decode(token_mint),), + "associated_token_account": (base58.decode(associated_token_account),), + "spl_token": (base58.decode(spl_token),), + }, + ) + + +@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") +class TestSolanaPredefinedTransactions(unittest.TestCase): + def test_is_predefined_token_transfer(self): + # note: if there are multiple transfer instructions they are the same + # in the tests because that's the info the test cares about. In reality + # the instructions can differ in the destination account and amount. + valid_test_cases = [ + [create_transfer_token_instruction()], + [create_transfer_token_instruction(), create_transfer_token_instruction()], + [ + create_create_token_account_instruction(), + create_transfer_token_instruction(), + ], + [ + create_create_token_account_instruction(), + create_transfer_token_instruction(), + create_transfer_token_instruction(), + ], + [ + create_create_token_account_instruction( + spl_token=TOKEN_2022_PROGRAM_ID + ), + create_transfer_token_instruction( + program_id=TOKEN_2022_PROGRAM_ID, + instruction_id=TOKEN_2022_PROGRAM_ID_INS_TRANSFER_CHECKED, + ), + create_transfer_token_instruction( + program_id=TOKEN_2022_PROGRAM_ID, + instruction_id=TOKEN_2022_PROGRAM_ID_INS_TRANSFER_CHECKED, + ), + ], + ] + + invalid_test_cases = [ + # only create account + [ + create_create_token_account_instruction(), + ], + # there are other instructions + [ + create_transfer_token_instruction(), + create_mock_instruction( + SYSTEM_PROGRAM_ID, SYSTEM_PROGRAM_ID_INS_TRANSFER, {} + ), + ], + # multiple create account instructions + [ + create_create_token_account_instruction(), + create_create_token_account_instruction(), + create_transfer_token_instruction(), + ], + # transfer instructions program_id mismatch + [ + create_transfer_token_instruction( + program_id=TOKEN_PROGRAM_ID, + instruction_id=TOKEN_PROGRAM_ID_INS_TRANSFER_CHECKED, + ), + create_transfer_token_instruction( + program_id=TOKEN_2022_PROGRAM_ID, + instruction_id=TOKEN_2022_PROGRAM_ID_INS_TRANSFER_CHECKED, + ), + ], + # transfer instructions token_mint mismatch + [ + create_transfer_token_instruction( + token_mint="GHArwcWCuk9WkUG4XKUbt935rKfmBmywbEWyFxdH3mou" + ), + create_transfer_token_instruction( + token_mint="GZDphoFQJ9m7uRU7TdS8cVDGFvsiQbcaY3n5mdoQHmDj" + ), + ], + # transfer instructions destination mismatch + [ + create_transfer_token_instruction( + destination_account="92YgwqTtTWB7qY92JT6mbL2WCmhAs7LPZL4jLcizNfwx" + ), + create_transfer_token_instruction( + destination_account="74pZnim7gywyschy4MGkW6eZURv1DBXqwHTCqLRk63wz" + ), + ], + # transfer instructions owner mismatch + [ + create_transfer_token_instruction( + owner="14CCvQzQzHCVgZM3j9soPnXuJXh1RmCfwLVUcdfbZVBS" + ), + create_transfer_token_instruction( + owner="BVRFH6vt5bNXub6WnnFRgaHFTcbkjBrf7x1troU1izGg" + ), + ], + # token program mismatch + [ + create_create_token_account_instruction( + spl_token=TOKEN_2022_PROGRAM_ID + ), + create_transfer_token_instruction( + program_id=TOKEN_PROGRAM_ID, + ), + ], + # create account token_mint mismatch + [ + create_create_token_account_instruction( + token_mint="GZDphoFQJ9m7uRU7TdS8cVDGFvsiQbcaY3n5mdoQHmDj" + ), + create_transfer_token_instruction( + token_mint="GHArwcWCuk9WkUG4XKUbt935rKfmBmywbEWyFxdH3mou", + ), + ], + # create account associated_token_account mismatch + [ + create_create_token_account_instruction( + associated_token_account="74pZnim7gywyschy4MGkW6eZURv1DBXqwHTCqLRk63wz" + ), + create_transfer_token_instruction( + destination_account="92YgwqTtTWB7qY92JT6mbL2WCmhAs7LPZL4jLcizNfwx", + ), + ], + # create account is not first + [ + create_transfer_token_instruction(), + create_create_token_account_instruction(), + ], + ] + + for instructions in valid_test_cases: + self.assertTrue(is_predefined_token_transfer(instructions)) + + for instructions in invalid_test_cases: + self.assertFalse(is_predefined_token_transfer(instructions)) + + +if __name__ == "__main__": + unittest.main() diff --git a/python/src/trezorlib/cli/solana.py b/python/src/trezorlib/cli/solana.py index 845e8ed984..3fe80a5164 100644 --- a/python/src/trezorlib/cli/solana.py +++ b/python/src/trezorlib/cli/solana.py @@ -1,4 +1,5 @@ -from typing import TYPE_CHECKING +import json +from typing import TYPE_CHECKING, Optional, TextIO import click @@ -50,12 +51,35 @@ def get_address( @cli.command() @click.argument("serialized_tx", type=str) @click.option("-n", "--address", default=DEFAULT_PATH, help=PATH_HELP) +@click.option("-a", "--additional-information-file", type=click.File("r")) @with_client def sign_tx( client: "TrezorClient", address: str, serialized_tx: str, + additional_information_file: Optional[TextIO], ) -> messages.SolanaTxSignature: """Sign Solana transaction.""" address_n = tools.parse_path(address) - return solana.sign_tx(client, address_n, bytes.fromhex(serialized_tx)) + + additional_information = None + if additional_information_file: + raw_additional_information = json.load(additional_information_file) + additional_information = messages.SolanaTxAdditionalInfo( + token_accounts_infos=[ + messages.SolanaTxTokenAccountInfo( + base_address=token_account["base_address"], + token_program=token_account["token_program"], + token_mint=token_account["token_mint"], + token_account=token_account["token_account"], + ) + for token_account in raw_additional_information["token_accounts_infos"] + ] + ) + + return solana.sign_tx( + client, + address_n, + bytes.fromhex(serialized_tx), + additional_information, + ) diff --git a/python/src/trezorlib/messages.py b/python/src/trezorlib/messages.py index bafaeddcab..36cfd33424 100644 --- a/python/src/trezorlib/messages.py +++ b/python/src/trezorlib/messages.py @@ -6716,11 +6716,49 @@ class SolanaAddress(protobuf.MessageType): self.address = address +class SolanaTxTokenAccountInfo(protobuf.MessageType): + MESSAGE_WIRE_TYPE = None + FIELDS = { + 1: protobuf.Field("base_address", "string", repeated=False, required=True), + 2: protobuf.Field("token_program", "string", repeated=False, required=True), + 3: protobuf.Field("token_mint", "string", repeated=False, required=True), + 4: protobuf.Field("token_account", "string", repeated=False, required=True), + } + + def __init__( + self, + *, + base_address: "str", + token_program: "str", + token_mint: "str", + token_account: "str", + ) -> None: + self.base_address = base_address + self.token_program = token_program + self.token_mint = token_mint + self.token_account = token_account + + +class SolanaTxAdditionalInfo(protobuf.MessageType): + MESSAGE_WIRE_TYPE = None + FIELDS = { + 1: protobuf.Field("token_accounts_infos", "SolanaTxTokenAccountInfo", repeated=True, required=False, default=None), + } + + def __init__( + self, + *, + token_accounts_infos: Optional[Sequence["SolanaTxTokenAccountInfo"]] = None, + ) -> None: + self.token_accounts_infos: Sequence["SolanaTxTokenAccountInfo"] = token_accounts_infos if token_accounts_infos is not None else [] + + class SolanaSignTx(protobuf.MessageType): MESSAGE_WIRE_TYPE = 904 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("serialized_tx", "bytes", repeated=False, required=True), + 3: protobuf.Field("additional_info", "SolanaTxAdditionalInfo", repeated=False, required=False, default=None), } def __init__( @@ -6728,9 +6766,11 @@ class SolanaSignTx(protobuf.MessageType): *, serialized_tx: "bytes", address_n: Optional[Sequence["int"]] = None, + additional_info: Optional["SolanaTxAdditionalInfo"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.serialized_tx = serialized_tx + self.additional_info = additional_info class SolanaTxSignature(protobuf.MessageType): diff --git a/python/src/trezorlib/solana.py b/python/src/trezorlib/solana.py index d2c20cf34a..be7f2e5fcb 100644 --- a/python/src/trezorlib/solana.py +++ b/python/src/trezorlib/solana.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, List, Optional from . import messages from .tools import expect @@ -40,10 +40,12 @@ def sign_tx( client: "TrezorClient", address_n: List[int], serialized_tx: bytes, + additional_info: Optional[messages.SolanaTxAdditionalInfo], ) -> "MessageType": return client.call( messages.SolanaSignTx( address_n=address_n, serialized_tx=serialized_tx, + additional_info=additional_info, ) ) diff --git a/rust/trezor-client/src/protos/generated/messages_solana.rs b/rust/trezor-client/src/protos/generated/messages_solana.rs index 02691ba585..d00ba1e86f 100644 --- a/rust/trezor-client/src/protos/generated/messages_solana.rs +++ b/rust/trezor-client/src/protos/generated/messages_solana.rs @@ -708,6 +708,466 @@ impl ::protobuf::reflect::ProtobufValue for SolanaAddress { type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; } +// @@protoc_insertion_point(message:hw.trezor.messages.solana.SolanaTxTokenAccountInfo) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct SolanaTxTokenAccountInfo { + // message fields + // @@protoc_insertion_point(field:hw.trezor.messages.solana.SolanaTxTokenAccountInfo.base_address) + pub base_address: ::std::option::Option<::std::string::String>, + // @@protoc_insertion_point(field:hw.trezor.messages.solana.SolanaTxTokenAccountInfo.token_program) + pub token_program: ::std::option::Option<::std::string::String>, + // @@protoc_insertion_point(field:hw.trezor.messages.solana.SolanaTxTokenAccountInfo.token_mint) + pub token_mint: ::std::option::Option<::std::string::String>, + // @@protoc_insertion_point(field:hw.trezor.messages.solana.SolanaTxTokenAccountInfo.token_account) + pub token_account: ::std::option::Option<::std::string::String>, + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.solana.SolanaTxTokenAccountInfo.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a SolanaTxTokenAccountInfo { + fn default() -> &'a SolanaTxTokenAccountInfo { + ::default_instance() + } +} + +impl SolanaTxTokenAccountInfo { + pub fn new() -> SolanaTxTokenAccountInfo { + ::std::default::Default::default() + } + + // required string base_address = 1; + + pub fn base_address(&self) -> &str { + match self.base_address.as_ref() { + Some(v) => v, + None => "", + } + } + + pub fn clear_base_address(&mut self) { + self.base_address = ::std::option::Option::None; + } + + pub fn has_base_address(&self) -> bool { + self.base_address.is_some() + } + + // Param is passed by value, moved + pub fn set_base_address(&mut self, v: ::std::string::String) { + self.base_address = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_base_address(&mut self) -> &mut ::std::string::String { + if self.base_address.is_none() { + self.base_address = ::std::option::Option::Some(::std::string::String::new()); + } + self.base_address.as_mut().unwrap() + } + + // Take field + pub fn take_base_address(&mut self) -> ::std::string::String { + self.base_address.take().unwrap_or_else(|| ::std::string::String::new()) + } + + // required string token_program = 2; + + pub fn token_program(&self) -> &str { + match self.token_program.as_ref() { + Some(v) => v, + None => "", + } + } + + pub fn clear_token_program(&mut self) { + self.token_program = ::std::option::Option::None; + } + + pub fn has_token_program(&self) -> bool { + self.token_program.is_some() + } + + // Param is passed by value, moved + pub fn set_token_program(&mut self, v: ::std::string::String) { + self.token_program = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_token_program(&mut self) -> &mut ::std::string::String { + if self.token_program.is_none() { + self.token_program = ::std::option::Option::Some(::std::string::String::new()); + } + self.token_program.as_mut().unwrap() + } + + // Take field + pub fn take_token_program(&mut self) -> ::std::string::String { + self.token_program.take().unwrap_or_else(|| ::std::string::String::new()) + } + + // required string token_mint = 3; + + pub fn token_mint(&self) -> &str { + match self.token_mint.as_ref() { + Some(v) => v, + None => "", + } + } + + pub fn clear_token_mint(&mut self) { + self.token_mint = ::std::option::Option::None; + } + + pub fn has_token_mint(&self) -> bool { + self.token_mint.is_some() + } + + // Param is passed by value, moved + pub fn set_token_mint(&mut self, v: ::std::string::String) { + self.token_mint = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_token_mint(&mut self) -> &mut ::std::string::String { + if self.token_mint.is_none() { + self.token_mint = ::std::option::Option::Some(::std::string::String::new()); + } + self.token_mint.as_mut().unwrap() + } + + // Take field + pub fn take_token_mint(&mut self) -> ::std::string::String { + self.token_mint.take().unwrap_or_else(|| ::std::string::String::new()) + } + + // required string token_account = 4; + + pub fn token_account(&self) -> &str { + match self.token_account.as_ref() { + Some(v) => v, + None => "", + } + } + + pub fn clear_token_account(&mut self) { + self.token_account = ::std::option::Option::None; + } + + pub fn has_token_account(&self) -> bool { + self.token_account.is_some() + } + + // Param is passed by value, moved + pub fn set_token_account(&mut self, v: ::std::string::String) { + self.token_account = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_token_account(&mut self) -> &mut ::std::string::String { + if self.token_account.is_none() { + self.token_account = ::std::option::Option::Some(::std::string::String::new()); + } + self.token_account.as_mut().unwrap() + } + + // Take field + pub fn take_token_account(&mut self) -> ::std::string::String { + self.token_account.take().unwrap_or_else(|| ::std::string::String::new()) + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(4); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "base_address", + |m: &SolanaTxTokenAccountInfo| { &m.base_address }, + |m: &mut SolanaTxTokenAccountInfo| { &mut m.base_address }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "token_program", + |m: &SolanaTxTokenAccountInfo| { &m.token_program }, + |m: &mut SolanaTxTokenAccountInfo| { &mut m.token_program }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "token_mint", + |m: &SolanaTxTokenAccountInfo| { &m.token_mint }, + |m: &mut SolanaTxTokenAccountInfo| { &mut m.token_mint }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "token_account", + |m: &SolanaTxTokenAccountInfo| { &m.token_account }, + |m: &mut SolanaTxTokenAccountInfo| { &mut m.token_account }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "SolanaTxTokenAccountInfo", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for SolanaTxTokenAccountInfo { + const NAME: &'static str = "SolanaTxTokenAccountInfo"; + + fn is_initialized(&self) -> bool { + if self.base_address.is_none() { + return false; + } + if self.token_program.is_none() { + return false; + } + if self.token_mint.is_none() { + return false; + } + if self.token_account.is_none() { + return false; + } + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.base_address = ::std::option::Option::Some(is.read_string()?); + }, + 18 => { + self.token_program = ::std::option::Option::Some(is.read_string()?); + }, + 26 => { + self.token_mint = ::std::option::Option::Some(is.read_string()?); + }, + 34 => { + self.token_account = ::std::option::Option::Some(is.read_string()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if let Some(v) = self.base_address.as_ref() { + my_size += ::protobuf::rt::string_size(1, &v); + } + if let Some(v) = self.token_program.as_ref() { + my_size += ::protobuf::rt::string_size(2, &v); + } + if let Some(v) = self.token_mint.as_ref() { + my_size += ::protobuf::rt::string_size(3, &v); + } + if let Some(v) = self.token_account.as_ref() { + my_size += ::protobuf::rt::string_size(4, &v); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if let Some(v) = self.base_address.as_ref() { + os.write_string(1, v)?; + } + if let Some(v) = self.token_program.as_ref() { + os.write_string(2, v)?; + } + if let Some(v) = self.token_mint.as_ref() { + os.write_string(3, v)?; + } + if let Some(v) = self.token_account.as_ref() { + os.write_string(4, v)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> SolanaTxTokenAccountInfo { + SolanaTxTokenAccountInfo::new() + } + + fn clear(&mut self) { + self.base_address = ::std::option::Option::None; + self.token_program = ::std::option::Option::None; + self.token_mint = ::std::option::Option::None; + self.token_account = ::std::option::Option::None; + self.special_fields.clear(); + } + + fn default_instance() -> &'static SolanaTxTokenAccountInfo { + static instance: SolanaTxTokenAccountInfo = SolanaTxTokenAccountInfo { + base_address: ::std::option::Option::None, + token_program: ::std::option::Option::None, + token_mint: ::std::option::Option::None, + token_account: ::std::option::Option::None, + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for SolanaTxTokenAccountInfo { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("SolanaTxTokenAccountInfo").unwrap()).clone() + } +} + +impl ::std::fmt::Display for SolanaTxTokenAccountInfo { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for SolanaTxTokenAccountInfo { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:hw.trezor.messages.solana.SolanaTxAdditionalInfo) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct SolanaTxAdditionalInfo { + // message fields + // @@protoc_insertion_point(field:hw.trezor.messages.solana.SolanaTxAdditionalInfo.token_accounts_infos) + pub token_accounts_infos: ::std::vec::Vec, + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.solana.SolanaTxAdditionalInfo.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a SolanaTxAdditionalInfo { + fn default() -> &'a SolanaTxAdditionalInfo { + ::default_instance() + } +} + +impl SolanaTxAdditionalInfo { + pub fn new() -> SolanaTxAdditionalInfo { + ::std::default::Default::default() + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(1); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( + "token_accounts_infos", + |m: &SolanaTxAdditionalInfo| { &m.token_accounts_infos }, + |m: &mut SolanaTxAdditionalInfo| { &mut m.token_accounts_infos }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "SolanaTxAdditionalInfo", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for SolanaTxAdditionalInfo { + const NAME: &'static str = "SolanaTxAdditionalInfo"; + + fn is_initialized(&self) -> bool { + for v in &self.token_accounts_infos { + if !v.is_initialized() { + return false; + } + }; + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.token_accounts_infos.push(is.read_message()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + for value in &self.token_accounts_infos { + let len = value.compute_size(); + my_size += 1 + ::protobuf::rt::compute_raw_varint64_size(len) + len; + }; + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + for v in &self.token_accounts_infos { + ::protobuf::rt::write_message_field_with_cached_size(1, v, os)?; + }; + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> SolanaTxAdditionalInfo { + SolanaTxAdditionalInfo::new() + } + + fn clear(&mut self) { + self.token_accounts_infos.clear(); + self.special_fields.clear(); + } + + fn default_instance() -> &'static SolanaTxAdditionalInfo { + static instance: SolanaTxAdditionalInfo = SolanaTxAdditionalInfo { + token_accounts_infos: ::std::vec::Vec::new(), + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for SolanaTxAdditionalInfo { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("SolanaTxAdditionalInfo").unwrap()).clone() + } +} + +impl ::std::fmt::Display for SolanaTxAdditionalInfo { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for SolanaTxAdditionalInfo { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + // @@protoc_insertion_point(message:hw.trezor.messages.solana.SolanaSignTx) #[derive(PartialEq,Clone,Default,Debug)] pub struct SolanaSignTx { @@ -716,6 +1176,8 @@ pub struct SolanaSignTx { pub address_n: ::std::vec::Vec, // @@protoc_insertion_point(field:hw.trezor.messages.solana.SolanaSignTx.serialized_tx) pub serialized_tx: ::std::option::Option<::std::vec::Vec>, + // @@protoc_insertion_point(field:hw.trezor.messages.solana.SolanaSignTx.additional_info) + pub additional_info: ::protobuf::MessageField, // special fields // @@protoc_insertion_point(special_field:hw.trezor.messages.solana.SolanaSignTx.special_fields) pub special_fields: ::protobuf::SpecialFields, @@ -769,7 +1231,7 @@ impl SolanaSignTx { } fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { - let mut fields = ::std::vec::Vec::with_capacity(2); + let mut fields = ::std::vec::Vec::with_capacity(3); let mut oneofs = ::std::vec::Vec::with_capacity(0); fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( "address_n", @@ -781,6 +1243,11 @@ impl SolanaSignTx { |m: &SolanaSignTx| { &m.serialized_tx }, |m: &mut SolanaSignTx| { &mut m.serialized_tx }, )); + fields.push(::protobuf::reflect::rt::v2::make_message_field_accessor::<_, SolanaTxAdditionalInfo>( + "additional_info", + |m: &SolanaSignTx| { &m.additional_info }, + |m: &mut SolanaSignTx| { &mut m.additional_info }, + )); ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( "SolanaSignTx", fields, @@ -796,6 +1263,11 @@ impl ::protobuf::Message for SolanaSignTx { if self.serialized_tx.is_none() { return false; } + for v in &self.additional_info { + if !v.is_initialized() { + return false; + } + }; true } @@ -811,6 +1283,9 @@ impl ::protobuf::Message for SolanaSignTx { 18 => { self.serialized_tx = ::std::option::Option::Some(is.read_bytes()?); }, + 26 => { + ::protobuf::rt::read_singular_message_into_field(is, &mut self.additional_info)?; + }, tag => { ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; }, @@ -829,6 +1304,10 @@ impl ::protobuf::Message for SolanaSignTx { if let Some(v) = self.serialized_tx.as_ref() { my_size += ::protobuf::rt::bytes_size(2, &v); } + if let Some(v) = self.additional_info.as_ref() { + let len = v.compute_size(); + my_size += 1 + ::protobuf::rt::compute_raw_varint64_size(len) + len; + } my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); self.special_fields.cached_size().set(my_size as u32); my_size @@ -841,6 +1320,9 @@ impl ::protobuf::Message for SolanaSignTx { if let Some(v) = self.serialized_tx.as_ref() { os.write_bytes(2, v)?; } + if let Some(v) = self.additional_info.as_ref() { + ::protobuf::rt::write_message_field_with_cached_size(3, v, os)?; + } os.write_unknown_fields(self.special_fields.unknown_fields())?; ::std::result::Result::Ok(()) } @@ -860,6 +1342,7 @@ impl ::protobuf::Message for SolanaSignTx { fn clear(&mut self) { self.address_n.clear(); self.serialized_tx = ::std::option::Option::None; + self.additional_info.clear(); self.special_fields.clear(); } @@ -867,6 +1350,7 @@ impl ::protobuf::Message for SolanaSignTx { static instance: SolanaSignTx = SolanaSignTx { address_n: ::std::vec::Vec::new(), serialized_tx: ::std::option::Option::None, + additional_info: ::protobuf::MessageField::none(), special_fields: ::protobuf::SpecialFields::new(), }; &instance @@ -1060,10 +1544,17 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \x18\x01\x20\x03(\rR\x08addressN\x12!\n\x0cshow_display\x18\x02\x20\x01(\ \x08R\x0bshowDisplay\x12\x1a\n\x08chunkify\x18\x03\x20\x01(\x08R\x08chun\ kify\")\n\rSolanaAddress\x12\x18\n\x07address\x18\x01\x20\x02(\tR\x07add\ - ress\"P\n\x0cSolanaSignTx\x12\x1b\n\taddress_n\x18\x01\x20\x03(\rR\x08ad\ - dressN\x12#\n\rserialized_tx\x18\x02\x20\x02(\x0cR\x0cserializedTx\"1\n\ - \x11SolanaTxSignature\x12\x1c\n\tsignature\x18\x01\x20\x02(\x0cR\tsignat\ - ure\ + ress\"\xa6\x01\n\x18SolanaTxTokenAccountInfo\x12!\n\x0cbase_address\x18\ + \x01\x20\x02(\tR\x0bbaseAddress\x12#\n\rtoken_program\x18\x02\x20\x02(\t\ + R\x0ctokenProgram\x12\x1d\n\ntoken_mint\x18\x03\x20\x02(\tR\ttokenMint\ + \x12#\n\rtoken_account\x18\x04\x20\x02(\tR\x0ctokenAccount\"\x7f\n\x16So\ + lanaTxAdditionalInfo\x12e\n\x14token_accounts_infos\x18\x01\x20\x03(\x0b\ + 23.hw.trezor.messages.solana.SolanaTxTokenAccountInfoR\x12tokenAccountsI\ + nfos\"\xac\x01\n\x0cSolanaSignTx\x12\x1b\n\taddress_n\x18\x01\x20\x03(\r\ + R\x08addressN\x12#\n\rserialized_tx\x18\x02\x20\x02(\x0cR\x0cserializedT\ + x\x12Z\n\x0fadditional_info\x18\x03\x20\x01(\x0b21.hw.trezor.messages.so\ + lana.SolanaTxAdditionalInfoR\x0eadditionalInfo\"1\n\x11SolanaTxSignature\ + \x12\x1c\n\tsignature\x18\x01\x20\x02(\x0cR\tsignature\ "; /// `FileDescriptorProto` object which was a source for this generated file @@ -1082,11 +1573,13 @@ pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor { let generated_file_descriptor = generated_file_descriptor_lazy.get(|| { let mut deps = ::std::vec::Vec::with_capacity(1); deps.push(super::messages_common::file_descriptor().clone()); - let mut messages = ::std::vec::Vec::with_capacity(6); + let mut messages = ::std::vec::Vec::with_capacity(8); messages.push(SolanaGetPublicKey::generated_message_descriptor_data()); messages.push(SolanaPublicKey::generated_message_descriptor_data()); messages.push(SolanaGetAddress::generated_message_descriptor_data()); messages.push(SolanaAddress::generated_message_descriptor_data()); + messages.push(SolanaTxTokenAccountInfo::generated_message_descriptor_data()); + messages.push(SolanaTxAdditionalInfo::generated_message_descriptor_data()); messages.push(SolanaSignTx::generated_message_descriptor_data()); messages.push(SolanaTxSignature::generated_message_descriptor_data()); let mut enums = ::std::vec::Vec::with_capacity(0);