Exemple #1
0
    def _create_solana_account(
        self, private_key: PrivateKey, commitment: Commitment, subsidizer: Optional[PrivateKey] = None
    ):
        config = self._internal_client.get_service_config()
        if not config.subsidizer_account.value and not subsidizer:
            raise NoSubsidizerError()

        subsidizer_id = (subsidizer.public_key if subsidizer else
                         PublicKey(config.subsidizer_account.value))

        instructions = []
        if self._app_index > 0:
            m = AgoraMemo.new(1, TransactionType.NONE, self._app_index, b'')
            instructions.append(memo.memo_instruction(base64.b64encode(m.val).decode('utf-8')))

        create_instruction, addr = token.create_associated_token_account(
            subsidizer_id,
            private_key.public_key,
            PublicKey(config.token.value))
        instructions.append(create_instruction)
        instructions.append(token.set_authority(
            addr,
            private_key.public_key,
            token.AuthorityType.CLOSE_ACCOUNT,
            new_authority=subsidizer_id,
        ))
        transaction = solana.Transaction.new(subsidizer_id, instructions)

        recent_blockhash_resp = self._internal_client.get_recent_blockhash()
        transaction.set_blockhash(recent_blockhash_resp.blockhash.value)
        transaction.sign([private_key])
        if subsidizer:
            transaction.sign([subsidizer])

        self._internal_client.create_solana_account(transaction, commitment)
Exemple #2
0
    def from_proto(
            cls, item: tx_pb_v4.HistoryItem,
            state: tx_pb_v4.GetTransactionResponse.State) -> 'TransactionData':
        payments = []
        if item.invoice_list and item.invoice_list.invoices:
            if len(item.payments) != len(item.invoice_list.invoices):
                raise ValueError(
                    'number of invoices does not match number of payments')
            il = InvoiceList.from_proto(item.invoice_list)
        else:
            il = None

        tx_type = TransactionType.UNKNOWN
        memo = None
        if item.solana_transaction.value:
            solana_tx = solana.Transaction.unmarshal(
                item.solana_transaction.value)
            program_idx = solana_tx.message.instructions[0].program_index
            if solana_tx.message.accounts[
                    program_idx] == solana.MEMO_PROGRAM_KEY:
                decompiled_memo = solana.decompile_memo(solana_tx.message, 0)
                memo_data = decompiled_memo.data.decode('utf-8')
                try:
                    agora_memo = AgoraMemo.from_b64_string(memo_data)
                    tx_type = agora_memo.tx_type()
                except ValueError:
                    memo = memo_data
        elif item.stellar_transaction.envelope_xdr:
            env = te.TransactionEnvelope.from_xdr(
                base64.b64encode(item.stellar_transaction.envelope_xdr))
            tx = env.tx
            if isinstance(tx.memo, stellar_memo.HashMemo):
                try:
                    agora_memo = AgoraMemo.from_base_memo(tx.memo)
                    tx_type = agora_memo.tx_type()
                except ValueError:
                    pass
            elif isinstance(tx.memo, stellar_memo.TextMemo):
                memo = tx.memo.text.decode()

        for idx, p in enumerate(item.payments):
            inv = il.invoices[idx] if il and il.invoices else None
            payments.append(
                ReadOnlyPayment(PublicKey(p.source.value),
                                PublicKey(p.destination.value),
                                tx_type,
                                p.amount,
                                invoice=inv,
                                memo=memo))

        return cls(
            item.transaction_id.value,
            TransactionState.from_proto_v4(state),
            payments,
            error=TransactionErrors.from_proto_error(item.transaction_error)
            if item.transaction_error else None,
        )
Exemple #3
0
 def from_proto(cls, proto: account_pb_v4.AccountInfo) -> 'AccountInfo':
     return cls(
         PublicKey(proto.account_id.value),
         proto.balance,
         PublicKey(proto.owner.value)
         if proto.owner and proto.owner.value else None,
         PublicKey(proto.close_authority.value)
         if proto.close_authority and proto.close_authority.value else None,
     )
