def _create_account_info(self, info: RPCResponse) -> AccountInfo: if not info: raise ValueError("Invalid account owner") if info["result"]["value"]["owner"] != str(self.program_id): raise AttributeError("Invalid account owner") bytes_data = decode_byte_string(info["result"]["value"]["data"][0]) if len(bytes_data) != ACCOUNT_LAYOUT.sizeof(): raise ValueError("Invalid account size") decoded_data = ACCOUNT_LAYOUT.parse(bytes_data) mint = PublicKey(decoded_data.mint) owner = PublicKey(decoded_data.owner) amount = decoded_data.amount if decoded_data.delegate_option == 0: delegate = None delegated_amount = 0 else: delegate = PublicKey(decoded_data.delegate) delegated_amount = decoded_data.delegated_amount is_initialized = decoded_data.state != 0 is_frozen = decoded_data.state == 2 if decoded_data.is_native_option == 1: rent_exempt_reserve = decoded_data.is_native is_native = True else: rent_exempt_reserve = None is_native = False if decoded_data.close_authority_option == 0: close_authority = None else: close_authority = PublicKey(decoded_data.owner) if mint != self.pubkey: raise AttributeError(f"Invalid account mint: {decoded_data.mint} != {self.pubkey}") return AccountInfo( mint, owner, amount, delegate, delegated_amount, is_initialized, is_frozen, is_native, rent_exempt_reserve, close_authority, )
def _create_account_args( self, owner: PublicKey, skip_confirmation: bool, balance_needed: int, ) -> Tuple[PublicKey, Transaction, Account, Account, TxOpts]: new_account = Account() # Allocate memory for the account # Construct transaction txn = Transaction() txn.add( sp.create_account( sp.CreateAccountParams( from_pubkey=self.payer.public_key(), new_account_pubkey=new_account.public_key(), lamports=balance_needed, space=ACCOUNT_LAYOUT.sizeof(), program_id=self.program_id, ))) txn.add( spl_token.initialize_account( spl_token.InitializeAccountParams( account=new_account.public_key(), mint=self.pubkey, owner=owner, program_id=self.program_id))) return ( new_account.public_key(), txn, self.payer, new_account, TxOpts(skip_preflight=True, skip_confirmation=skip_confirmation), )
def topup(self, api_endpoint, to, amount=None, skip_confirmation=True): """ Send a small amount of native currency to the specified wallet to handle gas fees. Return a status flag of success or fail and the native transaction data. """ msg = "" try: # Connect to the api_endpoint client = Client(api_endpoint) msg += "Initialized client" # List accounts sender_account = Account(self.private_key) dest_account = PublicKey(to) msg += " | Gathered accounts" # List signers signers = [sender_account] # Start transaction tx = Transaction() # Determine the amount to send try: if amount is None: min_rent_reseponse = client.get_minimum_balance_for_rent_exemption( ACCOUNT_LAYOUT.sizeof()) lamports = min_rent_reseponse["result"] else: lamports = int(amount) msg += f" | Fetched lamports: {lamports * 1e-9} SOL" except Exception as e: msg += " | ERROR: couldn't process lamports" raise (e) # Generate transaction transfer_ix = transfer( TransferParams(from_pubkey=sender_account.public_key(), to_pubkey=dest_account, lamports=lamports)) tx = tx.add(transfer_ix) msg += f" | Transferring funds" # Send request try: response = client.send_transaction( tx, *signers, opts=types.TxOpts(skip_confirmation=skip_confirmation)) return json.dumps({ 'status': HTTPStatus.OK, 'msg': f"Successfully sent {lamports * 1e-9} SOL to {to}", 'tx': response.get('result') if skip_confirmation else response['result']['transaction']['signatures'], }) except Exception as e: msg += f" | ERROR: Encountered exception while attempting to send transaction: {e}" raise (e) except Exception as e: return json.dumps({ 'status': HTTPStatus.BAD_REQUEST, 'msg': msg, })
def get_min_balance_rent_for_exempt_for_account(conn: Client) -> int: """Get the minimum balance for the account to be rent exempt. :param conn: RPC connection to a solana cluster. :return: Number of lamports required. """ resp = conn.get_minimum_balance_for_rent_exemption(ACCOUNT_LAYOUT.sizeof()) return resp["result"]
def create_account( self, owner: PublicKey, skip_confirmation: bool = False, ) -> PublicKey: """Create and initialize a new account. This account may then be used as a `transfer()` or `approve()` destination. :param owner: User account that will own the new account. :param skip_confirmation: (optional) Option to skip transaction confirmation. :return: Public key of the new empty account. If skip confirmation is set to `False`, this method will block for at most 30 seconds or until the transaction is confirmed. """ new_account = Account() # Allocate memory for the account balance_needed = Token.get_min_balance_rent_for_exempt_for_account( self._conn) # Construct transaction txn = Transaction() txn.add( sp.create_account( sp.CreateAccountParams( from_pubkey=self.payer.public_key(), new_account_pubkey=new_account.public_key(), lamports=balance_needed, space=ACCOUNT_LAYOUT.sizeof(), program_id=self.program_id, ))) txn.add( spl_token.initialize_account( spl_token.InitializeAccountParams( account=new_account.public_key(), mint=self.pubkey, owner=owner, program_id=self.program_id))) # Send the two instructions self._conn.send_transaction(txn, self.payer, new_account, opts=TxOpts( skip_preflight=True, skip_confirmation=skip_confirmation)) return new_account.public_key()
def _create_wrapped_native_account_args( program_id: PublicKey, owner: PublicKey, payer: Keypair, amount: int, skip_confirmation: bool, balance_needed: int, ) -> Tuple[PublicKey, Transaction, Keypair, Keypair, TxOpts]: new_keypair = Keypair() # Allocate memory for the account # Construct transaction txn = Transaction() txn.add( sp.create_account( sp.CreateAccountParams( from_pubkey=payer.public_key, new_account_pubkey=new_keypair.public_key, lamports=balance_needed, space=ACCOUNT_LAYOUT.sizeof(), program_id=program_id, ) ) ) txn.add( sp.transfer( sp.TransferParams(from_pubkey=payer.public_key, to_pubkey=new_keypair.public_key, lamports=amount) ) ) txn.add( spl_token.initialize_account( spl_token.InitializeAccountParams( account=new_keypair.public_key, mint=WRAPPED_SOL_MINT, owner=owner, program_id=program_id ) ) ) return new_keypair.public_key, txn, payer, new_keypair, TxOpts(skip_confirmation=skip_confirmation)
def collect(self, api_endpoint, pool_account, collector, skip_confirmation=True): msg = "" client = Client(api_endpoint) msg += "Initialized client" signers = [Account(self.private_key)] pool = self.load_binary_option(api_endpoint, pool_account) pool_account = PublicKey(pool_account) collector_account = PublicKey(collector) escrow_account = PublicKey(pool["escrow"]) escrow_mint_account = PublicKey(pool["escrow_mint"]) long_token_mint_account = PublicKey(pool["long_mint"]) short_token_mint_account = PublicKey(pool["short_mint"]) token_account = PublicKey(TOKEN_PROGRAM_ID) escrow_authority_account = PublicKey.find_program_address( [ bytes(long_token_mint_account), bytes(short_token_mint_account), bytes(token_account), bytes(PublicKey(BINARY_OPTION_PROGRAM_ID)) ], PublicKey(BINARY_OPTION_PROGRAM_ID), )[0] # Transaction tx = Transaction() atas = [] for mint_account in (long_token_mint_account, short_token_mint_account, escrow_mint_account): token_pda_address = get_associated_token_address( collector_account, mint_account) associated_token_account_info = client.get_account_info( token_pda_address) account_info = associated_token_account_info['result']['value'] if account_info is not None: account_state = ACCOUNT_LAYOUT.parse( base64.b64decode(account_info['data'][0])).state else: account_state = 0 if account_state == 0: msg += f" | Error Fetching PDA: {token_pda_address}" raise Exception() else: msg += f" | Fetched PDA: {token_pda_address}" atas.append(token_pda_address) collect_ix = collect_instruction( pool_account, collector_account, atas[0], atas[1], atas[2], long_token_mint_account, short_token_mint_account, escrow_account, escrow_authority_account, token_account, ) tx = tx.add(collect_ix) try: response = client.send_transaction( tx, *signers, opts=types.TxOpts(skip_confirmation=skip_confirmation)) return json.dumps({ 'status': HTTPStatus.OK, 'msg': msg + f" | Collect successful", 'tx': response.get('result') if skip_confirmation else response['result']['transaction']['signatures'], }) except Exception as e: msg += f" | ERROR: Encountered exception while attempting to send transaction: {e}" print(msg) raise (e)
def trade(self, api_endpoint, pool_account, buyer_encrypted_private_key, seller_encrypted_private_key, size, buyer_price, seller_price, skip_confirmation=True): msg = "" client = Client(api_endpoint) msg += "Initialized client" # Create account objects buyer_private_key = list( self.cipher.decrypt(buyer_encrypted_private_key)) seller_private_key = list( self.cipher.decrypt(seller_encrypted_private_key)) assert (len(buyer_private_key) == 32) assert (len(seller_private_key) == 32) source_account = Account(self.private_key) buyer = Account(buyer_private_key) seller = Account(seller_private_key) # Signers signers = [buyer, seller, source_account] pool = self.load_binary_option(api_endpoint, pool_account) # List non-derived accounts pool_account = PublicKey(pool_account) escrow_account = PublicKey(pool["escrow"]) escrow_mint_account = PublicKey(pool["escrow_mint"]) long_token_mint_account = PublicKey(pool["long_mint"]) short_token_mint_account = PublicKey(pool["short_mint"]) buyer_account = buyer.public_key() seller_account = seller.public_key() token_account = PublicKey(TOKEN_PROGRAM_ID) escrow_owner_account = PublicKey.find_program_address( [ bytes(long_token_mint_account), bytes(short_token_mint_account), bytes(token_account), bytes(PublicKey(BINARY_OPTION_PROGRAM_ID)) ], PublicKey(BINARY_OPTION_PROGRAM_ID), )[0] # Transaction tx = Transaction() atas = [] for acct in [buyer_account, seller_account]: acct_atas = [] for mint_account in (long_token_mint_account, short_token_mint_account, escrow_mint_account): token_pda_address = get_associated_token_address( acct, mint_account) associated_token_account_info = client.get_account_info( token_pda_address) account_info = associated_token_account_info['result']['value'] if account_info is not None: account_state = ACCOUNT_LAYOUT.parse( base64.b64decode(account_info['data'][0])).state else: account_state = 0 if account_state == 0: msg += f" | Creating PDA: {token_pda_address}" associated_token_account_ix = create_associated_token_account( payer=source_account.public_key(), owner=acct, mint=mint_account, ) tx = tx.add(associated_token_account_ix) else: msg += f" | Fetched PDA: {token_pda_address}" acct_atas.append(token_pda_address) atas.append(acct_atas) trade_ix = trade_instruction( pool_account, escrow_account, long_token_mint_account, short_token_mint_account, buyer_account, seller_account, atas[0][2], atas[1][2], atas[0][0], atas[0][1], atas[1][0], atas[1][1], escrow_owner_account, token_account, int(size), int(buyer_price), int(seller_price), ) tx = tx.add(trade_ix) # Send request try: response = client.send_transaction( tx, *signers, opts=types.TxOpts(skip_confirmation=skip_confirmation)) return json.dumps({ 'status': HTTPStatus.OK, 'msg': msg + f" | Trade successful", 'tx': response.get('result') if skip_confirmation else response['result']['transaction']['signatures'], }) except Exception as e: msg += f" | ERROR: Encountered exception while attempting to send transaction: {e}" raise (e)