2021-06-25 03:10:41 -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 json
import typing
from decimal import Decimal
from solana . publickey import PublicKey
from . market import Market
from . marketlookup import MarketLookup
2021-07-23 03:42:22 -07:00
from . serummarket import SerumMarketStub
2021-06-25 03:10:41 -07:00
from . token import Token
# # 🥭 SerumMarketLookup class
#
# This class allows us to look up Serum market addresses and tokens, all from our Solana
# static data.
#
# The static data is the [Solana token list](https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/solana.tokenlist.json) provided by Serum.
#
# You can load a `SerumMarketLookup` class by something like:
# ```
# with open("solana.tokenlist.json") as json_file:
# token_data = json.load(json_file)
# spot_market_lookup = SerumMarketLookup(token_data)
# ```
2021-11-08 03:39:09 -08:00
# This uses the same data file as `SPLTokenLookup` but it looks a lot more complicated. The
2021-06-25 03:10:41 -07:00
# main reason for this is that tokens are described in a list, whereas markets are optional
# child attributes of tokens.
#
# To find a token, we can just go through the list.
#
# To find a market, we need to split the market symbol into the two token symbols, go through
# the list, check if the item has the optional `extensions` attribute, and in there see if
# there is a name-value pair for the particular market we're interested in. Also, the
# current file only lists USDC and USDT markets, so that's all we can support this way.
class SerumMarketLookup ( MarketLookup ) :
2021-11-09 05:23:36 -08:00
def __init__ ( self , serum_program_address : PublicKey , token_data : typing . Dict [ str , typing . Any ] ) - > None :
2021-06-25 03:10:41 -07:00
super ( ) . __init__ ( )
2021-08-26 02:31:02 -07:00
self . serum_program_address : PublicKey = serum_program_address
2021-11-09 05:23:36 -08:00
self . token_data : typing . Dict [ str , typing . Any ] = token_data
2021-06-25 03:10:41 -07:00
@staticmethod
2021-08-26 02:31:02 -07:00
def load ( serum_program_address : PublicKey , token_data_filename : str ) - > " SerumMarketLookup " :
2021-10-15 12:37:43 -07:00
with open ( token_data_filename , encoding = " utf-8 " ) as json_file :
2021-06-25 03:10:41 -07:00
token_data = json . load ( json_file )
2021-08-26 02:31:02 -07:00
return SerumMarketLookup ( serum_program_address , token_data )
2021-06-25 03:10:41 -07:00
@staticmethod
2021-11-09 05:23:36 -08:00
def _find_data_by_symbol ( symbol : str , token_data : typing . Dict [ str , typing . Any ] ) - > typing . Optional [ typing . Dict [ str , typing . Any ] ] :
2021-06-25 03:10:41 -07:00
for token in token_data [ " tokens " ] :
if token [ " symbol " ] == symbol :
2021-11-09 05:23:36 -08:00
return typing . cast ( typing . Dict [ str , typing . Any ] , token )
2021-06-25 03:10:41 -07:00
return None
@staticmethod
2021-11-09 05:23:36 -08:00
def _find_token_by_symbol_or_error ( symbol : str , token_data : typing . Dict [ str , typing . Any ] ) - > Token :
2021-06-25 03:10:41 -07:00
found_token_data = SerumMarketLookup . _find_data_by_symbol ( symbol , token_data )
if found_token_data is None :
raise Exception ( f " Could not find data for token symbol ' { symbol } ' . " )
2021-11-08 03:39:09 -08:00
return Token ( symbol , found_token_data [ " name " ] , Decimal ( found_token_data [ " decimals " ] ) , PublicKey ( found_token_data [ " address " ] ) )
2021-06-25 03:10:41 -07:00
def find_by_symbol ( self , symbol : str ) - > typing . Optional [ Market ] :
if " / " not in symbol :
return None
2021-09-07 10:40:34 -07:00
symbol = symbol . upper ( )
2021-07-13 12:01:32 -07:00
if symbol . startswith ( " SERUM: " ) :
symbol = symbol . split ( " : " , 1 ) [ 1 ]
2021-06-25 03:10:41 -07:00
base_symbol , quote_symbol = symbol . split ( " / " )
base_data = SerumMarketLookup . _find_data_by_symbol ( base_symbol , self . token_data )
if base_data is None :
2021-12-13 03:15:24 -08:00
self . _logger . warning ( f " Could not find data for base token ' { base_symbol } ' " )
2021-06-25 03:10:41 -07:00
return None
2021-11-08 03:39:09 -08:00
base = Token ( base_data [ " symbol " ] , base_data [ " name " ] , Decimal (
base_data [ " decimals " ] ) , PublicKey ( base_data [ " address " ] ) )
2021-06-25 03:10:41 -07:00
quote_data = SerumMarketLookup . _find_data_by_symbol ( quote_symbol , self . token_data )
if quote_data is None :
2021-12-13 03:15:24 -08:00
self . _logger . warning ( f " Could not find data for quote token ' { quote_symbol } ' " )
2021-06-25 03:10:41 -07:00
return None
2021-11-08 03:39:09 -08:00
quote = Token ( quote_data [ " symbol " ] , quote_data [ " name " ] , Decimal (
quote_data [ " decimals " ] ) , PublicKey ( quote_data [ " address " ] ) )
2021-06-25 03:10:41 -07:00
if " extensions " not in base_data :
2021-12-13 03:15:24 -08:00
self . _logger . warning ( f " No markets found for base token ' { base . symbol } ' . " )
2021-06-25 03:10:41 -07:00
return None
if quote . symbol == " USDC " :
if " serumV3Usdc " not in base_data [ " extensions " ] :
2021-12-13 03:15:24 -08:00
self . _logger . warning ( f " No USDC market found for base token ' { base . symbol } ' . " )
2021-06-25 03:10:41 -07:00
return None
market_address_string = base_data [ " extensions " ] [ " serumV3Usdc " ]
market_address = PublicKey ( market_address_string )
elif quote . symbol == " USDT " :
if " serumV3Usdt " not in base_data [ " extensions " ] :
2021-12-13 03:15:24 -08:00
self . _logger . warning ( f " No USDT market found for base token ' { base . symbol } ' . " )
2021-06-25 03:10:41 -07:00
return None
market_address_string = base_data [ " extensions " ] [ " serumV3Usdt " ]
market_address = PublicKey ( market_address_string )
else :
2021-12-13 03:15:24 -08:00
self . _logger . warning (
2021-06-25 03:10:41 -07:00
f " Could not find market with quote token ' { quote . symbol } ' . Only markets based on USDC or USDT are supported. " )
return None
2021-08-26 02:31:02 -07:00
return SerumMarketStub ( self . serum_program_address , market_address , base , quote )
2021-06-25 03:10:41 -07:00
def find_by_address ( self , address : PublicKey ) - > typing . Optional [ Market ] :
address_string : str = str ( address )
for token_data in self . token_data [ " tokens " ] :
if " extensions " in token_data :
if " serumV3Usdc " in token_data [ " extensions " ] :
if token_data [ " extensions " ] [ " serumV3Usdc " ] == address_string :
market_address_string = token_data [ " extensions " ] [ " serumV3Usdc " ]
market_address = PublicKey ( market_address_string )
2021-11-08 03:39:09 -08:00
base = Token ( token_data [ " symbol " ] , token_data [ " name " ] , Decimal (
token_data [ " decimals " ] ) , PublicKey ( token_data [ " address " ] ) )
2021-06-25 03:10:41 -07:00
quote_data = SerumMarketLookup . _find_data_by_symbol ( " USDC " , self . token_data )
if quote_data is None :
raise Exception ( " Could not load token data for USDC (which should always be present). " )
2021-11-08 03:39:09 -08:00
quote = Token ( quote_data [ " symbol " ] , quote_data [ " name " ] , Decimal (
quote_data [ " decimals " ] ) , PublicKey ( quote_data [ " address " ] ) )
2021-08-26 02:31:02 -07:00
return SerumMarketStub ( self . serum_program_address , market_address , base , quote )
2021-06-25 03:10:41 -07:00
if " serumV3Usdt " in token_data [ " extensions " ] :
if token_data [ " extensions " ] [ " serumV3Usdt " ] == address_string :
market_address_string = token_data [ " extensions " ] [ " serumV3Usdt " ]
market_address = PublicKey ( market_address_string )
2021-11-08 03:39:09 -08:00
base = Token ( token_data [ " symbol " ] , token_data [ " name " ] , Decimal (
token_data [ " decimals " ] ) , PublicKey ( token_data [ " address " ] ) )
2021-06-25 03:10:41 -07:00
quote_data = SerumMarketLookup . _find_data_by_symbol ( " USDT " , self . token_data )
if quote_data is None :
raise Exception ( " Could not load token data for USDT (which should always be present). " )
2021-11-08 03:39:09 -08:00
quote = Token ( quote_data [ " symbol " ] , quote_data [ " name " ] , Decimal (
quote_data [ " decimals " ] ) , PublicKey ( quote_data [ " address " ] ) )
2021-08-26 02:31:02 -07:00
return SerumMarketStub ( self . serum_program_address , market_address , base , quote )
2021-06-25 03:10:41 -07:00
return None
def all_markets ( self ) - > typing . Sequence [ Market ] :
usdt = SerumMarketLookup . _find_token_by_symbol_or_error ( " USDT " , self . token_data )
usdc = SerumMarketLookup . _find_token_by_symbol_or_error ( " USDC " , self . token_data )
2021-07-23 03:42:22 -07:00
all_markets : typing . List [ SerumMarketStub ] = [ ]
2021-06-25 03:10:41 -07:00
for token_data in self . token_data [ " tokens " ] :
if " extensions " in token_data :
if " serumV3Usdc " in token_data [ " extensions " ] :
market_address_string = token_data [ " extensions " ] [ " serumV3Usdc " ]
market_address = PublicKey ( market_address_string )
2021-11-08 03:39:09 -08:00
base = Token ( token_data [ " symbol " ] , token_data [ " name " ] , Decimal (
token_data [ " decimals " ] ) , PublicKey ( token_data [ " address " ] ) )
2021-08-26 02:31:02 -07:00
all_markets + = [ SerumMarketStub ( self . serum_program_address , market_address , base , usdc ) ]
2021-06-25 03:10:41 -07:00
if " serumV3Usdt " in token_data [ " extensions " ] :
market_address_string = token_data [ " extensions " ] [ " serumV3Usdt " ]
market_address = PublicKey ( market_address_string )
2021-11-08 03:39:09 -08:00
base = Token ( token_data [ " symbol " ] , token_data [ " name " ] , Decimal (
token_data [ " decimals " ] ) , PublicKey ( token_data [ " address " ] ) )
2021-08-26 02:31:02 -07:00
all_markets + = [ SerumMarketStub ( self . serum_program_address , market_address , base , usdt ) ]
2021-06-25 03:10:41 -07:00
return all_markets