Exemple #4
0
    def resolve_token_accounts(self, public_key: PublicKey) -> List[PublicKey]:
        """Resolve the provided public key to its token accounts.

        :param public_key: the :class:`PublicKey <agora.model.keys.PublicKey>` of the owner.
        :return: a list of :class:`PublicKey <agora.model.keys.PublicKey>` objects.
        """
        cached = self._get_from_cache(public_key)
        if cached:
            return cached

        def _call_resolve():
            response = self._account_stub.ResolveTokenAccounts(
                account_pb.ResolveTokenAccountsRequest(
                    account_id=model_pb.SolanaAccountId(value=public_key.raw)))
            if not response.token_accounts:
                raise NoTokenAccountsError()

            return response

        try:
            resp = retry(self._retry_strategies, _call_resolve)
            token_accounts = [
                PublicKey(account_id.value)
                for account_id in resp.token_accounts
            ]
        except NoTokenAccountsError:
            token_accounts = []

        if token_accounts:
            self._set_in_cache(public_key, token_accounts)

        return token_accounts
Exemple #5
0
def decompile_set_authority(m: Message, index: int, token_program: PublicKey) -> DecompileSetAuthority:
    if index >= len(m.instructions):
        raise ValueError(f"instruction doesn't exist at {index}")

    i = m.instructions[index]

    if m.accounts[i.program_index] != token_program:
        raise ValueError('incorrect program')

    if len(i.accounts) != 2:
        raise ValueError(f'invalid number of accounts: {len(i.accounts)}')

    if len(i.data) < 3:
        raise ValueError(f'invalid instruction data size: {len(i.data)}')

    if i.data[0] != Command.SET_AUTHORITY:
        raise ValueError(f'invalid instruction data: {i.data}')

    if i.data[2] == 0 and len(i.data) != 3:
        raise ValueError(f'invalid instruction data size: {len(i.data)}')

    if i.data[2] == 1 and len(i.data) != 3 + ED25519_PUB_KEY_SIZE:
        raise ValueError(f'invalid instruction data size: {len(i.data)}')

    return DecompileSetAuthority(
        m.accounts[i.accounts[0]],
        m.accounts[i.accounts[1]],
        AuthorityType(i.data[1]),
        PublicKey(i.data[3:]) if i.data[2] == 1 else None,
    )
Exemple #6
0
def decompile_create_account(m: Message,
                             index: int) -> DecompiledCreateAccount:
    if index >= len(m.instructions):
        raise ValueError(f"instruction doesn't exist at {index}")

    i = m.instructions[index]
    if m.accounts[i.program_index] != _PROGRAM_KEY:
        raise ValueError('incorrect program')

    if len(i.accounts) != 2:
        raise ValueError(f'invalid number of accounts: {len(i.accounts)}')

    if len(i.data) != 52:
        raise ValueError(f'invalid instruction data size: {len(i.data)}')

    if int.from_bytes(i.data[0:4], 'little') != Command.CREATE_ACCOUNT:
        raise ValueError(f'incorrect command')

    return DecompiledCreateAccount(
        m.accounts[i.accounts[0]],
        m.accounts[i.accounts[1]],
        PublicKey(i.data[4 + 2 * 8:]),
        int.from_bytes(i.data[4:12], 'little'),
        int.from_bytes(i.data[12:20], 'little'),
    )
