2021-06-07 07:10:18 -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 typing
|
|
|
|
|
|
|
|
|
|
from decimal import Decimal
|
|
|
|
|
from solana.publickey import PublicKey
|
|
|
|
|
|
|
|
|
|
from .accountinfo import AccountInfo
|
|
|
|
|
from .addressableaccount import AddressableAccount
|
2021-10-04 10:27:07 -07:00
|
|
|
|
from .cache import Cache, PriceCache, PerpMarketCache
|
2021-06-07 07:10:18 -07:00
|
|
|
|
from .context import Context
|
|
|
|
|
from .layouts import layouts
|
2021-10-02 04:28:16 -07:00
|
|
|
|
from .lotsizeconverter import LotSizeConverter, RaisingLotSizeConverter
|
2021-10-04 10:27:07 -07:00
|
|
|
|
|
2021-06-25 07:50:37 -07:00
|
|
|
|
from .metadata import Metadata
|
|
|
|
|
from .perpmarketinfo import PerpMarketInfo
|
2021-07-01 13:24:08 -07:00
|
|
|
|
from .rootbank import RootBank
|
2021-06-25 07:50:37 -07:00
|
|
|
|
from .spotmarketinfo import SpotMarketInfo
|
2021-07-19 07:04:53 -07:00
|
|
|
|
from .token import SolToken, Token
|
2021-06-25 07:50:37 -07:00
|
|
|
|
from .tokeninfo import TokenInfo
|
2021-06-25 03:10:41 -07:00
|
|
|
|
from .tokenlookup import TokenLookup
|
2021-06-07 07:10:18 -07:00
|
|
|
|
from .tokenvalue import TokenValue
|
|
|
|
|
from .version import Version
|
|
|
|
|
|
2021-06-25 07:50:37 -07:00
|
|
|
|
|
2021-08-03 10:32:12 -07:00
|
|
|
|
# # 🥭 GroupBasketMarket class
|
|
|
|
|
#
|
|
|
|
|
# `GroupBasketMarket` gathers basket items together instead of separate arrays.
|
|
|
|
|
#
|
|
|
|
|
class GroupBasketMarket:
|
2021-10-02 04:28:16 -07:00
|
|
|
|
def __init__(self, base_token_info: TokenInfo, quote_token_info: TokenInfo, spot_market_info: SpotMarketInfo, perp_market_info: typing.Optional[PerpMarketInfo], perp_lot_size_converter: LotSizeConverter, oracle: PublicKey):
|
2021-08-03 10:32:12 -07:00
|
|
|
|
self.base_token_info: TokenInfo = base_token_info
|
|
|
|
|
self.quote_token_info: TokenInfo = quote_token_info
|
|
|
|
|
self.spot_market_info: SpotMarketInfo = spot_market_info
|
2021-10-02 04:28:16 -07:00
|
|
|
|
self.perp_market_info: typing.Optional[PerpMarketInfo] = perp_market_info
|
|
|
|
|
self.perp_lot_size_converter: LotSizeConverter = perp_lot_size_converter
|
2021-08-03 10:32:12 -07:00
|
|
|
|
self.oracle: PublicKey = oracle
|
|
|
|
|
|
|
|
|
|
def __str__(self) -> str:
|
|
|
|
|
base_token_info = f"{self.base_token_info}".replace("\n", "\n ")
|
|
|
|
|
quote_token_info = f"{self.quote_token_info}".replace("\n", "\n ")
|
|
|
|
|
spot_market_info = f"{self.spot_market_info}".replace("\n", "\n ")
|
|
|
|
|
perp_market_info = f"{self.perp_market_info}".replace("\n", "\n ")
|
|
|
|
|
return f"""« 𝙶𝚛𝚘𝚞𝚙𝙱𝚊𝚜𝚔𝚎𝚝𝙼𝚊𝚛𝚔𝚎𝚝 {self.base_token_info.token.symbol}
|
|
|
|
|
Base Token:
|
|
|
|
|
{base_token_info}
|
|
|
|
|
Quote Token:
|
|
|
|
|
{quote_token_info}
|
|
|
|
|
Oracle: {self.oracle}
|
|
|
|
|
Spot Market:
|
|
|
|
|
{spot_market_info}
|
|
|
|
|
Perp Market:
|
|
|
|
|
{perp_market_info}
|
|
|
|
|
»"""
|
|
|
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
|
|
|
|
return f"{self}"
|
|
|
|
|
|
|
|
|
|
|
2021-08-04 05:08:34 -07:00
|
|
|
|
TMappedGroupBasketValue = typing.TypeVar("TMappedGroupBasketValue")
|
2021-08-03 10:32:12 -07:00
|
|
|
|
|
|
|
|
|
|
2021-06-07 07:10:18 -07:00
|
|
|
|
# # 🥭 Group class
|
|
|
|
|
#
|
2021-06-25 07:50:37 -07:00
|
|
|
|
# `Group` defines root functionality for Mango Markets.
|
|
|
|
|
#
|
2021-06-07 07:10:18 -07:00
|
|
|
|
class Group(AddressableAccount):
|
2021-06-08 00:17:37 -07:00
|
|
|
|
def __init__(self, account_info: AccountInfo, version: Version, name: str,
|
2021-08-03 10:32:12 -07:00
|
|
|
|
meta_data: Metadata,
|
|
|
|
|
shared_quote_token: TokenInfo,
|
|
|
|
|
basket_indices: typing.Sequence[bool],
|
|
|
|
|
basket: typing.Sequence[GroupBasketMarket],
|
|
|
|
|
signer_nonce: Decimal, signer_key: PublicKey,
|
2021-08-26 02:31:02 -07:00
|
|
|
|
admin: PublicKey, serum_program_address: PublicKey, cache: PublicKey, valid_interval: Decimal,
|
2021-10-04 10:27:07 -07:00
|
|
|
|
insurance_vault: PublicKey, srm_vault: PublicKey, msrm_vault: PublicKey, fees_vault: PublicKey):
|
2021-06-07 07:10:18 -07:00
|
|
|
|
super().__init__(account_info)
|
|
|
|
|
self.version: Version = version
|
2021-06-08 00:17:37 -07:00
|
|
|
|
self.name: str = name
|
2021-06-25 07:50:37 -07:00
|
|
|
|
|
|
|
|
|
self.meta_data: Metadata = meta_data
|
2021-08-03 10:32:12 -07:00
|
|
|
|
self.shared_quote_token: TokenInfo = shared_quote_token
|
|
|
|
|
self.basket_indices: typing.Sequence[bool] = basket_indices
|
|
|
|
|
self.basket: typing.Sequence[GroupBasketMarket] = basket
|
2021-06-07 07:10:18 -07:00
|
|
|
|
self.signer_nonce: Decimal = signer_nonce
|
|
|
|
|
self.signer_key: PublicKey = signer_key
|
|
|
|
|
self.admin: PublicKey = admin
|
2021-08-26 02:31:02 -07:00
|
|
|
|
self.serum_program_address: PublicKey = serum_program_address
|
2021-06-25 07:50:37 -07:00
|
|
|
|
self.cache: PublicKey = cache
|
|
|
|
|
self.valid_interval: Decimal = valid_interval
|
2021-10-04 10:27:07 -07:00
|
|
|
|
self.insurance_vault: PublicKey = insurance_vault
|
2021-07-13 04:43:50 -07:00
|
|
|
|
self.srm_vault: PublicKey = srm_vault
|
|
|
|
|
self.msrm_vault: PublicKey = msrm_vault
|
2021-10-04 10:27:07 -07:00
|
|
|
|
self.fees_vault: PublicKey = fees_vault
|
2021-06-07 07:10:18 -07:00
|
|
|
|
|
|
|
|
|
@property
|
2021-08-03 10:32:12 -07:00
|
|
|
|
def base_tokens(self) -> typing.Sequence[typing.Optional[TokenInfo]]:
|
|
|
|
|
return Group._map_sequence_to_basket_indices(self.basket, self.basket_indices, lambda item: item.base_token_info)
|
2021-06-07 07:10:18 -07:00
|
|
|
|
|
|
|
|
|
@property
|
2021-08-03 10:32:12 -07:00
|
|
|
|
def tokens(self) -> typing.Sequence[typing.Optional[TokenInfo]]:
|
|
|
|
|
return [*self.base_tokens, self.shared_quote_token]
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def oracles(self) -> typing.Sequence[typing.Optional[PublicKey]]:
|
|
|
|
|
return Group._map_sequence_to_basket_indices(self.basket, self.basket_indices, lambda item: item.oracle)
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def spot_markets(self) -> typing.Sequence[typing.Optional[SpotMarketInfo]]:
|
|
|
|
|
return Group._map_sequence_to_basket_indices(self.basket, self.basket_indices, lambda item: item.spot_market_info)
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def perp_markets(self) -> typing.Sequence[typing.Optional[PerpMarketInfo]]:
|
|
|
|
|
return Group._map_sequence_to_basket_indices(self.basket, self.basket_indices, lambda item: item.perp_market_info)
|
2021-06-25 07:50:37 -07:00
|
|
|
|
|
2021-08-30 16:30:22 -07:00
|
|
|
|
@property
|
|
|
|
|
def markets(self) -> typing.Sequence[typing.Optional[GroupBasketMarket]]:
|
|
|
|
|
return Group._map_sequence_to_basket_indices(self.basket, self.basket_indices, lambda item: item)
|
|
|
|
|
|
2021-06-07 07:10:18 -07:00
|
|
|
|
@staticmethod
|
2021-10-04 10:27:07 -07:00
|
|
|
|
def from_layout(layout: typing.Any, name: str, account_info: AccountInfo, version: Version, root_banks: typing.Sequence[RootBank], token_lookup: TokenLookup) -> "Group":
|
2021-07-01 13:24:08 -07:00
|
|
|
|
meta_data: Metadata = Metadata.from_layout(layout.meta_data)
|
|
|
|
|
tokens: typing.List[typing.Optional[TokenInfo]] = [
|
|
|
|
|
TokenInfo.from_layout_or_none(t, token_lookup, root_banks) for t in layout.tokens]
|
|
|
|
|
|
2021-08-03 10:32:12 -07:00
|
|
|
|
quote_token_info: typing.Optional[TokenInfo] = tokens[-1]
|
|
|
|
|
if quote_token_info is None:
|
|
|
|
|
raise Exception("Could not find quote token info at end of group tokens.")
|
|
|
|
|
basket: typing.List[GroupBasketMarket] = []
|
|
|
|
|
in_basket: typing.List[bool] = []
|
|
|
|
|
for index, base_token_info in enumerate(tokens[:-1]):
|
|
|
|
|
if base_token_info is not None:
|
2021-10-02 04:28:16 -07:00
|
|
|
|
spot_market_info: typing.Optional[SpotMarketInfo] = SpotMarketInfo.from_layout_or_none(
|
|
|
|
|
layout.spot_markets[index])
|
|
|
|
|
if spot_market_info is None:
|
|
|
|
|
raise Exception(f"Could not find spot market at index {index} of group layout.")
|
|
|
|
|
perp_market_info: typing.Optional[PerpMarketInfo] = PerpMarketInfo.from_layout_or_none(
|
|
|
|
|
layout.perp_markets[index])
|
|
|
|
|
perp_lot_size_converter: LotSizeConverter = RaisingLotSizeConverter()
|
|
|
|
|
if perp_market_info is not None:
|
|
|
|
|
perp_lot_size_converter = LotSizeConverter(
|
|
|
|
|
base_token_info.token, perp_market_info.base_lot_size, quote_token_info.token, perp_market_info.quote_lot_size)
|
|
|
|
|
|
2021-08-03 10:32:12 -07:00
|
|
|
|
oracle: PublicKey = layout.oracles[index]
|
|
|
|
|
item: GroupBasketMarket = GroupBasketMarket(
|
2021-10-02 04:28:16 -07:00
|
|
|
|
base_token_info, quote_token_info, spot_market_info, perp_market_info, perp_lot_size_converter, oracle)
|
2021-08-03 10:32:12 -07:00
|
|
|
|
basket += [item]
|
|
|
|
|
in_basket += [True]
|
|
|
|
|
else:
|
|
|
|
|
in_basket += [False]
|
|
|
|
|
|
2021-07-01 13:24:08 -07:00
|
|
|
|
signer_nonce: Decimal = layout.signer_nonce
|
|
|
|
|
signer_key: PublicKey = layout.signer_key
|
|
|
|
|
admin: PublicKey = layout.admin
|
2021-08-26 02:31:02 -07:00
|
|
|
|
serum_program_address: PublicKey = layout.serum_program_address
|
2021-07-01 13:24:08 -07:00
|
|
|
|
cache: PublicKey = layout.cache
|
|
|
|
|
valid_interval: Decimal = layout.valid_interval
|
2021-10-04 10:27:07 -07:00
|
|
|
|
insurance_vault: PublicKey = layout.insurance_vault
|
2021-07-13 04:43:50 -07:00
|
|
|
|
srm_vault: PublicKey = layout.srm_vault
|
|
|
|
|
msrm_vault: PublicKey = layout.msrm_vault
|
2021-10-04 10:27:07 -07:00
|
|
|
|
fees_vault: PublicKey = layout.fees_vault
|
2021-07-01 13:24:08 -07:00
|
|
|
|
|
2021-10-04 10:27:07 -07:00
|
|
|
|
return Group(account_info, version, name, meta_data, quote_token_info, in_basket, basket, signer_nonce, signer_key, admin, serum_program_address, cache, valid_interval, insurance_vault, srm_vault, msrm_vault, fees_vault)
|
2021-06-07 07:10:18 -07:00
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def parse(context: Context, account_info: AccountInfo) -> "Group":
|
|
|
|
|
data = account_info.data
|
2021-06-25 07:50:37 -07:00
|
|
|
|
if len(data) != layouts.GROUP.sizeof():
|
2021-06-07 07:10:18 -07:00
|
|
|
|
raise Exception(
|
2021-07-12 02:57:05 -07:00
|
|
|
|
f"Group data length ({len(data)}) does not match expected size ({layouts.GROUP.sizeof()})")
|
2021-06-07 07:10:18 -07:00
|
|
|
|
|
2021-06-28 01:48:16 -07:00
|
|
|
|
name = context.lookup_group_name(account_info.address)
|
2021-10-04 10:27:07 -07:00
|
|
|
|
layout = layouts.GROUP.parse(data)
|
|
|
|
|
root_bank_addresses = [ti.root_bank for ti in layout.tokens if ti is not None and ti.root_bank is not None]
|
|
|
|
|
root_banks = RootBank.load_multiple(context, root_bank_addresses)
|
|
|
|
|
return Group.parse_locally(account_info, name, root_banks, context.token_lookup)
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def parse_locally(account_info: AccountInfo, name: str, root_banks: typing.Sequence[RootBank], token_lookup: TokenLookup) -> "Group":
|
|
|
|
|
data = account_info.data
|
|
|
|
|
if len(data) != layouts.GROUP.sizeof():
|
|
|
|
|
raise Exception(
|
|
|
|
|
f"Group data length ({len(data)}) does not match expected size ({layouts.GROUP.sizeof()})")
|
|
|
|
|
|
|
|
|
|
layout = layouts.GROUP.parse(data)
|
|
|
|
|
return Group.from_layout(layout, name, account_info, Version.V3, root_banks, token_lookup)
|
2021-06-07 07:10:18 -07:00
|
|
|
|
|
2021-08-03 10:32:12 -07:00
|
|
|
|
@staticmethod
|
2021-08-04 05:08:34 -07:00
|
|
|
|
def _map_sequence_to_basket_indices(items: typing.Sequence[GroupBasketMarket], in_basket: typing.Sequence[bool], selector: typing.Callable[[typing.Any], TMappedGroupBasketValue]) -> typing.Sequence[typing.Optional[TMappedGroupBasketValue]]:
|
|
|
|
|
mapped_items: typing.List[typing.Optional[TMappedGroupBasketValue]] = []
|
2021-08-03 10:32:12 -07:00
|
|
|
|
basket_counter = 0
|
|
|
|
|
for available in in_basket:
|
|
|
|
|
if available:
|
|
|
|
|
mapped_items += [selector(items[basket_counter])]
|
|
|
|
|
basket_counter += 1
|
|
|
|
|
else:
|
|
|
|
|
mapped_items += [None]
|
|
|
|
|
|
|
|
|
|
return mapped_items
|
|
|
|
|
|
2021-06-07 07:10:18 -07:00
|
|
|
|
@staticmethod
|
2021-06-25 07:50:37 -07:00
|
|
|
|
def load(context: Context, address: typing.Optional[PublicKey] = None) -> "Group":
|
2021-08-26 02:31:02 -07:00
|
|
|
|
group_address: PublicKey = address or context.group_address
|
2021-06-25 07:50:37 -07:00
|
|
|
|
account_info = AccountInfo.load(context, group_address)
|
2021-06-07 07:10:18 -07:00
|
|
|
|
if account_info is None:
|
2021-06-25 07:50:37 -07:00
|
|
|
|
raise Exception(f"Group account not found at address '{group_address}'")
|
2021-06-07 07:10:18 -07:00
|
|
|
|
return Group.parse(context, account_info)
|
|
|
|
|
|
2021-07-12 10:26:35 -07:00
|
|
|
|
def find_spot_market_index(self, spot_market_address: PublicKey) -> int:
|
|
|
|
|
for index, spot in enumerate(self.spot_markets):
|
|
|
|
|
if spot is not None and spot.address == spot_market_address:
|
|
|
|
|
return index
|
|
|
|
|
|
|
|
|
|
raise Exception(f"Could not find spot market {spot_market_address} in group {self.address}")
|
|
|
|
|
|
|
|
|
|
def find_perp_market_index(self, perp_market_address: PublicKey) -> int:
|
|
|
|
|
for index, pm in enumerate(self.perp_markets):
|
|
|
|
|
if pm is not None and pm.address == perp_market_address:
|
|
|
|
|
return index
|
|
|
|
|
|
|
|
|
|
raise Exception(f"Could not find perp market {perp_market_address} in group {self.address}")
|
|
|
|
|
|
2021-08-30 16:30:22 -07:00
|
|
|
|
def find_base_token_market_index(self, base_token: TokenInfo) -> int:
|
|
|
|
|
for index, bt in enumerate(self.base_tokens):
|
|
|
|
|
if bt is not None and bt.token == base_token.token:
|
|
|
|
|
return index
|
|
|
|
|
|
|
|
|
|
raise Exception(f"Could not find base token {base_token} in group {self.address}")
|
|
|
|
|
|
2021-10-04 10:27:07 -07:00
|
|
|
|
def find_token_market_index_or_none(self, token: Token) -> typing.Optional[int]:
|
|
|
|
|
for index, bt in enumerate(self.base_tokens):
|
|
|
|
|
if bt is not None and bt.token == token:
|
|
|
|
|
return index
|
|
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def find_token_market_index(self, token: Token) -> int:
|
|
|
|
|
index = self.find_token_market_index_or_none(token)
|
|
|
|
|
if index is not None:
|
|
|
|
|
return index
|
|
|
|
|
|
|
|
|
|
raise Exception(f"Could not find token {token} in group {self.address}")
|
|
|
|
|
|
2021-07-19 07:04:53 -07:00
|
|
|
|
def find_token_info_by_token(self, token: Token) -> TokenInfo:
|
2021-07-29 04:38:34 -07:00
|
|
|
|
for token_info in self.tokens:
|
2021-07-19 07:04:53 -07:00
|
|
|
|
if token_info is not None and token_info.token == token:
|
|
|
|
|
return token_info
|
|
|
|
|
|
|
|
|
|
raise Exception(f"Could not find token info for mint {token.mint} in group {self.address}")
|
|
|
|
|
|
2021-08-11 11:14:26 -07:00
|
|
|
|
def find_token_info_by_symbol(self, symbol: str) -> TokenInfo:
|
|
|
|
|
for token_info in self.tokens:
|
|
|
|
|
if token_info is not None and token_info.token.symbol_matches(symbol):
|
|
|
|
|
return token_info
|
|
|
|
|
|
|
|
|
|
raise Exception(f"Could not find token info for symbol '{symbol}' in group {self.address}")
|
|
|
|
|
|
2021-10-04 10:27:07 -07:00
|
|
|
|
def token_price_from_cache(self, cache: Cache, token: Token) -> TokenValue:
|
|
|
|
|
if token == self.shared_quote_token.token:
|
|
|
|
|
# The price of 1 unit of the shared quote token is always 1.
|
|
|
|
|
return TokenValue(token, Decimal(1))
|
|
|
|
|
|
|
|
|
|
token_index: int = self.find_token_market_index(token)
|
|
|
|
|
cached_price: typing.Optional[PriceCache] = cache.price_cache[token_index]
|
|
|
|
|
if cached_price is None:
|
|
|
|
|
raise Exception(f"Could not find price index of basket token {token.symbol}.")
|
|
|
|
|
|
|
|
|
|
price: Decimal = cached_price.price
|
|
|
|
|
decimals_difference = token.decimals - self.shared_quote_token.decimals
|
|
|
|
|
if decimals_difference != 0:
|
|
|
|
|
adjustment = 10 ** decimals_difference
|
|
|
|
|
price = price * adjustment
|
|
|
|
|
|
|
|
|
|
return TokenValue(self.shared_quote_token.token, price)
|
|
|
|
|
|
|
|
|
|
def perp_market_cache_from_cache(self, cache: Cache, token: Token) -> typing.Optional[PerpMarketCache]:
|
|
|
|
|
token_index: int = self.find_token_market_index(token)
|
|
|
|
|
return cache.perp_market_cache[token_index]
|
|
|
|
|
|
2021-06-25 07:50:37 -07:00
|
|
|
|
def fetch_balances(self, context: Context, root_address: PublicKey) -> typing.Sequence[TokenValue]:
|
2021-06-07 07:10:18 -07:00
|
|
|
|
balances: typing.List[TokenValue] = []
|
2021-08-07 07:07:19 -07:00
|
|
|
|
sol_balance = context.client.get_balance(root_address)
|
2021-06-07 07:10:18 -07:00
|
|
|
|
balances += [TokenValue(SolToken, sol_balance)]
|
|
|
|
|
|
2021-06-25 07:50:37 -07:00
|
|
|
|
for basket_token in self.tokens:
|
|
|
|
|
if basket_token is not None and basket_token.token is not None:
|
|
|
|
|
balance = TokenValue.fetch_total_value(context, root_address, basket_token.token)
|
|
|
|
|
balances += [balance]
|
2021-06-07 07:10:18 -07:00
|
|
|
|
return balances
|
|
|
|
|
|
2021-08-01 10:03:46 -07:00
|
|
|
|
def __str__(self) -> str:
|
2021-08-03 10:32:12 -07:00
|
|
|
|
basket_count = len(self.basket)
|
|
|
|
|
basket = "\n ".join([f"{item}".replace("\n", "\n ") for item in self.basket])
|
2021-06-25 07:50:37 -07:00
|
|
|
|
return f"""« 𝙶𝚛𝚘𝚞𝚙 {self.version} [{self.address}]
|
|
|
|
|
{self.meta_data}
|
|
|
|
|
Name: {self.name}
|
|
|
|
|
Signer [Nonce: {self.signer_nonce}]: {self.signer_key}
|
|
|
|
|
Admin: {self.admin}
|
2021-08-26 02:31:02 -07:00
|
|
|
|
DEX Program ID: {self.serum_program_address}
|
2021-07-01 13:24:08 -07:00
|
|
|
|
Cache: {self.cache}
|
2021-10-04 10:27:07 -07:00
|
|
|
|
Insurance Vault: {self.insurance_vault}
|
2021-07-13 04:43:50 -07:00
|
|
|
|
SRM Vault: {self.srm_vault}
|
|
|
|
|
MSRM Vault: {self.msrm_vault}
|
2021-10-04 10:27:07 -07:00
|
|
|
|
Fees Vault: {self.fees_vault}
|
2021-06-25 07:50:37 -07:00
|
|
|
|
Valid Interval: {self.valid_interval}
|
2021-08-03 10:32:12 -07:00
|
|
|
|
Basket [{basket_count} markets]:
|
|
|
|
|
{basket}
|
2021-06-25 07:50:37 -07:00
|
|
|
|
»"""
|