2021-07-23 02:20:44 -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)
import argparse
2021-09-07 13:44:48 -07:00
import datetime
2021-08-26 10:03:42 -07:00
import copy
2021-07-23 02:20:44 -07:00
import os
2021-08-25 09:06:00 -07:00
import typing
2021-07-23 02:20:44 -07:00
2021-09-13 04:17:19 -07:00
from decimal import Decimal
2021-07-23 02:20:44 -07:00
from solana . publickey import PublicKey
2021-08-26 10:03:42 -07:00
from . client import BetterClient
2021-07-23 02:20:44 -07:00
from . constants import MangoConstants
from . context import Context
from . idsjsonmarketlookup import IdsJsonMarketLookup
from . idsjsontokenlookup import IdsJsonTokenLookup
from . marketlookup import CompoundMarketLookup , MarketLookup
from . serummarketlookup import SerumMarketLookup
from . spltokenlookup import SplTokenLookup
from . tokenlookup import TokenLookup , CompoundTokenLookup
# # 🥭 ContextBuilder
#
# ## Environment Variables
#
# It's possible to override the values in the `Context` variables provided. This can be easier than creating
# the `Context` in code or introducing dependencies and configuration.
#
# The following environment variables are read:
2021-08-25 09:06:00 -07:00
# * NAME
# * CLUSTER
# * CLUSTER_URL
# * GROUP_NAME
# * GROUP_ADDRESS
2021-08-26 02:31:02 -07:00
# * MANGO_PROGRAM_ADDRESS
2021-08-25 09:06:00 -07:00
# * SERUM_PROGRAM_ADDRESS
2021-07-23 02:20:44 -07:00
# # 🥭 ContextBuilder class
#
# A `ContextBuilder` class to allow building `Context` objects without introducing circular dependencies.
#
class ContextBuilder :
# Configuring a `Context` is a common operation for command-line programs and can involve a
# lot of duplicate code.
#
# This function centralises some of it to ensure consistency and readability.
#
@staticmethod
2021-09-13 06:05:19 -07:00
def add_command_line_parameters ( parser : argparse . ArgumentParser ) - > None :
2021-08-25 09:06:00 -07:00
parser . add_argument ( " --name " , type = str , default = " Mango Explorer " ,
2021-08-09 02:27:47 -07:00
help = " Name of the program (used in reports and alerts) " )
2021-08-26 02:31:02 -07:00
parser . add_argument ( " --cluster-name " , type = str , default = None , help = " Solana RPC cluster name " )
2021-08-25 09:06:00 -07:00
parser . add_argument ( " --cluster-url " , type = str , default = None , help = " Solana RPC cluster URL " )
parser . add_argument ( " --group-name " , type = str , default = None , help = " Mango group name " )
2021-08-26 02:31:02 -07:00
parser . add_argument ( " --group-address " , type = PublicKey , default = None , help = " Mango group address " )
parser . add_argument ( " --mango-program-address " , type = PublicKey , default = None , help = " Mango program address " )
parser . add_argument ( " --serum-program-address " , type = PublicKey , default = None , help = " Serum program address " )
parser . add_argument ( " --skip-preflight " , default = False , action = " store_true " , help = " Skip pre-flight checks " )
2021-09-07 13:44:48 -07:00
parser . add_argument ( " --blockhash-cache-duration " , type = int , help = " How long to cache ' recent ' blockhashes " )
2021-09-13 04:17:19 -07:00
parser . add_argument ( " --gma-chunk-size " , type = Decimal , default = None ,
help = " Maximum number of addresses to send in a single call to getMultipleAccounts() " )
parser . add_argument ( " --gma-chunk-pause " , type = Decimal , default = None ,
help = " number of seconds to pause between successive getMultipleAccounts() calls to avoid rate limiting " )
2021-07-23 02:20:44 -07:00
parser . add_argument ( " --token-data-file " , type = str , default = SplTokenLookup . DefaultDataFilepath ,
help = " data file that contains token symbols, names, mints and decimals (format is same as https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/solana.tokenlist.json) " )
# This function is the converse of `add_command_line_parameters()` - it takes
# an argument of parsed command-line parameters and expects to see the ones it added
# to that collection in the `add_command_line_parameters()` call.
#
# It then uses those parameters to create a properly-configured `Context` object.
#
@staticmethod
2021-08-26 10:03:42 -07:00
def from_command_line_parameters ( args : argparse . Namespace ) - > Context :
2021-08-25 09:06:00 -07:00
name : typing . Optional [ str ] = args . name
2021-08-26 02:31:02 -07:00
cluster_name : typing . Optional [ str ] = args . cluster_name
2021-08-25 09:06:00 -07:00
cluster_url : typing . Optional [ str ] = args . cluster_url
2021-08-26 02:31:02 -07:00
group_name : typing . Optional [ str ] = args . group_name
group_address : typing . Optional [ PublicKey ] = args . group_address
mango_program_address : typing . Optional [ PublicKey ] = args . mango_program_address
serum_program_address : typing . Optional [ PublicKey ] = args . serum_program_address
2021-08-25 09:06:00 -07:00
skip_preflight : bool = bool ( args . skip_preflight )
2021-09-07 13:44:48 -07:00
blockhash_cache_duration : typing . Optional [ datetime . timedelta ] = datetime . timedelta (
seconds = args . blockhash_cache_duration ) if args . blockhash_cache_duration is not None else None
2021-09-13 04:17:19 -07:00
gma_chunk_size : typing . Optional [ Decimal ] = args . gma_chunk_size
gma_chunk_pause : typing . Optional [ Decimal ] = args . gma_chunk_pause
2021-07-23 02:20:44 -07:00
token_filename : str = args . token_data_file
2021-09-13 04:17:19 -07:00
return ContextBuilder . _build ( name , cluster_name , cluster_url , skip_preflight , blockhash_cache_duration , group_name , group_address , mango_program_address , serum_program_address , gma_chunk_size , gma_chunk_pause , token_filename )
2021-07-23 02:20:44 -07:00
@staticmethod
def default ( ) :
2021-09-13 04:17:19 -07:00
return ContextBuilder . _build ( None , None , None , False , None , None , None , None , None , None , None , SplTokenLookup . DefaultDataFilepath )
2021-08-25 09:06:00 -07:00
@staticmethod
def from_group_name ( context : Context , group_name : str ) - > Context :
2021-08-26 02:31:02 -07:00
return ContextBuilder . _build ( context . name , context . client . cluster_name , context . client . cluster_url ,
2021-09-07 13:44:48 -07:00
context . client . skip_preflight , context . client . compatible_client . blockhash_cache_duration ,
2021-09-13 04:17:19 -07:00
group_name , None , None , None , context . gma_chunk_size , context . gma_chunk_pause ,
SplTokenLookup . DefaultDataFilepath )
2021-08-25 09:06:00 -07:00
@staticmethod
def forced_to_devnet ( context : Context ) - > Context :
2021-08-26 02:31:02 -07:00
cluster_name : str = " devnet "
cluster_url : str = MangoConstants [ " cluster_urls " ] [ cluster_name ]
2021-08-26 10:03:42 -07:00
fresh_context = copy . copy ( context )
fresh_context . client = BetterClient . from_configuration ( context . name ,
cluster_name ,
cluster_url ,
context . client . commitment ,
context . client . skip_preflight ,
2021-09-07 13:44:48 -07:00
context . client . instruction_reporter ,
context . client . compatible_client . blockhash_cache_duration )
2021-08-26 10:03:42 -07:00
return fresh_context
2021-08-25 09:06:00 -07:00
@staticmethod
def forced_to_mainnet_beta ( context : Context ) - > Context :
2021-08-26 02:31:02 -07:00
cluster_name : str = " mainnet "
cluster_url : str = MangoConstants [ " cluster_urls " ] [ cluster_name ]
2021-08-26 10:03:42 -07:00
fresh_context = copy . copy ( context )
fresh_context . client = BetterClient . from_configuration ( context . name ,
cluster_name ,
cluster_url ,
context . client . commitment ,
context . client . skip_preflight ,
2021-09-07 13:44:48 -07:00
context . client . instruction_reporter ,
context . client . compatible_client . blockhash_cache_duration )
2021-08-26 10:03:42 -07:00
return fresh_context
2021-07-23 02:20:44 -07:00
# This function is the converse of `add_command_line_parameters()` - it takes
# an argument of parsed command-line parameters and expects to see the ones it added
# to that collection in the `add_command_line_parameters()` call.
#
# It then uses those parameters to create a properly-configured `Context` object.
#
@staticmethod
2021-08-26 02:31:02 -07:00
def _build ( name : typing . Optional [ str ] , cluster_name : typing . Optional [ str ] , cluster_url : typing . Optional [ str ] ,
2021-09-07 13:44:48 -07:00
skip_preflight : bool , blockhash_cache_duration : typing . Optional [ datetime . timedelta ] ,
group_name : typing . Optional [ str ] , group_address : typing . Optional [ PublicKey ] ,
2021-08-25 09:06:00 -07:00
program_address : typing . Optional [ PublicKey ] , serum_program_address : typing . Optional [ PublicKey ] ,
2021-09-13 04:17:19 -07:00
gma_chunk_size : typing . Optional [ Decimal ] , gma_chunk_pause : typing . Optional [ Decimal ] ,
2021-08-25 09:06:00 -07:00
token_filename : str ) - > " Context " :
def public_key_or_none ( address : typing . Optional [ str ] ) - > typing . Optional [ PublicKey ] :
if address is not None and address != " " :
return PublicKey ( address )
return None
2021-08-27 12:37:23 -07:00
# The first group is only used to determine the default cluster if it is not otherwise specified.
first_group_data = MangoConstants [ " groups " ] [ 0 ]
2021-08-25 09:06:00 -07:00
actual_name : str = name or os . environ . get ( " NAME " ) or " Mango Explorer "
2021-08-27 12:37:23 -07:00
actual_cluster : str = cluster_name or os . environ . get ( " CLUSTER_NAME " ) or first_group_data [ " cluster " ]
# Now that we have the actual cluster name, taking environment variables and defaults into account,
# we can decide what we want as the default group.
for group_data in MangoConstants [ " groups " ] :
if group_data [ " cluster " ] == actual_cluster :
default_group_data = group_data
break
2021-09-07 13:44:48 -07:00
actual_blockhash_cache_duration : datetime . timedelta = blockhash_cache_duration or datetime . timedelta ( seconds = 15 )
2021-08-25 09:06:00 -07:00
actual_cluster_url : str = cluster_url or os . environ . get (
" CLUSTER_URL " ) or MangoConstants [ " cluster_urls " ] [ actual_cluster ]
actual_skip_preflight : bool = skip_preflight
actual_group_name : str = group_name or os . environ . get ( " GROUP_NAME " ) or default_group_data [ " name " ]
found_group_data : typing . Any = None
for group in MangoConstants [ " groups " ] :
if group [ " cluster " ] == actual_cluster and group [ " name " ] . upper ( ) == actual_group_name . upper ( ) :
found_group_data = group
if found_group_data is None :
2021-08-26 05:43:23 -07:00
raise Exception ( f " Could not find group named ' { actual_group_name } ' in cluster ' { actual_cluster } ' . " )
2021-08-25 09:06:00 -07:00
actual_group_address : PublicKey = group_address or public_key_or_none ( os . environ . get (
" GROUP_ADDRESS " ) ) or PublicKey ( found_group_data [ " publicKey " ] )
actual_program_address : PublicKey = program_address or public_key_or_none ( os . environ . get (
2021-08-26 02:31:02 -07:00
" MANGO_PROGRAM_ADDRESS " ) ) or PublicKey ( found_group_data [ " mangoProgramId " ] )
2021-08-25 09:06:00 -07:00
actual_serum_program_address : PublicKey = serum_program_address or public_key_or_none ( os . environ . get (
" SERUM_PROGRAM_ADDRESS " ) ) or PublicKey ( found_group_data [ " serumProgramId " ] )
2021-09-13 04:17:19 -07:00
actual_gma_chunk_size : Decimal = gma_chunk_size or Decimal ( 100 )
actual_gma_chunk_pause : Decimal = gma_chunk_pause or Decimal ( 0 )
2021-08-25 09:06:00 -07:00
ids_json_token_lookup : TokenLookup = IdsJsonTokenLookup ( actual_cluster , actual_group_name )
2021-07-23 02:20:44 -07:00
all_token_lookup = ids_json_token_lookup
2021-08-25 09:06:00 -07:00
if actual_cluster == " mainnet " :
2021-07-23 02:20:44 -07:00
mainnet_spl_token_lookup : TokenLookup = SplTokenLookup . load ( token_filename )
all_token_lookup = CompoundTokenLookup ( [ ids_json_token_lookup , mainnet_spl_token_lookup ] )
2021-08-25 09:06:00 -07:00
elif actual_cluster == " devnet " :
2021-07-23 02:20:44 -07:00
devnet_token_filename = token_filename . rsplit ( ' . ' , 1 ) [ 0 ] + " .devnet.json "
devnet_spl_token_lookup : TokenLookup = SplTokenLookup . load ( devnet_token_filename )
all_token_lookup = CompoundTokenLookup ( [ ids_json_token_lookup , devnet_spl_token_lookup ] )
token_lookup : TokenLookup = all_token_lookup
2021-08-25 09:06:00 -07:00
ids_json_market_lookup : MarketLookup = IdsJsonMarketLookup ( actual_cluster )
2021-07-23 02:20:44 -07:00
all_market_lookup = ids_json_market_lookup
2021-08-25 09:06:00 -07:00
if actual_cluster == " mainnet " :
mainnet_serum_market_lookup : SerumMarketLookup = SerumMarketLookup . load (
actual_serum_program_address , token_filename )
2021-07-23 02:20:44 -07:00
all_market_lookup = CompoundMarketLookup ( [ ids_json_market_lookup , mainnet_serum_market_lookup ] )
2021-08-25 09:06:00 -07:00
elif actual_cluster == " devnet " :
2021-07-23 02:20:44 -07:00
devnet_token_filename = token_filename . rsplit ( ' . ' , 1 ) [ 0 ] + " .devnet.json "
2021-08-19 03:00:39 -07:00
devnet_serum_market_lookup : SerumMarketLookup = SerumMarketLookup . load (
2021-08-25 09:06:00 -07:00
actual_serum_program_address , devnet_token_filename )
2021-07-23 02:20:44 -07:00
all_market_lookup = CompoundMarketLookup ( [ ids_json_market_lookup , devnet_serum_market_lookup ] )
market_lookup : MarketLookup = all_market_lookup
2021-09-13 04:17:19 -07:00
return Context ( actual_name , actual_cluster , actual_cluster_url , actual_skip_preflight , actual_blockhash_cache_duration , actual_program_address , actual_serum_program_address , actual_group_name , actual_group_address , actual_gma_chunk_size , actual_gma_chunk_pause , token_lookup , market_lookup )