Exemple #7
0
    def _submit_solana_earn_batch_tx(
        self, batch: EarnBatch, service_config: tx_pb.GetServiceConfigResponse, commitment: Commitment,
        transfer_sender: Optional[PublicKey] = None,
    ) -> SubmitTransactionResult:
        subsidizer_id = (batch.subsidizer.public_key if batch.subsidizer
                         else PublicKey(service_config.subsidizer_account.value))

        transfer_sender = transfer_sender if transfer_sender else batch.sender.public_key
        instructions = [
            token.transfer(
                transfer_sender,
                earn.destination,
                batch.sender.public_key,
                earn.quarks,
            ) for earn in batch.earns]

        invoices = [earn.invoice for earn in batch.earns if earn.invoice]
        invoice_list = InvoiceList(invoices) if invoices else None
        if batch.memo:
            instructions = [memo.memo_instruction(batch.memo)] + instructions
        elif self._app_index > 0:
            fk = invoice_list.get_sha_224_hash() if invoice_list else b''
            agora_memo = AgoraMemo.new(1, TransactionType.EARN, self._app_index, fk)
            instructions = [memo.memo_instruction(base64.b64encode(agora_memo.val).decode('utf-8'))] + instructions

        tx = solana.Transaction.new(subsidizer_id, instructions)
        if batch.subsidizer:
            signers = [batch.subsidizer, batch.sender]
        else:
            signers = [batch.sender]

        return self._sign_and_submit_solana_tx(signers, tx, commitment, invoice_list=invoice_list,
                                               dedupe_id=batch.dedupe_id)
Exemple #8
0
def create_program_address(program: PublicKey,
                           seeds: List[bytes]) -> PublicKey:
    """Mirrors the implementation of the Solana SDK's CreateProgramAddress. ProgramAddresses are public keys that
    _do not_ lie on the ed25519 curve to ensure that there is no associated private key. In the event that the program
    and seed parameters result in a valid Public key, InvalidPublicKeyError is raised.

    Reference:
    https://github.com/solana-labs/solana/blob/5548e599fe4920b71766e0ad1d121755ce9c63d5/sdk/program/src/pubkey.rs#L158

    :return :class:`PublicKey <agora.keys.PublicKey>`
    """
    if len(seeds) > MAX_SEEDS:
        raise ValueError('too many seeds')

    sha256 = hashlib.sha256()
    for s in seeds:
        if len(s) > MAX_SEED_LENGTH:
            raise ValueError('max seed length exceeded')

        sha256.update(s)

    for v in [program.raw, "ProgramDerivedAddress".encode()]:
        sha256.update(v)

    h = sha256.digest()
    pub = h[:32]

    # Following the Solana SDK, we want to _reject_ the generated public key if it's a a valid point on the ed25519 curve
    try:
        decodepoint(pub)
    except NotOnCurve:
        return PublicKey(pub)

    raise InvalidPublicKeyError()
    def test_memo_progam(self):
        data = 'somedata'
        i = memo_instruction(data)

        assert i.data.decode('utf-8') == data

        tx = Transaction.unmarshal(Transaction.new(PublicKey(bytes(32)), [i]).marshal())
        memo = decompile_memo(tx.message, 0)
        assert memo.data.decode('utf-8') == data
Exemple #10
0
    def test_kin_keypair_compat(self):
        kp = Keypair.random()

        pub = PublicKey(kp.raw_public_key())
        assert pub.stellar_address == kp.address().decode()
        assert pub.raw == kp.raw_public_key()

        priv = PrivateKey(kp.raw_seed())
        assert priv.stellar_seed == kp.seed().decode()
        assert priv.raw == kp.raw_seed()
Exemple #11
0
    def resolve_token_accounts(self, public_key: PublicKey) -> List[PublicKey]:
        def _resolve():
            return self._account_stub_v4.ResolveTokenAccounts(
                account_pb_v4.ResolveTokenAccountsRequest(
                    account_id=model_pb_v4.SolanaAccountId(
                        value=public_key.raw)))

        resp = retry(self._retry_strategies, _resolve)
        return [
            PublicKey(token_account.value)
            for token_account in resp.token_accounts
        ]
