2021-08-07 07:07:19 -07:00
# # ⚠ Warning
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
# NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# [🥭 Mango Markets](https://mango.markets/) support is available at:
# [Docs](https://docs.mango.markets/)
# [Discord](https://discord.gg/67jySBhxrg)
# [Twitter](https://twitter.com/mangomarkets)
# [Github](https://github.com/blockworks-foundation)
# [Email](mailto:hello@blockworks.foundation)
2021-08-11 11:14:26 -07:00
import datetime
2021-08-07 07:07:19 -07:00
import json
import logging
import requests
import time
import typing
2021-12-01 08:14:30 -08:00
from base64 import b64decode
2021-08-07 07:07:19 -07:00
from decimal import Decimal
2021-12-01 08:14:30 -08:00
from solana . blockhash import Blockhash , BlockhashCache
2021-10-11 09:08:54 -07:00
from solana . keypair import Keypair
2021-08-07 07:07:19 -07:00
from solana . publickey import PublicKey
2021-08-27 12:37:23 -07:00
from solana . rpc . api import Client
2021-08-07 07:07:19 -07:00
from solana . rpc . commitment import Commitment
2021-12-01 08:14:30 -08:00
from solana . rpc . providers . http import HTTPProvider
from solana . rpc . types import DataSliceOpts , MemcmpOpts , RPCMethod , RPCResponse , TokenAccountOpts , TxOpts
2021-08-27 12:37:23 -07:00
from solana . transaction import Transaction
2021-08-07 07:07:19 -07:00
from . constants import SOL_DECIMAL_DIVISOR
2021-08-19 01:46:54 -07:00
from . instructionreporter import InstructionReporter
2021-10-15 12:37:43 -07:00
from . logmessages import expand_log_messages
2021-08-07 07:07:19 -07:00
2021-09-30 04:08:01 -07:00
# # 🥭 ClientException class
#
# A `ClientException` exception base class that allows trapping and handling rate limiting
# independent of other error handling.
#
class ClientException ( Exception ) :
2021-11-09 05:23:36 -08:00
def __init__ ( self , message : str , name : str , cluster_url : str ) - > None :
2021-09-30 04:08:01 -07:00
super ( ) . __init__ ( message )
self . message : str = message
self . name : str = name
self . cluster_url : str = cluster_url
def __str__ ( self ) - > str :
return f " « { type ( self ) } ' { self . message } ' from ' { self . name } ' on { self . cluster_url } » "
def __repr__ ( self ) - > str :
return f " { self } "
2021-08-07 07:07:19 -07:00
# # 🥭 RateLimitException class
#
# A `RateLimitException` exception base class that allows trapping and handling rate limiting
# independent of other error handling.
#
2021-09-30 04:08:01 -07:00
class RateLimitException ( ClientException ) :
2021-08-07 07:07:19 -07:00
pass
# # 🥭 TooMuchBandwidthRateLimitException class
#
# A `TooMuchBandwidthRateLimitException` exception that specialises the `RateLimitException`
# for when too much bandwidth has been consumed.
#
class TooMuchBandwidthRateLimitException ( RateLimitException ) :
pass
# # 🥭 TooManyRequestsRateLimitException class
#
# A `TooManyRequestsRateLimitException` exception that specialises the `RateLimitException`
# for when too many requests have been sent in a short time.
#
class TooManyRequestsRateLimitException ( RateLimitException ) :
pass
2021-09-30 02:48:55 -07:00
# # 🥭 BlockhashNotFoundException class
#
# A `BlockhashNotFoundException` exception allows trapping and handling exceptions when a blockhash is sent that
# the node doesn't understand. This can happen when the blockhash is too old (and the node no longer
# considers it 'recent') or when it's too new (and hasn't yet made it to the node that is responding).
#
2021-09-30 04:08:01 -07:00
class BlockhashNotFoundException ( ClientException ) :
2021-11-09 05:23:36 -08:00
def __init__ ( self , name : str , cluster_url : str , blockhash : typing . Optional [ Blockhash ] = None ) - > None :
2021-09-30 03:23:33 -07:00
message : str = f " Blockhash ' { blockhash } ' not found on { cluster_url } . "
2021-09-30 04:08:01 -07:00
super ( ) . __init__ ( message , name , cluster_url )
2021-09-30 03:23:33 -07:00
self . blockhash : typing . Optional [ Blockhash ] = blockhash
2021-09-30 02:48:55 -07:00
def __str__ ( self ) - > str :
2021-10-01 06:51:39 -07:00
return f " « BlockhashNotFoundException ' { self . name } ' [ { self . blockhash } ] on { self . cluster_url } » "
2021-09-30 02:48:55 -07:00
# # 🥭 NodeIsBehindException class
#
# A `NodeIsBehindException` exception allows trapping and handling exceptions when a node is behind by too
# many slots.
#
2021-09-30 04:08:01 -07:00
class NodeIsBehindException ( ClientException ) :
2021-11-09 05:23:36 -08:00
def __init__ ( self , name : str , cluster_url : str , slots_behind : int ) - > None :
2021-09-30 03:23:33 -07:00
message : str = f " Node is behind by { slots_behind } slots. "
2021-09-30 04:08:01 -07:00
super ( ) . __init__ ( message , name , cluster_url )
2021-09-30 03:23:33 -07:00
self . slots_behind : int = slots_behind
2021-09-30 02:48:55 -07:00
def __str__ ( self ) - > str :
2021-10-01 06:51:39 -07:00
return f " « NodeIsBehindException ' { self . name } ' [behind by { self . slots_behind } slots] on { self . cluster_url } » "
2021-09-30 02:48:55 -07:00
2021-09-30 03:23:33 -07:00
# # 🥭 FailedToFetchBlockhashException class
#
# A `FailedToFetchBlockhashException` exception allows trapping and handling exceptions when we fail
# to fetch a recent or distinct blockhash.
#
2021-09-30 04:08:01 -07:00
class FailedToFetchBlockhashException ( ClientException ) :
2021-11-09 05:23:36 -08:00
def __init__ ( self , message : str , name : str , cluster_url : str , pauses : typing . Sequence [ float ] ) - > None :
2021-09-30 04:08:01 -07:00
super ( ) . __init__ ( message , name , cluster_url )
2021-10-01 06:51:39 -07:00
self . pauses : typing . Sequence [ float ] = pauses
2021-09-30 03:23:33 -07:00
def __str__ ( self ) - > str :
2021-10-01 06:51:39 -07:00
if len ( self . pauses ) == 0 :
return f " « FailedToFetchBlockhashException ' { self . name } ' Failed to get recent blockhash on { self . cluster_url } » "
pauses_text = " , " . join ( f " { pause } " for pause in self . pauses [ : - 1 ] )
return f " « FailedToFetchBlockhashException ' { self . name } ' Failed to get a fresh, recent blockhash after { len ( self . pauses ) } attempts - paused { pauses_text } seconds between attempts on { self . cluster_url } » "
2021-09-30 03:23:33 -07:00
2021-08-07 07:07:19 -07:00
# # 🥭 TransactionException class
#
# A `TransactionException` exception that can provide additional error data, or at least better output
# of problems at the right place.
#
2021-09-30 04:08:01 -07:00
class TransactionException ( ClientException ) :
2021-11-09 05:23:36 -08:00
def __init__ ( self , transaction : typing . Optional [ Transaction ] , message : str , code : int , name : str , cluster_url : str , rpc_method : str , request_text : str , response_text : str , accounts : typing . Union [ str , typing . List [ str ] , None ] , errors : typing . Union [ str , typing . List [ str ] , None ] , logs : typing . Union [ str , typing . List [ str ] , None ] , instruction_reporter : InstructionReporter = InstructionReporter ( ) ) - > None :
2021-09-30 04:08:01 -07:00
super ( ) . __init__ ( message , name , cluster_url )
2021-08-19 01:46:54 -07:00
self . transaction : typing . Optional [ Transaction ] = transaction
2021-08-07 07:07:19 -07:00
self . code : int = code
2021-08-31 02:40:05 -07:00
self . rpc_method : str = rpc_method
self . request_text : str = request_text
self . response_text : str = response_text
2021-08-17 04:50:27 -07:00
def _ensure_list ( item : typing . Union [ str , typing . List [ str ] , None ] ) - > typing . List [ str ] :
if item is None :
return [ ]
if isinstance ( item , str ) :
return [ item ]
2021-08-17 12:45:26 -07:00
if isinstance ( item , list ) :
return item
return [ f " { item } " ]
2021-10-15 12:37:43 -07:00
self . accounts : typing . Sequence [ str ] = _ensure_list ( accounts )
self . errors : typing . Sequence [ str ] = _ensure_list ( errors )
self . logs : typing . Sequence [ str ] = expand_log_messages ( _ensure_list ( logs ) )
2021-08-19 01:46:54 -07:00
self . instruction_reporter : InstructionReporter = instruction_reporter
2021-08-07 07:07:19 -07:00
def __str__ ( self ) - > str :
2021-12-02 00:48:15 -08:00
try :
request_details : str = " "
response_details : str = " "
if logging . DEBUG > = logging . root . level :
request_details = f """
2021-08-31 02:40:05 -07:00
Request :
{ self . request_text } """
response_details = f """
Response :
{ self . response_text } """
2021-12-02 00:48:15 -08:00
transaction_details = " "
if self . transaction is not None :
instruction_details = " \n " . join (
list ( map ( self . instruction_reporter . report , self . transaction . instructions ) ) )
transaction_details = " \n Instructions: \n " + instruction_details . replace ( " \n " , " \n " )
accounts = " No Accounts "
if len ( self . accounts ) > 0 :
accounts = " \n " . join ( [ f " { item } " . replace ( " \n " , " \n " ) for item in self . accounts ] )
errors = " No Errors "
if len ( self . errors ) > 0 :
errors = " \n " . join ( [ f " { item } " . replace ( " \n " , " \n " ) for item in self . errors ] )
logs = " No Logs "
if len ( self . logs ) > 0 :
logs = " \n " . join ( [ f " { item } " . replace ( " \n " , " \n " ) for item in self . logs ] )
return f """ « 𝚃 𝚛 𝚊 𝚗 𝚜 𝚊 𝚌 𝚝 𝚒 𝚘 𝚗 𝙴 𝚡 𝚌 𝚎 𝚙 𝚝 𝚒 𝚘 𝚗 in ' { self . name } ' [ { self . rpc_method } ]: { self . code } :: { self . message } { transaction_details }
2021-08-07 07:07:19 -07:00
Accounts :
{ accounts }
Errors :
{ errors }
Logs :
2021-08-31 02:40:05 -07:00
{ logs } { request_details } { response_details }
2021-08-07 07:07:19 -07:00
» """
2021-12-02 00:48:15 -08:00
except Exception as exception :
return f " TransactionException printing failed with: { exception } "
2021-08-07 07:07:19 -07:00
def __repr__ ( self ) - > str :
return f " { self } "
UnspecifiedCommitment = Commitment ( " unspecified " )
UnspecifiedEncoding = " unspecified "
2021-12-01 08:14:30 -08:00
# # 🥭 ErrorHandlingProvider class
2021-08-07 07:07:19 -07:00
#
2021-12-01 08:14:30 -08:00
# A `ErrorHandlingProvider` extends the HTTPProvider with better error handling.
2021-08-07 07:07:19 -07:00
#
2021-12-01 08:14:30 -08:00
class ErrorHandlingProvider ( HTTPProvider ) :
def __init__ ( self , name : str , cluster_url : str , instruction_reporter : InstructionReporter ) :
super ( ) . __init__ ( cluster_url )
2021-08-09 02:27:47 -07:00
self . name : str = name
2021-08-07 07:07:19 -07:00
self . cluster_url : str = cluster_url
2021-09-15 11:27:07 -07:00
self . instruction_reporter : InstructionReporter = instruction_reporter
2021-08-19 01:46:54 -07:00
2021-12-01 08:14:30 -08:00
def make_request ( self , method : RPCMethod , * params : typing . Any ) - > RPCResponse :
# This is the entire method in HTTPProvider that we're overriding here:
#
# """Make an HTTP request to an http rpc endpoint."""
# request_kwargs = self._before_request(method=method, params=params, is_async=False)
# raw_response = requests.post(**request_kwargs)
# return self._after_request(raw_response=raw_response, method=method)
2021-08-07 07:07:19 -07:00
2021-12-01 08:14:30 -08:00
request_kwargs = self . _before_request ( method = method , params = params , is_async = False )
raw_response = requests . post ( * * request_kwargs )
2021-08-07 07:07:19 -07:00
# Some custom exceptions specifically for rate-limiting. This allows calling code to handle this
# specific case if they so choose.
#
# "You will see HTTP respose codes 429 for too many requests or 413 for too much bandwidth."
if raw_response . status_code == 413 :
2021-09-30 04:08:01 -07:00
raise TooMuchBandwidthRateLimitException (
f " Rate limited (too much bandwidth) calling method ' { method } ' . " , self . name , self . cluster_url )
2021-08-07 07:07:19 -07:00
elif raw_response . status_code == 429 :
2021-09-30 04:08:01 -07:00
raise TooManyRequestsRateLimitException (
f " Rate limited (too many requests) calling method ' { method } ' . " , self . name , self . cluster_url )
2021-08-07 07:07:19 -07:00
# Not a rate-limit problem, but maybe there was some other error?
raw_response . raise_for_status ( )
# All seems OK, but maybe the server returned an error? If so, try to pass on as much
# information as we can.
2021-08-31 02:40:05 -07:00
response_text : str = raw_response . text
2021-11-09 05:23:36 -08:00
response : typing . Dict [ str , typing . Any ] = json . loads ( response_text )
2021-08-07 07:07:19 -07:00
if " error " in response :
if response [ " error " ] is str :
message : str = typing . cast ( str , response [ " error " ] )
2021-09-30 04:08:01 -07:00
raise ClientException ( f " Transaction failed: ' { message } ' " , self . name , self . cluster_url )
2021-08-07 07:07:19 -07:00
else :
2021-12-01 08:14:30 -08:00
error = response [ " error " ]
error_message : str = error [ " message " ] if " message " in error else " No message "
error_data : typing . Dict [ str , typing . Any ] = error [ " data " ] if " data " in error else { }
2021-08-07 07:07:19 -07:00
error_accounts = error_data [ " accounts " ] if " accounts " in error_data else " No accounts "
2021-12-01 08:14:30 -08:00
error_code : int = error [ " code " ] if " code " in error else - 1
2021-08-09 13:07:21 -07:00
error_err = error_data [ " err " ] if " err " in error_data else " No error text returned "
2021-08-07 07:07:19 -07:00
error_logs = error_data [ " logs " ] if " logs " in error_data else " No logs "
2021-12-01 08:14:30 -08:00
parameters = json . dumps ( { " jsonrpc " : " 2.0 " , " method " : method , " params " : params } )
2021-08-07 07:07:19 -07:00
2021-12-01 08:14:30 -08:00
transaction : typing . Optional [ Transaction ] = None
blockhash : typing . Optional [ Blockhash ] = None
if method == " sendTransaction " :
transaction = Transaction . deserialize ( b64decode ( params [ 0 ] ) )
blockhash = transaction . recent_blockhash
2021-08-07 07:07:19 -07:00
2021-12-01 08:14:30 -08:00
if error_code == - 32005 :
slots_behind : int = error [ " data " ] [ " numSlotsBehind " ] if " numSlotsBehind " in error [ " data " ] else - 1
raise NodeIsBehindException ( self . name , self . cluster_url , slots_behind )
2021-08-07 07:07:19 -07:00
2021-12-01 08:14:30 -08:00
if error_err == " BlockhashNotFound " :
raise BlockhashNotFoundException ( self . name , self . cluster_url , blockhash )
2021-08-07 07:07:19 -07:00
2021-12-01 08:14:30 -08:00
exception_message : str = f " Transaction failed with: ' { error_message } ' "
raise TransactionException ( transaction , exception_message , error_code , self . name ,
self . cluster_url , method , parameters , response_text , error_accounts ,
error_err , error_logs , self . instruction_reporter )
2021-08-07 07:07:19 -07:00
2021-12-01 08:14:30 -08:00
# The call succeeded.
return typing . cast ( RPCResponse , response )
2021-08-07 07:07:19 -07:00
class BetterClient :
2021-12-01 08:14:30 -08:00
def __init__ ( self , client : Client , name : str , cluster_name : str , cluster_url : str , commitment : Commitment , skip_preflight : bool , encoding : str , blockhash_cache_duration : int , instruction_reporter : InstructionReporter ) - > None :
2021-08-07 07:07:19 -07:00
self . logger : logging . Logger = logging . getLogger ( self . __class__ . __name__ )
2021-12-01 08:14:30 -08:00
self . compatible_client : Client = client
self . name : str = name
self . cluster_name : str = cluster_name
self . cluster_url : str = cluster_url
self . commitment : Commitment = commitment
self . skip_preflight : bool = skip_preflight
self . encoding : str = encoding
self . blockhash_cache_duration : int = blockhash_cache_duration
self . instruction_reporter : InstructionReporter = instruction_reporter
2021-08-07 07:07:19 -07:00
# kangda said in Discord: https://discord.com/channels/791995070613159966/836239696467591186/847816026245693451
# "I think you are better off doing 4,8,16,20,30"
self . retry_pauses : typing . Sequence [ Decimal ] = [ Decimal ( 4 ) , Decimal (
8 ) , Decimal ( 16 ) , Decimal ( 20 ) , Decimal ( 30 ) ]
@staticmethod
2021-12-01 08:14:30 -08:00
def from_configuration ( name : str , cluster_name : str , cluster_url : str , commitment : Commitment , skip_preflight : bool , encoding : str , blockhash_cache_duration : int , instruction_reporter : InstructionReporter ) - > " BetterClient " :
provider : HTTPProvider = ErrorHandlingProvider ( name , cluster_url , instruction_reporter )
blockhash_cache : typing . Union [ BlockhashCache , bool ] = False
if blockhash_cache_duration > 0 :
blockhash_cache = BlockhashCache ( blockhash_cache_duration )
client : Client = Client ( cluster_url , commitment = commitment , blockhash_cache = blockhash_cache )
client . _provider = provider
2021-08-07 07:07:19 -07:00
2021-12-01 08:14:30 -08:00
return BetterClient ( client , name , cluster_name , cluster_url , commitment , skip_preflight , encoding , blockhash_cache_duration , instruction_reporter )
2021-08-07 07:07:19 -07:00
def get_balance ( self , pubkey : typing . Union [ PublicKey , str ] , commitment : Commitment = UnspecifiedCommitment ) - > Decimal :
2021-12-01 08:14:30 -08:00
resolved_commitment , _ = self . __resolve_defaults ( commitment )
response = self . compatible_client . get_balance ( pubkey , resolved_commitment )
2021-08-07 07:07:19 -07:00
value = Decimal ( response [ " result " ] [ " value " ] )
return value / SOL_DECIMAL_DIVISOR
def get_account_info ( self , pubkey : typing . Union [ PublicKey , str ] , commitment : Commitment = UnspecifiedCommitment ,
2021-11-09 05:23:36 -08:00
encoding : str = UnspecifiedEncoding , data_slice : typing . Optional [ DataSliceOpts ] = None ) - > typing . Any :
2021-12-01 08:14:30 -08:00
resolved_commitment , resolved_encoding = self . __resolve_defaults ( commitment , encoding )
response = self . compatible_client . get_account_info ( pubkey , resolved_commitment , resolved_encoding , data_slice )
2021-08-07 07:07:19 -07:00
return response [ " result " ]
2021-11-25 08:28:30 -08:00
def get_confirmed_signatures_for_address2 ( self , account : typing . Union [ str , Keypair , PublicKey ] , before : typing . Optional [ str ] = None , until : typing . Optional [ str ] = None , limit : typing . Optional [ int ] = None ) - > typing . Sequence [ str ] :
response = self . compatible_client . get_confirmed_signature_for_address2 ( account , before , until , limit )
2021-08-07 07:07:19 -07:00
return [ result [ " signature " ] for result in response [ " result " ] ]
2021-11-09 05:23:36 -08:00
def get_confirmed_transaction ( self , signature : str , encoding : str = " json " ) - > typing . Any :
2021-12-01 08:14:30 -08:00
_ , resolved_encoding = self . __resolve_defaults ( None , encoding )
response = self . compatible_client . get_confirmed_transaction ( signature , resolved_encoding )
2021-08-07 07:07:19 -07:00
return response [ " result " ]
def get_minimum_balance_for_rent_exemption ( self , size : int , commitment : Commitment = UnspecifiedCommitment ) - > int :
2021-12-01 08:14:30 -08:00
resolved_commitment , _ = self . __resolve_defaults ( commitment )
response = self . compatible_client . get_minimum_balance_for_rent_exemption ( size , resolved_commitment )
2021-11-09 05:23:36 -08:00
return int ( response [ " result " ] )
2021-08-07 07:07:19 -07:00
def get_program_accounts ( self , pubkey : typing . Union [ str , PublicKey ] ,
commitment : Commitment = UnspecifiedCommitment ,
encoding : typing . Optional [ str ] = UnspecifiedEncoding ,
data_slice : typing . Optional [ DataSliceOpts ] = None ,
data_size : typing . Optional [ int ] = None ,
2021-11-09 05:23:36 -08:00
memcmp_opts : typing . Optional [ typing . List [ MemcmpOpts ] ] = None ) - > typing . Any :
2021-12-01 08:14:30 -08:00
resolved_commitment , resolved_encoding = self . __resolve_defaults ( commitment , encoding )
2021-08-07 07:07:19 -07:00
response = self . compatible_client . get_program_accounts (
2021-12-01 08:14:30 -08:00
pubkey , resolved_commitment , resolved_encoding , data_slice , data_size , memcmp_opts )
2021-08-07 07:07:19 -07:00
return response [ " result " ]
def get_recent_blockhash ( self , commitment : Commitment = UnspecifiedCommitment ) - > Blockhash :
2021-12-01 08:14:30 -08:00
resolved_commitment , _ = self . __resolve_defaults ( commitment )
response = self . compatible_client . get_recent_blockhash ( resolved_commitment )
2021-08-07 07:07:19 -07:00
return Blockhash ( response [ " result " ] [ " value " ] [ " blockhash " ] )
2021-09-13 08:15:20 -07:00
def get_token_account_balance ( self , pubkey : typing . Union [ str , PublicKey ] , commitment : Commitment = UnspecifiedCommitment ) - > Decimal :
2021-12-01 08:14:30 -08:00
resolved_commitment , _ = self . __resolve_defaults ( commitment )
response = self . compatible_client . get_token_account_balance ( pubkey , resolved_commitment )
2021-09-13 08:15:20 -07:00
value = Decimal ( response [ " result " ] [ " value " ] [ " amount " ] )
decimal_places = response [ " result " ] [ " value " ] [ " decimals " ]
divisor = Decimal ( 10 * * decimal_places )
return value / divisor
2021-08-07 07:07:19 -07:00
2021-11-09 05:23:36 -08:00
def get_token_accounts_by_owner ( self , owner : PublicKey , token_account_options : TokenAccountOpts , commitment : Commitment = UnspecifiedCommitment , ) - > typing . Any :
2021-12-01 08:14:30 -08:00
resolved_commitment , _ = self . __resolve_defaults ( commitment )
response = self . compatible_client . get_token_accounts_by_owner ( owner , token_account_options , resolved_commitment )
2021-08-07 07:07:19 -07:00
return response [ " result " ] [ " value " ]
2021-12-01 08:14:30 -08:00
def get_multiple_accounts ( self , pubkeys : typing . List [ typing . Union [ PublicKey , str ] ] , commitment : Commitment = UnspecifiedCommitment ,
2021-11-09 05:23:36 -08:00
encoding : str = UnspecifiedEncoding , data_slice : typing . Optional [ DataSliceOpts ] = None ) - > typing . Any :
2021-12-01 08:14:30 -08:00
resolved_commitment , resolved_encoding = self . __resolve_defaults ( commitment , encoding )
response = self . compatible_client . get_multiple_accounts (
pubkeys , resolved_commitment , resolved_encoding , data_slice )
2021-08-07 07:07:19 -07:00
return response [ " result " ] [ " value " ]
2021-10-11 09:08:54 -07:00
def send_transaction ( self , transaction : Transaction , * signers : Keypair , opts : TxOpts = TxOpts ( preflight_commitment = UnspecifiedCommitment ) ) - > str :
2021-12-01 08:14:30 -08:00
proper_commitment : Commitment = opts . preflight_commitment
if proper_commitment == UnspecifiedCommitment :
proper_commitment = self . commitment
proper_opts = TxOpts ( preflight_commitment = proper_commitment ,
skip_confirmation = opts . skip_confirmation ,
skip_preflight = opts . skip_preflight )
response = self . compatible_client . send_transaction ( transaction , * signers , opts = proper_opts )
2021-11-09 05:23:36 -08:00
return str ( response [ " result " ] )
2021-08-07 07:07:19 -07:00
def wait_for_confirmation ( self , transaction_ids : typing . Sequence [ str ] , max_wait_in_seconds : int = 60 ) - > typing . Sequence [ str ] :
self . logger . info ( f " Waiting up to { max_wait_in_seconds } seconds for { transaction_ids } . " )
all_confirmed : typing . List [ str ] = [ ]
2021-08-11 11:14:26 -07:00
start_time : datetime . datetime = datetime . datetime . now ( )
cutoff : datetime . datetime = start_time + datetime . timedelta ( seconds = max_wait_in_seconds )
for transaction_id in transaction_ids :
while datetime . datetime . now ( ) < cutoff :
2021-08-07 07:07:19 -07:00
time . sleep ( 1 )
confirmed = self . get_confirmed_transaction ( transaction_id )
if confirmed is not None :
2021-08-11 11:14:26 -07:00
self . logger . info (
f " Confirmed { transaction_id } after { datetime . datetime . now ( ) - start_time } seconds. " )
2021-08-07 07:07:19 -07:00
all_confirmed + = [ transaction_id ]
2021-08-11 11:14:26 -07:00
break
2021-08-07 07:07:19 -07:00
if len ( all_confirmed ) != len ( transaction_ids ) :
2021-08-11 11:14:26 -07:00
self . logger . info ( f " Timed out after { max_wait_in_seconds } seconds waiting on transaction { transaction_id } . " )
2021-08-07 07:07:19 -07:00
return all_confirmed
2021-12-01 08:14:30 -08:00
def __resolve_defaults ( self , commitment : typing . Optional [ Commitment ] , encoding : typing . Optional [ str ] = None ) - > typing . Tuple [ Commitment , str ] :
if commitment is None or commitment == UnspecifiedCommitment :
commitment = self . commitment
if encoding is None or encoding == UnspecifiedEncoding :
encoding = self . encoding
return commitment , encoding
2021-08-07 07:07:19 -07:00
def __str__ ( self ) - > str :
2021-08-26 02:31:02 -07:00
return f " « 𝙱 𝚎 𝚝 𝚝 𝚎 𝚛 𝙲 𝚕 𝚒 𝚎 𝚗 𝚝 [ { self . cluster_name } ]: { self . cluster_url } » "
2021-08-07 07:07:19 -07:00
def __repr__ ( self ) - > str :
return f " { self } "