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)
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
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, )
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, )
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
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
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)
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, )
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'), )
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 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, )
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
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()
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 ]
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
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)
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)
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
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)
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]
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:
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}')
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:
# 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
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
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)}')
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
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'))]))
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
# 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