Exemple #12
0
    def unmarshal(cls, b: bytes) -> 'Message':
        # Header
        num_signatures = b[0]
        num_read_only_signed = b[1]
        num_read_only = b[2]
        b = b[3:]

        # Accounts
        accounts_length, offset = shortvec.decode_length(b)
        accounts = []
        for _ in range(accounts_length):
            accounts.append(PublicKey(b[offset:offset + ED25519_PUB_KEY_SIZE]))
            offset += ED25519_PUB_KEY_SIZE
        b = b[offset:]

        # Recent Blockhash
        recent_blockhash = b[:HASH_LENGTH]
        b = b[HASH_LENGTH:]

        # Instructions
        instructions_length, offset = shortvec.decode_length(b)
        b = b[offset:]
        instructions = []
        for i in range(instructions_length):
            program_index = b[0]
            if program_index >= accounts_length:
                raise ValueError(
                    f'program index out of range: {i}:{program_index}')
            b = b[1:]

            # Account Indices
            account_length, offset = shortvec.decode_length(b)
            b = b[offset:]
            instruction_accounts = b[:account_length]
            for account_index in instruction_accounts:
                if account_index >= accounts_length:
                    raise ValueError(
                        f'instruction account out of range: {account_index}')
            b = b[account_length:]

            # Data
            data_length, offset = shortvec.decode_length(b)
            b = b[offset:]
            data = b[:data_length]
            b = b[data_length:]

            instructions.append(
                CompiledInstruction(program_index, instruction_accounts, data))

        return cls(Header(num_signatures, num_read_only_signed, num_read_only),
                   accounts, recent_blockhash, instructions)
Exemple #13
0
    def _submit_solana_payment_tx(
        self,
        payment: Payment,
        service_config: tx_pb.GetServiceConfigResponse,
        commitment: Commitment,
        transfer_sender: Optional[PublicKey] = None
    ) -> SubmitTransactionResult:
        token_program = PublicKey(service_config.token_program.value)
        subsidizer_id = (payment.subsidizer.public_key
                         if payment.subsidizer else PublicKey(
                             service_config.subsidizer_account.value))

        instructions = []
        invoice_list = None
        if payment.memo:
            instructions = [memo_instruction(payment.memo)]
        elif self.app_index > 0:
            if payment.invoice:
                invoice_list = InvoiceList(invoices=[payment.invoice])
            fk = invoice_list.get_sha_224_hash() if payment.invoice else b''
            memo = AgoraMemo.new(1, payment.tx_type, self.app_index, fk)
            instructions = [
                memo_instruction(base64.b64encode(memo.val).decode('utf-8'))
            ]

        sender = transfer_sender if transfer_sender else payment.sender.public_key
        instructions.append(
            transfer(sender, payment.destination, payment.sender.public_key,
                     payment.quarks, token_program))

        tx = solana.Transaction.new(subsidizer_id, instructions)
        if payment.subsidizer:
            signers = [payment.subsidizer, payment.sender]
        else:
            signers = [payment.sender]

        return self._sign_and_submit_solana_tx(signers, tx, commitment,
                                               invoice_list)
Exemple #14
0
    def test_transaction_cross_impl(self):
        pk = PrivateKey(bytes([48, 83, 2, 1, 1, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32, 255, 101, 36, 24, 124, 23,
                               167, 21, 132, 204, 155, 5, 185, 58, 121, 75]))
        program_id = PublicKey(bytes([2, 2, 2, 4, 5, 6, 7, 8, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 8, 7, 6,
                                      5, 4, 2, 2, 2]))
        to = PublicKey(bytes([1, 1, 1, 4, 5, 6, 7, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8, 7, 6, 5, 4, 1,
                              1, 1]))

        tx = Transaction.new(
            pk.public_key,
            [
                Instruction(
                    program_id,
                    bytes([1, 2, 3]),
                    [AccountMeta.new(pk.public_key, True), AccountMeta.new(to, False)],
                ),
            ],
        )
        tx.sign([pk])

        generated = base64.b64decode(_RUST_GENERATED_ADJUSTED)
        assert tx.marshal() == generated
        assert Transaction.unmarshal(generated) == tx
