Esempio n. 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)
Esempio n. 2
0
    def test_find_program_address_ref(self, program_id, expected):
        """Test with addresses generated by rust impl
        """
        pid = PublicKey.from_base58(program_id)
        exp = PublicKey.from_base58(expected)

        actual = find_program_address(pid, ['Lil\''.encode(), 'Bits'.encode()])
        assert actual == exp
Esempio n. 3
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,
        )
Esempio n. 4
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,
     )
Esempio n. 5
0
    def test_get_associated_account(self):
        # Values generated from taken from spl code.
        wallet = PublicKey.from_base58(
            '4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM')
        mint = PublicKey.from_base58(
            '8opHzTAnfzRpPEx21XtnrVTX28YQuCpAjcn1PczScKh')

        expected = PublicKey.from_base58(
            'H7MQwEzt97tUJryocn3qaEoy2ymWstwyEk1i9Yv3EmuZ')

        actual = get_associated_account(wallet, mint)
        assert actual == expected
Esempio n. 6
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
Esempio n. 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)
Esempio n. 8
0
def gen_account_id_from_address(address: str) -> xdr_type.AccountID:
    public_key = PublicKey.from_string(address)

    return xdr_type.AccountID(
        type=xdr_const.PUBLIC_KEY_TYPE_ED25519,
        ed25519=public_key.raw,
    )
Esempio n. 9
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'),
    )
Esempio n. 10
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()
Esempio n. 11
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,
    )
Esempio n. 12
0
    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
Esempio n. 13
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()
Esempio n. 14
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
        ]
Esempio n. 15
0
    def test_from_string(self):
        address = "GCABWU4FHL3RGOIWCX5TOVLIAMLEU2YXXLCMHVXLDOFHKLNLGCSBRJYP"
        seed = "SCZ4KGTCMAFIJQCCJDMMKDFUB7NYV56VBNEU7BKMR4PQFUETJCWLV6GN"

        pub = PublicKey.from_string(address)
        assert pub.stellar_address == address

        priv = PrivateKey.from_string(seed)
        assert priv.stellar_seed == seed

        # Test invalid cases
        with pytest.raises(ValueError):
            PublicKey.from_string('invalidlength')

        with pytest.raises(ValueError):
            PublicKey.from_string(seed)  # not an address

        with pytest.raises(ValueError):
            PrivateKey.from_string('invalidlength')

        with pytest.raises(ValueError):
            PrivateKey.from_string(address)  # not a seed
Esempio n. 16
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)
Esempio n. 17
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)
Esempio n. 18
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
Esempio n. 19
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)
Esempio n. 20
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]
Esempio n. 21
0
                required=True,
                help='The private seed of the sender account')
ap.add_argument(
    '-d',
    '--destinations',
    required=True,
    help=
    'A comma-delimited list of account public addresses to send earns to (e.g. add1,addr2,add3'
)
args = vars(ap.parse_args())

client = Client(Environment.TEST, 1)  # 1 is the test app index

source = PrivateKey.from_string(args['sender'])
destinations = [
    PublicKey.from_string(addr) for addr in args['destinations'].split(',')
]

# Send an earn batch with 1 Kin each
earns = [
    Earn(dest, kin_to_quarks('1')) for idx, dest in enumerate(destinations)
]
batch_result = client.submit_earn_batch(source, earns)
print(
    f'{len(batch_result.succeeded)} succeeded, {len(batch_result.failed)} failed'
)
for result in batch_result.succeeded:
    print(
        f'Sent 1 kin to {result.earn.destination.stellar_address} in transaction {result.tx_id.hex()}'
    )
for result in batch_result.failed:
Esempio n. 22
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}')
Esempio n. 23
0
from typing import NamedTuple

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

# The address of the memo program that should be used.
# todo: lock this in, or make configurable
PROGRAM_KEY = PublicKey.from_base58(
    'Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo')


# Reference: https://github.com/solana-labs/solana-program-library/blob/master/memo/program/src/entrypoint.rs
def memo_instruction(data: str) -> Instruction:
    return Instruction(
        PROGRAM_KEY,
        bytes(data, 'utf-8'),
    )


