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
import logging
import os
from solana . publickey import PublicKey
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:
# * CLUSTER (defaults to: mainnet-beta)
# * CLUSTER_URL (defaults to URL for RPC server for CLUSTER defined in `ids.json`)
# * GROUP_NAME (defaults to: BTC_ETH_USDT)
#
2021-08-13 13:34:17 -07:00
default_name : str = os . environ . get ( " NAME " ) or " Mango Explorer "
default_skip_preflight : bool = False
2021-08-09 02:27:47 -07:00
2021-07-23 02:20:44 -07:00
_default_group_data = MangoConstants [ " groups " ] [ 0 ]
2021-08-13 13:34:17 -07:00
default_cluster : str = os . environ . get ( " CLUSTER " ) or _default_group_data [ " cluster " ]
default_cluster_url : str = os . environ . get ( " CLUSTER_URL " ) or MangoConstants [ " cluster_urls " ] [ default_cluster ]
2021-07-23 02:20:44 -07:00
2021-08-13 13:34:17 -07:00
default_program_id : PublicKey = PublicKey ( _default_group_data [ " mangoProgramId " ] )
default_dex_program_id : PublicKey = PublicKey ( _default_group_data [ " serumProgramId " ] )
2021-07-23 02:20:44 -07:00
2021-08-13 13:34:17 -07:00
default_group_name : str = os . environ . get ( " GROUP_NAME " ) or _default_group_data [ " name " ]
default_group_id : PublicKey = PublicKey ( _default_group_data [ " publicKey " ] )
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
def add_command_line_parameters ( parser : argparse . ArgumentParser , logging_default = logging . INFO ) - > None :
2021-08-09 02:27:47 -07:00
parser . add_argument ( " --name " , type = str , default = default_name ,
help = " Name of the program (used in reports and alerts) " )
2021-07-23 02:20:44 -07:00
parser . add_argument ( " --cluster " , type = str , default = default_cluster ,
help = " Solana RPC cluster name " )
parser . add_argument ( " --cluster-url " , type = str , default = default_cluster_url ,
help = " Solana RPC cluster URL " )
2021-08-13 13:34:17 -07:00
parser . add_argument ( " --skip-preflight " , default = default_skip_preflight , action = " store_true " ,
help = " Skip Solana pre-flight checks " )
2021-07-26 04:19:13 -07:00
parser . add_argument ( " --program-id " , type = PublicKey , default = default_program_id ,
2021-07-23 02:20:44 -07:00
help = " Mango program ID/address " )
2021-07-26 04:19:13 -07:00
parser . add_argument ( " --dex-program-id " , type = PublicKey , default = default_dex_program_id ,
2021-07-23 02:20:44 -07:00
help = " DEX program ID/address " )
parser . add_argument ( " --group-name " , type = str , default = default_group_name ,
help = " Mango group name " )
2021-07-26 04:19:13 -07:00
parser . add_argument ( " --group-id " , type = PublicKey , default = default_group_id ,
2021-07-23 02:20:44 -07:00
help = " Mango group ID/address " )
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 isn't really a Context thing but we don't have a better place for it (yet) and we
# don't want to duplicate it in every command.
parser . add_argument ( " --log-level " , default = logging_default , type = lambda level : getattr ( logging , level ) ,
help = " level of verbosity to log (possible values: DEBUG, INFO, WARNING, ERROR, CRITICAL) " )
# 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
def from_command_line_parameters ( args : argparse . Namespace ) - > " Context " :
# Here we should have values for all our parameters (because they'll either be specified
# on the command-line or will be the default_* value) but we may be in the situation where
# a group name is specified but not a group ID, and in that case we want to look up the
# group ID.
#
# In that situation, the group_name will not be default_group_name but the group_id will
# still be default_group_id. In that situation we want to override what we were passed
# as the group_id.
2021-08-09 02:27:47 -07:00
name : str = args . name
2021-07-23 02:20:44 -07:00
group_name : str = args . group_name
cluster : str = args . cluster
cluster_url : str = args . cluster_url
2021-08-13 13:34:17 -07:00
skip_preflight : bool = args . skip_preflight
2021-07-23 02:20:44 -07:00
token_filename : str = args . token_data_file
group_id : PublicKey = args . group_id
program_id : PublicKey = args . program_id
dex_program_id : PublicKey = args . dex_program_id
2021-08-13 13:34:17 -07:00
return ContextBuilder . _build ( name , cluster , cluster_url , skip_preflight , group_name , group_id , program_id , dex_program_id , token_filename )
2021-07-23 02:20:44 -07:00
@staticmethod
def default ( ) :
2021-08-13 13:34:17 -07:00
return ContextBuilder . _build ( default_name , default_cluster , default_cluster_url ,
default_skip_preflight , default_group_name , default_group_id ,
default_program_id , default_dex_program_id ,
SplTokenLookup . DefaultDataFilepath )
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-13 13:34:17 -07:00
def _build ( name : str , cluster : str , cluster_url : str , skip_preflight : bool , group_name : str , group_id : PublicKey , program_id : PublicKey , dex_program_id : PublicKey , token_filename : str ) - > " Context " :
2021-07-23 02:20:44 -07:00
ids_json_token_lookup : TokenLookup = IdsJsonTokenLookup ( cluster , group_name )
all_token_lookup = ids_json_token_lookup
if cluster == " mainnet-beta " :
mainnet_spl_token_lookup : TokenLookup = SplTokenLookup . load ( token_filename )
all_token_lookup = CompoundTokenLookup ( [ ids_json_token_lookup , mainnet_spl_token_lookup ] )
elif cluster == " devnet " :
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
ids_json_market_lookup : MarketLookup = IdsJsonMarketLookup ( cluster )
all_market_lookup = ids_json_market_lookup
if cluster == " mainnet-beta " :
mainnet_serum_market_lookup : SerumMarketLookup = SerumMarketLookup . load ( token_filename )
all_market_lookup = CompoundMarketLookup ( [ ids_json_market_lookup , mainnet_serum_market_lookup ] )
elif cluster == " devnet " :
devnet_token_filename = token_filename . rsplit ( ' . ' , 1 ) [ 0 ] + " .devnet.json "
devnet_serum_market_lookup : SerumMarketLookup = SerumMarketLookup . load ( devnet_token_filename )
all_market_lookup = CompoundMarketLookup ( [ ids_json_market_lookup , devnet_serum_market_lookup ] )
market_lookup : MarketLookup = all_market_lookup
if ( group_name != default_group_name ) and ( group_id == default_group_id ) :
for group in MangoConstants [ " groups " ] :
if group [ " cluster " ] == cluster and group [ " name " ] . upper ( ) == group_name . upper ( ) :
group_id = PublicKey ( group [ " publicKey " ] )
elif ( cluster != default_cluster ) and ( group_id == default_group_id ) :
for group in MangoConstants [ " groups " ] :
if group [ " cluster " ] == cluster and group [ " name " ] . upper ( ) == group_name . upper ( ) :
group_id = PublicKey ( group [ " publicKey " ] )
# Same problem here, but with cluster names and URLs. We want someone to be able to change the
# cluster just by changing the cluster name.
if ( cluster != default_cluster ) and ( cluster_url == default_cluster_url ) :
cluster_url = MangoConstants [ " cluster_urls " ] [ cluster ]
2021-08-13 13:34:17 -07:00
return Context ( name , cluster , cluster_url , skip_preflight , program_id , dex_program_id , group_name , group_id , token_lookup , market_lookup )