Exemple #15
0
    def _submit_solana_payment_tx(
        self, payment: Payment, service_config: tx_pb.GetServiceConfigResponse, commitment: Commitment,
        transfer_source: Optional[PublicKey] = None, create_instructions: List[solana.Instruction] = None,
        create_signer: Optional[PrivateKey] = None,
    ) -> SubmitTransactionResult:
        subsidizer_id = (payment.subsidizer.public_key if payment.subsidizer
                         else PublicKey(service_config.subsidizer_account.value))

        instructions = []
        invoice_list = None
        if payment.memo:
            instructions = [memo.memo_instruction(payment.memo)]
        elif self._app_index > 0:
            if payment.invoice:
                invoice_list = InvoiceList(invoices=[payment.invoice])
            fk = invoice_list.get_sha_224_hash() if payment.invoice else b''
            m = AgoraMemo.new(1, payment.tx_type, self._app_index, fk)
            instructions = [memo.memo_instruction(base64.b64encode(m.val).decode('utf-8'))]

        if create_instructions:
            instructions += create_instructions

        sender = transfer_source if transfer_source else payment.sender.public_key
        instructions.append(token.transfer(
            sender,
            payment.destination,
            payment.sender.public_key,
            payment.quarks,
        ))

        tx = solana.Transaction.new(subsidizer_id, instructions)
        if payment.subsidizer:
            signers = [payment.subsidizer, payment.sender]
        else:
            signers = [payment.sender]

        if create_signer:
            signers.append(create_signer)

        return self._sign_and_submit_solana_tx(signers, tx, commitment, invoice_list=invoice_list,
                                               dedupe_id=payment.dedupe_id)
Exemple #16
0
    def resolve_token_accounts(
            self, public_key: PublicKey,
            include_account_info: bool) -> List[AccountInfo]:
        """Resolves token accounts using Agora.

        :param public_key: the public key of the account to resolve token accounts for.
        :param include_account_info: indicates whether to include token account info in the response
        :return: A list of :class:`AccountInfo <agora.model.account.AccountInfo>` objects each representing a token
            account. Information other than AccountInfo.account_id will only be populated if `include_account_info` is
            True.
        """
        def _resolve():
            return self._account_stub_v4.ResolveTokenAccounts(
                account_pb.ResolveTokenAccountsRequest(
                    account_id=model_pb.SolanaAccountId(value=public_key.raw),
                    include_account_info=include_account_info,
                ),
                metadata=self._metadata,
                timeout=_GRPC_TIMEOUT_SECONDS)

        resp = retry(self._retry_strategies, _resolve)

        # This is currently in place for backward compat with the server - `token_accounts` is deprecated
        if resp.token_accounts and len(resp.token_account_infos) != len(
                resp.token_accounts):
            # If we aren't requesting account info, we can interpolate the results ourselves.
            if not include_account_info:
                return [
                    AccountInfo(PublicKey(a.value))
                    for a in resp.token_accounts
                ]
            else:
                raise Error(
                    'server does not support resolving with account info')

        return [AccountInfo.from_proto(a) for a in resp.token_account_infos]
Exemple #17
0
 def _parse_cache_entry(self, entry: bytes) -> List[PublicKey]:
     return [
         PublicKey(entry[i:i + ED25519_PUB_KEY_SIZE])
         for i in range(0, len(entry), ED25519_PUB_KEY_SIZE)
     ]