class DecompiledMemo(NamedTuple):
    data: bytes


def decompile_memo(m: Message, index: int) -> DecompiledMemo:
    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:
Esempio n. 24
0
# todo: lock in token program key and remove token_program parameters
from enum import IntEnum
from typing import NamedTuple, Optional

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

# Reference: https://github.com/solana-labs/solana-program-library/blob/11b1e3eefdd4e523768d63f7c70a7aa391ea0d02/token/program/src/state.rs#L125  # noqa: E501
ACCOUNT_SIZE = 165

# RentSysVar points to the system variable "Rent"
#
# Source: https://github.com/solana-labs/solana/blob/f02a78d8fff2dd7297dc6ce6eb5a68a3002f5359/sdk/src/sysvar/rent.rs#L11
_RENT_SYS_VAR = PublicKey.from_base58('SysvarRent111111111111111111111111111111111')


class Command(IntEnum):
    INITIALIZE_MINT = 0
    INITIALIZE_ACCOUNT = 1
    INITIALIZE_MULTISIG = 2
    TRANSFER = 3
    APPROVE = 4
    REVOKE = 5
    SET_AUTHORITY = 6
    MINT_TO = 7
    BURN = 8
    CLOSE_ACCOUNT = 9
    FREEZE_ACCOUNT = 10
    THAW_ACCOUNT = 11
    TRANSFER_2 = 12
Esempio n. 25
0
    def payments_from_envelope(
        cls,
        envelope: te.TransactionEnvelope,
        invoice_list: Optional[model_pb2.InvoiceList] = None,
        kin_version: Optional[int] = 3,
    ) -> List['ReadOnlyPayment']:
        """Returns a list of read only payments from a transaction envelope.

        :param envelope: A :class:`TransactionEnvelope <kin_base.transaction_envelope.TransactionEnvelope>.
        :param invoice_list: (optional) A protobuf invoice list associated with the transaction.
        :param kin_version: (optional) The version of Kin to parse payments for.
        :return: A List of :class:`ReadOnlyPayment <ReadOnlyPayment>` objects.
        """
        if invoice_list and invoice_list.invoices and len(
                invoice_list.invoices) != len(envelope.tx.operations):
            raise ValueError(
                f'number of invoices ({len(invoice_list.invoices)}) does not match number of transaction '
                f'operations ({len(envelope.tx.operations)})')

        tx = envelope.tx

        text_memo = None
        agora_memo = None
        if isinstance(tx.memo, memo.HashMemo):
            try:
                agora_memo = AgoraMemo.from_base_memo(tx.memo, False)
            except ValueError:
                pass
        elif isinstance(tx.memo, memo.TextMemo):
            text_memo = tx.memo

        payments = []
        for idx, op in enumerate(envelope.tx.operations):
            # Currently, only payment operations are supported in this method. Eventually, create account and merge
            # account operations could potentially be supported, but currently this is primarily only used for payment
            # operations
            if not isinstance(op, operation.Payment):
                continue

            # Only Kin payment operations are supported in this method.
            if kin_version == 2 and (op.asset.type != 'credit_alphanum4'
                                     or op.asset.code != 'KIN'):
                continue

            inv = invoice_list.invoices[
                idx] if invoice_list and invoice_list.invoices else None

            # Inside the kin_base module, the base currency has been 'scaled' by a factor of 100 from
            # Stellar (i.e., the smallest denomination used is 1e-5 instead of 1e-7). However, Kin 2 uses the minimum
            # Stellar denomination of 1e-7.
            #
            # When parsing an XDR transaction, `kin_base` assumes a smallest denomination of 1e-5. Therefore, for Kin 2
            # transactions, we must divide the resulting amounts by 100 to account for the 100x scaling factor.
            payments.append(
                ReadOnlyPayment(
                    sender=PublicKey.from_string(
                        op.source if op.source else tx.source.decode()),
                    destination=PublicKey.from_string(op.destination),
                    tx_type=agora_memo.tx_type()
                    if agora_memo else TransactionType.UNKNOWN,
                    quarks=int(kin_to_quarks(op.amount) / 100)
                    if kin_version == 2 else kin_to_quarks(op.amount),
                    invoice=Invoice.from_proto(inv) if inv else None,
                    memo=text_memo.text.decode() if text_memo else None,
                ))

        return payments