Exemple #18
0
        def _create():
            nonlocal subsidizer

            service_config_resp = self.get_service_config()
            if not service_config_resp.subsidizer_account.value and not subsidizer:
                raise NoSubsidizerError()

            subsidizer_id = (subsidizer.public_key
                             if subsidizer else PublicKey(
                                 service_config_resp.subsidizer_account.value))

            recent_blockhash_future = self._transaction_stub_v4.GetRecentBlockhash.future(
                tx_pb_v4.GetRecentBlockhashRequest(),
                metadata=self._metadata,
                timeout=_GRPC_TIMEOUT_SECONDS)
            min_balance_future = self._transaction_stub_v4.GetMinimumBalanceForRentExemption.future(
                tx_pb_v4.GetMinimumBalanceForRentExemptionRequest(
                    size=token.ACCOUNT_SIZE),
                metadata=self._metadata,
                timeout=_GRPC_TIMEOUT_SECONDS)
            recent_blockhash_resp = recent_blockhash_future.result()
            min_balance_resp = min_balance_future.result()

            token_program = PublicKey(service_config_resp.token_program.value)
            transaction = Transaction.new(subsidizer_id, [
                system.create_account(
                    subsidizer_id,
                    private_key.public_key,
                    token_program,
                    min_balance_resp.lamports,
                    token.ACCOUNT_SIZE,
                ),
                token.initialize_account(
                    private_key.public_key,
                    PublicKey(service_config_resp.token.value),
                    private_key.public_key,
                    token_program,
                ),
                token.set_authority(
                    private_key.public_key,
                    private_key.public_key,
                    token.AuthorityType.CloseAccount,
                    token_program,
                    new_authority=subsidizer_id,
                )
            ])
            transaction.set_blockhash(recent_blockhash_resp.blockhash.value)
            transaction.sign([private_key])
            if subsidizer:
                transaction.sign([subsidizer])

            req = account_pb_v4.CreateAccountRequest(
                transaction=model_pb_v4.Transaction(
                    value=transaction.marshal()),
                commitment=commitment.to_proto(),
            )
            resp = self._account_stub_v4.CreateAccount(
                req, metadata=self._metadata, timeout=_GRPC_TIMEOUT_SECONDS)

            if resp.result == account_pb_v4.CreateAccountResponse.Result.EXISTS:
                raise AccountExistsError()
            if resp.result == account_pb_v4.CreateAccountResponse.Result.PAYER_REQUIRED:
                raise PayerRequiredError()
            if resp.result == account_pb_v4.CreateAccountResponse.Result.BAD_NONCE:
                raise BadNonceError()
            if resp.result != account_pb_v4.CreateAccountResponse.Result.OK:
                raise Error(f'unexpected result from agora: {resp.result}')
Exemple #19
0
from enum import IntEnum
from typing import NamedTuple

from agora.keys import PublicKey
from agora.solana.instruction import Instruction, AccountMeta
from agora.solana.transaction import Message

_PROGRAM_KEY = PublicKey(bytes(32))


class Command(IntEnum):
    CREATE_ACCOUNT = 0
    ASSIGN = 1
    TRANSFER = 2
    CREATE_ACCOUNT_WITH_SEED = 3
    ADVANCE_NONCE_ACCOUNT = 4
    WITHDRAW_NONCE_ACCOUNT = 5
    INITIALIZE_NONCE_ACCOUNT = 6
    AUTHORIZE_NONCE_ACCOUNT = 7
    ALLOCATE = 8
    ALLOCATE_WITH_SEED = 9
    ASSIGN_WITH_SEED = 10
    TRANSFER_WITH_SEED = 11