Esempio n. 26
0
ap = argparse.ArgumentParser()
ap.add_argument('-s',
                '--sender',
                required=True,
                help='The private seed of the sender account')
ap.add_argument('-d',
                '--destination',
                required=True,
                help='The public address of the destination account')
args = vars(ap.parse_args())

client = Client(Environment.TEST, 1)  # 1 is the test app index

source = PrivateKey.from_base58(args['sender'])
dest = PublicKey.from_base58(args['destination'])

# Send a payment of 1 Kin
payment = Payment(source, dest, TransactionType.EARN, kin_to_quarks('1'))
try:
    tx_id = client.submit_payment(p)
    print(
        f'transaction successfully submitted with hash: {base58.b58encode(tx_id)}'
    )
except Error as e:
    print(f'transaction failed: {repr(e)}')
    if isinstance(e, TransactionErrors):
        print(
            f'tx_error={repr(e.tx_error)}, len(op_errors)={len(e.op_errors)}')
        for op_error in e.op_errors:
            print(f'op_error={repr(op_error)}')
Esempio n. 27
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
Esempio n. 28
0
from agora.keys import PrivateKey, PublicKey
from agora.model import Earn, Invoice, LineItem
from agora.model.earn import EarnBatch
from agora.utils import kin_to_quarks

ap = argparse.ArgumentParser()
ap.add_argument('-s', '--sender', required=True, help='The base58-encoded private seed of the sender account')
ap.add_argument('-d', '--destinations', required=True,
                help='A comma-delimited list of base58-encoded account addresses to send earns to '
                     '(e.g. add1,addr2,add3)')
args = vars(ap.parse_args())

client = Client(Environment.TEST, 1)  # 1 is the test app index

source = PrivateKey.from_base58(args['sender'])
destinations = [PublicKey.from_base58(addr) for addr in args['destinations'].split(',')]

# Send an earn batch with 1 Kin each
earns = [Earn(dest, kin_to_quarks('1')) for idx, dest in enumerate(destinations)]
batch_result = client.submit_earn_batch(EarnBatch(source, earns))
if batch_result.tx_error:
    print(f'{batch_result.tx_id} failed with error {repr(batch_result.tx_error)}')

    if batch_result.earn_errors:
        for e in batch_result.earn_errors:
            print(f'earn {e.earn_index} failed with error {repr(e.error)}')
else:
    print(f'{batch_result.tx_id} submitted')

# Send an earn batch of earns with 1 Kin each, with invoices
earns = [Earn(dest, kin_to_quarks('1'), invoice=Invoice([LineItem(f'Payment {idx}', kin_to_quarks('1'))]))
Esempio n. 29
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
Esempio n. 30
0
# todo: lock in token program key and remove token_program parameters
from enum import IntEnum
from typing import NamedTuple, Optional

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

# Reference: https://github.com/solana-labs/solana-program-library/blob/11b1e3eefdd4e523768d63f7c70a7aa391ea0d02/token/program/src/state.rs#L125  # noqa: E501
ACCOUNT_SIZE = 165

PROGRAM_KEY = PublicKey.from_base58(
    "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")


class Command(IntEnum):
    INITIALIZE_MINT = 0
    INITIALIZE_ACCOUNT = 1
    INITIALIZE_MULTISIG = 2
    TRANSFER = 3
    APPROVE = 4
    REVOKE = 5
    SET_AUTHORITY = 6
    MINT_TO = 7
    BURN = 8
    CLOSE_ACCOUNT = 9
    FREEZE_ACCOUNT = 10
    THAW_ACCOUNT = 11
    TRANSFER_2 = 12
    APPROVE_2 = 13