# Reference: https://github.com/solana-labs/solana/blob/f02a78d8fff2dd7297dc6ce6eb5a68a3002f5359/sdk/src/system_instruction.rs#L58-L72  #noqa: E501
def create_account(subsidizer: PublicKey, address: PublicKey, owner: PublicKey,
                   lamports: int, size: int) -> Instruction:
    """
    Account references
      0. [WRITE, SIGNER] Funding account
Exemple #20
0
    def merge_token_accounts(
        self, private_key: PrivateKey, create_associated_account: bool, commitment: Optional[Commitment] = None,
        subsidizer: Optional[PrivateKey] = None,
    ) -> Optional[bytes]:
        commitment = commitment if commitment else self._default_commitment

        existing_accounts = self._internal_client.resolve_token_accounts(private_key.public_key, True)
        if len(existing_accounts) == 0 or (len(existing_accounts) == 1 and not create_associated_account):
            return None

        dest = existing_accounts[0].account_id
        instructions = []
        signers = [private_key]

        config = self._internal_client.get_service_config()
        if not config.subsidizer_account.value and not subsidizer:
            raise NoSubsidizerError()

        if subsidizer:
            subsidizer_id = subsidizer.public_key
            signers.append(subsidizer)
        else:
            subsidizer_id = PublicKey(config.subsidizer_account.value)

        if create_associated_account:
            create_instruction, assoc = token.create_associated_token_account(
                subsidizer_id,
                private_key.public_key,
                PublicKey(config.token.value),
            )
            if existing_accounts[0].account_id.raw != assoc.raw:
                instructions.append(create_instruction)
                instructions.append(token.set_authority(
                    assoc,
                    private_key.public_key,
                    token.AuthorityType.CLOSE_ACCOUNT,
                    new_authority=subsidizer_id))
                dest = assoc
            elif len(existing_accounts) == 1:
                return None

        for existing_account in existing_accounts:
            if existing_account.account_id == dest:
                continue

            instructions.append(token.transfer(
                existing_account.account_id,
                dest,
                private_key.public_key,
                existing_account.balance,
            ))

            # If no close authority is set, it likely means we don't know it, and can't make any assumptions
            if not existing_account.close_authority:
                continue

            # If the subsidizer is the close authority, we can include the close instruction as they will be ok with
            # signing for it
            #
            # Alternatively, if we're the close authority, we are signing it.
            should_close = False
            for a in [private_key.public_key, subsidizer_id]:
                if existing_account.close_authority == a:
                    should_close = True
                    break

            if should_close:
                instructions.append(token.close_account(
                    existing_account.account_id,
                    existing_account.close_authority,
                    existing_account.close_authority,
                ))

        transaction = solana.Transaction.new(subsidizer_id, instructions)

        result = self._sign_and_submit_solana_tx(signers, transaction, commitment)
        if result.errors and result.errors.tx_error:
            raise result.errors.tx_error

        return result.tx_id
Exemple #21
0
    def _resolve_and_submit_solana_payment(
        self, payment: Payment, commitment: Commitment, sender_resolution: AccountResolution,
        dest_resolution: AccountResolution, sender_create: bool
    ) -> SubmitTransactionResult:
        config = self._internal_client.get_service_config()
        if not config.subsidizer_account.value and not payment.subsidizer:
            raise NoSubsidizerError()

        subsidizer_id = (payment.subsidizer.public_key if payment.subsidizer else
                         PublicKey(config.subsidizer_account.value))

        result = self._submit_solana_payment_tx(payment, config, commitment)
        if result.errors and isinstance(result.errors.tx_error, AccountNotFoundError):
            transfer_source = None
            create_instructions = []
            create_signer = None
            resubmit = False

            if sender_resolution == AccountResolution.PREFERRED:
                token_account_infos = self._internal_client.resolve_token_accounts(payment.sender.public_key, False)
                if token_account_infos:
                    transfer_source = token_account_infos[0].account_id
                    resubmit = True

            if dest_resolution == AccountResolution.PREFERRED:
                token_account_infos = self._internal_client.resolve_token_accounts(payment.destination, False)
                if token_account_infos:
                    payment.destination = token_account_infos[0].account_id
                    resubmit = True
                elif sender_create:
                    lamports = self._internal_client.get_minimum_balance_for_rent_exception()
                    temp_key = PrivateKey.random()

                    original_dest = payment.destination
                    payment.destination = temp_key.public_key
                    create_instructions = [
                        system.create_account(
                            subsidizer_id,
                            temp_key.public_key,
                            token.PROGRAM_KEY,
                            lamports,
                            token.ACCOUNT_SIZE,
                        ),
                        token.initialize_account(
                            temp_key.public_key,
                            PublicKey(config.token.value),
                            temp_key.public_key,
                        ),
                        token.set_authority(
                            temp_key.public_key,
                            temp_key.public_key,
                            token.AuthorityType.CLOSE_ACCOUNT,
                            new_authority=subsidizer_id,
                        ),
                        token.set_authority(
                            temp_key.public_key,
                            temp_key.public_key,
                            token.AuthorityType.ACCOUNT_HOLDER,
                            new_authority=original_dest,
                        ),
                    ]
                    create_signer = temp_key
                    resubmit = True

            if resubmit:
                result = self._submit_solana_payment_tx(
                    payment,
                    config,
                    commitment,
                    transfer_source=transfer_source,
                    create_instructions=create_instructions,
                    create_signer=create_signer,
                )

        return result