Updated to handle Groups with 5 tokens as well as Groups with 3 tokens.

This commit is contained in:
Geoff Taylor 2021-05-27 17:07:02 +01:00
parent 8b21f4c025
commit b6a595bb21
14 changed files with 1417 additions and 370 deletions

View File

@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "markdown",
"id": "altered-grade",
"id": "rising-train",
"metadata": {},
"source": [
"# ⚠ Warning\n",
@ -16,7 +16,7 @@
},
{
"cell_type": "markdown",
"id": "appointed-formula",
"id": "later-blind",
"metadata": {},
"source": [
"# 🥭 BaseModel\n",
@ -33,7 +33,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "organizational-scratch",
"id": "active-discount",
"metadata": {
"jupyter": {
"source_hidden": true
@ -42,6 +42,7 @@
"outputs": [],
"source": [
"import abc\n",
"import construct\n",
"import datetime\n",
"import enum\n",
"import logging\n",
@ -60,14 +61,14 @@
"from spl.token.client import Token as SplToken\n",
"from spl.token.constants import TOKEN_PROGRAM_ID\n",
"\n",
"from Constants import NUM_MARKETS, NUM_TOKENS, SOL_DECIMALS, SYSTEM_PROGRAM_ADDRESS\n",
"from Constants import SOL_DECIMALS, SOL_MINT_ADDRESS, SYSTEM_PROGRAM_ADDRESS\n",
"from Context import Context\n",
"from Decoder import decode_binary, encode_binary, encode_key\n"
"from Decoder import decode_binary, encode_int, encode_binary, encode_key\n"
]
},
{
"cell_type": "markdown",
"id": "premium-blind",
"id": "metric-dakota",
"metadata": {},
"source": [
"## Version enum\n",
@ -78,7 +79,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "treated-opening",
"id": "adjusted-constitutional",
"metadata": {},
"outputs": [],
"source": [
@ -93,7 +94,7 @@
},
{
"cell_type": "markdown",
"id": "municipal-wireless",
"id": "numerical-hanging",
"metadata": {},
"source": [
"## InstructionType enum\n",
@ -104,7 +105,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "coastal-biotechnology",
"id": "unique-nickname",
"metadata": {},
"outputs": [],
"source": [
@ -133,7 +134,34 @@
},
{
"cell_type": "markdown",
"id": "bottom-pickup",
"id": "round-intent",
"metadata": {},
"source": [
"## Internal functions\n",
"\n",
"These aren't published, they're really just for internal use. If they become more widely used we can move them to a better place."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "swedish-burns",
"metadata": {},
"outputs": [],
"source": [
"def _split_list_into_chunks(to_chunk: typing.List, chunk_size: int = 1000) -> typing.List[typing.List]:\n",
" chunks = []\n",
" start = 0\n",
" while start < len(to_chunk):\n",
" chunk = to_chunk[start:start + chunk_size]\n",
" chunks += [chunk]\n",
" start += chunk_size\n",
" return chunks\n"
]
},
{
"cell_type": "markdown",
"id": "specialized-paragraph",
"metadata": {},
"source": [
"## AccountInfo class\n"
@ -142,7 +170,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "ordinary-waterproof",
"id": "taken-employment",
"metadata": {},
"outputs": [],
"source": [
@ -181,10 +209,14 @@
"\n",
" @staticmethod\n",
" def load_multiple(context: Context, addresses: typing.List[PublicKey]) -> typing.List[\"AccountInfo\"]:\n",
" address_strings = list(map(PublicKey.__str__, addresses))\n",
" response = context.client._provider.make_request(RPCMethod(\"getMultipleAccounts\"), address_strings)\n",
" response_value_list = zip(response[\"result\"][\"value\"], addresses)\n",
" return list(map(lambda pair: AccountInfo._from_response_values(pair[0], pair[1]), response_value_list))\n",
" address_strings: typing.List[str] = list(map(PublicKey.__str__, addresses))\n",
" multiple: typing.List[AccountInfo] = []\n",
" for chunk in _split_list_into_chunks(address_strings):\n",
" response = context.client._provider.make_request(RPCMethod(\"getMultipleAccounts\"), address_strings)\n",
" result = context.unwrap_or_raise_exception(response)\n",
" response_value_list = zip(result[\"value\"], addresses)\n",
" multiple += list(map(lambda pair: AccountInfo._from_response_values(pair[0], pair[1]), response_value_list))\n",
" return multiple\n",
"\n",
" @staticmethod\n",
" def _from_response_values(response_values: typing.Dict[str, typing.Any], address: PublicKey) -> \"AccountInfo\":\n",
@ -202,7 +234,7 @@
},
{
"cell_type": "markdown",
"id": "injured-henry",
"id": "sporting-lafayette",
"metadata": {},
"source": [
"## AddressableAccount class\n",
@ -215,7 +247,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "expanded-creator",
"id": "worldwide-theta",
"metadata": {},
"outputs": [],
"source": [
@ -234,7 +266,7 @@
},
{
"cell_type": "markdown",
"id": "lyric-nevada",
"id": "peripheral-musical",
"metadata": {},
"source": [
"## SerumAccountFlags class\n",
@ -245,7 +277,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "posted-immunology",
"id": "limited-leisure",
"metadata": {},
"outputs": [],
"source": [
@ -288,7 +320,7 @@
},
{
"cell_type": "markdown",
"id": "gorgeous-tucson",
"id": "minute-twenty",
"metadata": {},
"source": [
"## MangoAccountFlags class\n",
@ -299,7 +331,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "chemical-irish",
"id": "final-proxy",
"metadata": {},
"outputs": [],
"source": [
@ -314,8 +346,8 @@
"\n",
" @staticmethod\n",
" def from_layout(layout: layouts.MANGO_ACCOUNT_FLAGS) -> \"MangoAccountFlags\":\n",
" return MangoAccountFlags(Version.UNSPECIFIED, layout.initialized, layout.group, layout.margin_account,\n",
" layout.srm_account)\n",
" return MangoAccountFlags(Version.UNSPECIFIED, layout.initialized, layout.group,\n",
" layout.margin_account, layout.srm_account)\n",
"\n",
" def __str__(self) -> str:\n",
" flags: typing.List[typing.Optional[str]] = []\n",
@ -332,7 +364,7 @@
},
{
"cell_type": "markdown",
"id": "proved-victoria",
"id": "solved-links",
"metadata": {},
"source": [
"## Index class"
@ -341,7 +373,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "noble-death",
"id": "lightweight-composite",
"metadata": {},
"outputs": [],
"source": [
@ -360,7 +392,7 @@
" return Index(Version.UNSPECIFIED, layout.last_update, borrow, deposit)\n",
"\n",
" def __str__(self) -> str:\n",
" return f\"« Index: Borrow: {self.borrow:,.8f}, Deposit: {self.deposit:,.8f} [last update: {self.last_update}] »\"\n",
" return f\"« Index [{self.last_update}]: Borrow: {self.borrow:,.8f}, Deposit: {self.deposit:,.8f} »\"\n",
"\n",
" def __repr__(self) -> str:\n",
" return f\"{self}\"\n"
@ -368,7 +400,7 @@
},
{
"cell_type": "markdown",
"id": "muslim-cooking",
"id": "intense-imagination",
"metadata": {},
"source": [
"## AggregatorConfig class"
@ -377,7 +409,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "accessory-decade",
"id": "demonstrated-classic",
"metadata": {},
"outputs": [],
"source": [
@ -410,7 +442,7 @@
},
{
"cell_type": "markdown",
"id": "cubic-politics",
"id": "double-salvation",
"metadata": {},
"source": [
"## Round class"
@ -419,7 +451,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "generic-livestock",
"id": "controlled-gabriel",
"metadata": {},
"outputs": [],
"source": [
@ -444,7 +476,7 @@
},
{
"cell_type": "markdown",
"id": "consistent-devil",
"id": "adaptive-rainbow",
"metadata": {},
"source": [
"## Answer class"
@ -453,7 +485,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "necessary-socket",
"id": "accompanied-plymouth",
"metadata": {},
"outputs": [],
"source": [
@ -479,7 +511,7 @@
},
{
"cell_type": "markdown",
"id": "subtle-guinea",
"id": "superb-voice",
"metadata": {},
"source": [
"## Aggregator class"
@ -488,7 +520,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "tutorial-warning",
"id": "better-pacific",
"metadata": {},
"outputs": [],
"source": [
@ -554,7 +586,7 @@
},
{
"cell_type": "markdown",
"id": "insured-notion",
"id": "broke-motel",
"metadata": {},
"source": [
"## Token class\n",
@ -565,7 +597,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "professional-cooper",
"id": "exclusive-brother",
"metadata": {},
"outputs": [],
"source": [
@ -577,8 +609,7 @@
" self.decimals: Decimal = decimals\n",
"\n",
" def round(self, value: Decimal) -> Decimal:\n",
" rounded = round(value, int(self.decimals))\n",
" return Decimal(rounded)\n",
" return round(value, int(self.decimals))\n",
"\n",
" def name_matches(self, name: str) -> bool:\n",
" return self.name.upper() == name.upper()\n",
@ -620,7 +651,7 @@
},
{
"cell_type": "markdown",
"id": "checked-fashion",
"id": "standing-weather",
"metadata": {},
"source": [
"## SolToken object\n",
@ -631,16 +662,16 @@
{
"cell_type": "code",
"execution_count": null,
"id": "industrial-black",
"id": "delayed-small",
"metadata": {},
"outputs": [],
"source": [
"SolToken = Token(\"SOL\", SYSTEM_PROGRAM_ADDRESS, SOL_DECIMALS)"
"SolToken = Token(\"SOL\", SOL_MINT_ADDRESS, SOL_DECIMALS)"
]
},
{
"cell_type": "markdown",
"id": "public-southeast",
"id": "awful-horror",
"metadata": {},
"source": [
"## TokenLookup class\n",
@ -658,7 +689,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "equipped-provision",
"id": "endless-prediction",
"metadata": {},
"outputs": [],
"source": [
@ -684,7 +715,7 @@
},
{
"cell_type": "markdown",
"id": "extra-beast",
"id": "unable-number",
"metadata": {},
"source": [
"## BasketToken class\n",
@ -695,7 +726,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "confidential-district",
"id": "julian-journey",
"metadata": {},
"outputs": [],
"source": [
@ -739,7 +770,8 @@
" return False\n",
"\n",
" def __str__(self) -> str:\n",
" return f\"\"\"« BasketToken [{self.token}]:\n",
" return f\"\"\"« BasketToken:\n",
" {self.token}\n",
" Vault: {self.vault}\n",
" Index: {self.index}\n",
"»\"\"\"\n",
@ -750,7 +782,7 @@
},
{
"cell_type": "markdown",
"id": "innocent-breath",
"id": "authorized-monitor",
"metadata": {},
"source": [
"## TokenValue class\n",
@ -761,7 +793,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "forced-reasoning",
"id": "responsible-privilege",
"metadata": {},
"outputs": [],
"source": [
@ -846,7 +878,7 @@
},
{
"cell_type": "markdown",
"id": "german-sheep",
"id": "intellectual-wichita",
"metadata": {},
"source": [
"## OwnedTokenValue class\n",
@ -857,7 +889,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "saving-slovenia",
"id": "virtual-scene",
"metadata": {},
"outputs": [],
"source": [
@ -897,7 +929,7 @@
},
{
"cell_type": "markdown",
"id": "suspended-appeal",
"id": "devoted-oxford",
"metadata": {},
"source": [
"## MarketMetadata class"
@ -906,7 +938,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "broken-birthday",
"id": "proof-bahrain",
"metadata": {},
"outputs": [],
"source": [
@ -930,9 +962,11 @@
" return self._market\n",
"\n",
" def __str__(self) -> str:\n",
" base = f\"{self.base}\".replace(\"\\n\", \"\\n \")\n",
" quote = f\"{self.quote}\".replace(\"\\n\", \"\\n \")\n",
" return f\"\"\"« Market '{self.name}' [{self.spot}]:\n",
" Base: {self.base}\n",
" Quote: {self.quote}\n",
" Base: {base}\n",
" Quote: {quote}\n",
" Oracle: {self.oracle} ({self.decimals} decimals)\n",
"»\"\"\"\n",
"\n",
@ -942,16 +976,18 @@
},
{
"cell_type": "markdown",
"id": "static-subscription",
"id": "chubby-needle",
"metadata": {},
"source": [
"## Group class"
"## Group class\n",
"\n",
"The `Group` class encapsulates the data for the Mango Group - the cross-margined basket of tokens with lending."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "checked-stewart",
"id": "thick-istanbul",
"metadata": {},
"outputs": [],
"source": [
@ -984,24 +1020,26 @@
" def shared_quote_token(self) -> BasketToken:\n",
" return self.basket_tokens[-1]\n",
"\n",
" @property\n",
" def base_tokens(self) -> typing.List[BasketToken]:\n",
" return self.basket_tokens[:-1]\n",
"\n",
" @staticmethod\n",
" def from_layout(layout: layouts.GROUP, context: Context, account_info: AccountInfo) -> \"Group\":\n",
" account_flags = MangoAccountFlags.from_layout(layout.account_flags)\n",
" def from_layout(layout: construct.Struct, context: Context, account_info: AccountInfo, version: Version) -> \"Group\":\n",
" account_flags: MangoAccountFlags = MangoAccountFlags.from_layout(layout.account_flags)\n",
" indexes = list(map(lambda pair: Index.from_layout(pair[0], pair[1]), zip(layout.indexes, layout.mint_decimals)))\n",
"\n",
" basket_tokens: typing.List[BasketToken] = []\n",
" for index in range(NUM_TOKENS):\n",
" token_address = layout.tokens[index]\n",
" for index, token_address in enumerate(layout.tokens):\n",
" token_name = context.lookup_token_name(token_address)\n",
" if token_name is None:\n",
" raise Exception(f\"Could not find token with mint '{token_address}' in Group.\")\n",
" raise Exception(f\"Could not find token with mint '{token_address}' in Group ['{account_info.address}'].\")\n",
" token = Token(token_name, token_address, layout.mint_decimals[index])\n",
" basket_token = BasketToken(token, layout.vaults[index], indexes[index])\n",
" basket_tokens += [basket_token]\n",
"\n",
" markets: typing.List[MarketMetadata] = []\n",
" for index in range(NUM_MARKETS):\n",
" market_address = layout.spot_markets[index]\n",
" for index, market_address in enumerate(layout.spot_markets):\n",
" market_name = context.lookup_market_name(market_address)\n",
" base_name, quote_name = market_name.split(\"/\")\n",
" base_token = BasketToken.find_by_name(basket_tokens, base_name)\n",
@ -1014,7 +1052,8 @@
"\n",
" maint_coll_ratio = layout.maint_coll_ratio.quantize(Decimal('.01'))\n",
" init_coll_ratio = layout.init_coll_ratio.quantize(Decimal('.01'))\n",
" return Group(account_info, Version.UNSPECIFIED, context, account_flags, basket_tokens, markets,\n",
"\n",
" return Group(account_info, version, context, account_flags, basket_tokens, markets,\n",
" layout.signer_nonce, layout.signer_key, layout.dex_program_id, layout.total_deposits,\n",
" layout.total_borrows, maint_coll_ratio, init_coll_ratio, layout.srm_vault,\n",
" layout.admin, layout.borrow_limits)\n",
@ -1022,11 +1061,16 @@
" @staticmethod\n",
" def parse(context: Context, account_info: AccountInfo) -> \"Group\":\n",
" data = account_info.data\n",
" if len(data) != layouts.GROUP.sizeof():\n",
" raise Exception(f\"Data length ({len(data)}) does not match expected size ({layouts.GROUP.sizeof()})\")\n",
" if len(data) == layouts.GROUP_V1.sizeof():\n",
" layout = layouts.GROUP_V1.parse(data)\n",
" version: Version = Version.V1\n",
" elif len(data) == layouts.GROUP_V2.sizeof():\n",
" version = Version.V2\n",
" layout = layouts.GROUP_V2.parse(data)\n",
" else:\n",
" raise Exception(f\"Group data length ({len(data)}) does not match expected size ({layouts.GROUP_V1.sizeof()} or {layouts.GROUP_V2.sizeof()})\")\n",
"\n",
" layout = layouts.GROUP.parse(data)\n",
" return Group.from_layout(layout, context, account_info)\n",
" return Group.from_layout(layout, context, account_info, version)\n",
"\n",
" @staticmethod\n",
" def load(context: Context):\n",
@ -1068,21 +1112,99 @@
" balances += [TokenValue(SolToken, sol_balance)]\n",
"\n",
" for basket_token in self.basket_tokens:\n",
" balance = TokenValue.fetch_total_value(self.context, root_address, basket_token.token)\n",
" balances += [balance]\n",
" if basket_token.token != SolToken:\n",
" balance = TokenValue.fetch_total_value(self.context, root_address, basket_token.token)\n",
" balances += [balance]\n",
" return balances\n",
"\n",
" # The old way of fetching ripe margin accounts was to fetch them all then inspect them to see\n",
" # if they were ripe. That was a big performance problem - fetching all groups was quite a penalty.\n",
" #\n",
" # This is still how it's done in load_ripe_margin_accounts_v1().\n",
" #\n",
" # The newer mechanism is to look for the has_borrows flag in the MangoAccount. That should\n",
" # mean fewer MarginAccounts need to be fetched.\n",
" #\n",
" # This newer method is implemented in load_ripe_margin_accounts_v2()\n",
" def load_ripe_margin_accounts(self) -> typing.List[\"MarginAccount\"]:\n",
" if self.version == Version.V1:\n",
" return self.load_ripe_margin_accounts_v1()\n",
" else:\n",
" return self.load_ripe_margin_accounts_v2()\n",
"\n",
" def load_ripe_margin_accounts_v2(self) -> typing.List[\"MarginAccount\"]:\n",
" started_at = time.time()\n",
"\n",
" filters = [\n",
" # 'has_borrows' offset is: 8 + 32 + 32 + (5 * 16) + (5 * 16) + (4 * 32) + 1\n",
" # = 361\n",
" MemcmpOpts(\n",
" offset=361,\n",
" bytes=encode_int(1)\n",
" ),\n",
" MemcmpOpts(\n",
" offset=layouts.MANGO_ACCOUNT_FLAGS.sizeof(), # mango_group is just after the MangoAccountFlags, which is the first entry\n",
" bytes=encode_key(self.address)\n",
" )\n",
" ]\n",
"\n",
" response = self.context.client.get_program_accounts(self.context.program_id, data_size=layouts.MARGIN_ACCOUNT_V2.sizeof(), memcmp_opts=filters, commitment=Single, encoding=\"base64\")\n",
" result = self.context.unwrap_or_raise_exception(response)\n",
" margin_accounts = []\n",
" open_orders_addresses = []\n",
" for margin_account_data in result:\n",
" address = PublicKey(margin_account_data[\"pubkey\"])\n",
" account = AccountInfo._from_response_values(margin_account_data[\"account\"], address)\n",
" margin_account = MarginAccount.parse(account)\n",
" open_orders_addresses += margin_account.open_orders\n",
" margin_accounts += [margin_account]\n",
"\n",
" self.logger.info(f\"Fetched {len(margin_accounts)} V2 margin accounts to process.\")\n",
"\n",
" open_orders_addresses = [oo for oo in open_orders_addresses if oo is not None]\n",
"\n",
" open_orders_account_infos = AccountInfo.load_multiple(self.context, open_orders_addresses)\n",
" open_orders_account_infos_by_address = {key: value for key, value in [(str(account_info.address), account_info) for account_info in open_orders_account_infos]}\n",
"\n",
" for margin_account in margin_accounts:\n",
" margin_account.install_open_orders_accounts(self, open_orders_account_infos_by_address)\n",
"\n",
" prices = self.fetch_token_prices()\n",
" ripe_accounts = MarginAccount.filter_out_unripe(margin_accounts, self, prices)\n",
"\n",
" time_taken = time.time() - started_at\n",
" self.logger.info(f\"Loading ripe 🥭 accounts complete. Time taken: {time_taken:.2f} seconds.\")\n",
" return ripe_accounts\n",
"\n",
" def load_ripe_margin_accounts_v1(self) -> typing.List[\"MarginAccount\"]:\n",
" started_at = time.time()\n",
"\n",
" margin_accounts = MarginAccount.load_all_for_group_with_open_orders(self.context, self.context.program_id, self)\n",
" self.logger.info(f\"Fetched {len(margin_accounts)} V1 margin accounts to process.\")\n",
"\n",
" prices = self.fetch_token_prices()\n",
" ripe_accounts = MarginAccount.filter_out_unripe(margin_accounts, self, prices)\n",
"\n",
" time_taken = time.time() - started_at\n",
" self.logger.info(f\"Loading ripe 🥭 accounts complete. Time taken: {time_taken:.2f} seconds.\")\n",
" return ripe_accounts\n",
"\n",
" def __str__(self) -> str:\n",
" total_deposits = \"\\n \".join(map(str, self.total_deposits))\n",
" total_borrows = \"\\n \".join(map(str, self.total_borrows))\n",
" borrow_limits = \"\\n \".join(map(str, self.borrow_limits))\n",
" shared_quote_token = str(self.shared_quote_token).replace(\"\\n\", \"\\n \")\n",
" base_tokens = \"\\n \".join([f\"{tok}\".replace(\"\\n\", \"\\n \") for tok in self.base_tokens])\n",
" markets = \"\\n \".join([f\"{mkt}\".replace(\"\\n\", \"\\n \") for mkt in self.markets])\n",
" return f\"\"\"\n",
"« Group [{self.version}] {self.address}:\n",
" Flags: {self.account_flags}\n",
" Tokens:\n",
"{self.basket_tokens}\n",
" Base Tokens:\n",
" {base_tokens}\n",
" Quote Token:\n",
" {shared_quote_token}\n",
" Markets:\n",
"{self.markets}\n",
" {markets}\n",
" DEX Program ID: « {self.dex_program_id} »\n",
" SRM Vault: « {self.srm_vault} »\n",
" Admin: « {self.admin} »\n",
@ -1102,7 +1224,7 @@
},
{
"cell_type": "markdown",
"id": "promising-xerox",
"id": "institutional-imperial",
"metadata": {},
"source": [
"## TokenAccount class"
@ -1111,7 +1233,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "potential-attempt",
"id": "approximate-tuition",
"metadata": {},
"outputs": [],
"source": [
@ -1195,7 +1317,7 @@
},
{
"cell_type": "markdown",
"id": "fancy-commonwealth",
"id": "lucky-vienna",
"metadata": {},
"source": [
"## OpenOrders class\n"
@ -1204,7 +1326,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "alive-spelling",
"id": "successful-wilderness",
"metadata": {},
"outputs": [],
"source": [
@ -1324,7 +1446,7 @@
},
{
"cell_type": "markdown",
"id": "critical-worcester",
"id": "cognitive-cargo",
"metadata": {},
"source": [
"## BalanceSheet class"
@ -1333,7 +1455,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "unauthorized-attendance",
"id": "married-charlotte",
"metadata": {},
"outputs": [],
"source": [
@ -1380,7 +1502,7 @@
},
{
"cell_type": "markdown",
"id": "intensive-student",
"id": "decimal-secretary",
"metadata": {},
"source": [
"## MarginAccount class\n"
@ -1389,26 +1511,35 @@
{
"cell_type": "code",
"execution_count": null,
"id": "fitted-postcard",
"id": "mexican-immigration",
"metadata": {},
"outputs": [],
"source": [
"class MarginAccount(AddressableAccount):\n",
" def __init__(self, account_info: AccountInfo, version: Version, account_flags: MangoAccountFlags,\n",
" mango_group: PublicKey, owner: PublicKey, deposits: typing.List[Decimal],\n",
" borrows: typing.List[Decimal], open_orders: typing.List[PublicKey]):\n",
" has_borrows: bool, mango_group: PublicKey, owner: PublicKey,\n",
" deposits: typing.List[Decimal], borrows: typing.List[Decimal],\n",
" open_orders: typing.List[PublicKey]):\n",
" super().__init__(account_info)\n",
" self.version: Version = version\n",
" self.account_flags: MangoAccountFlags = account_flags\n",
" self.has_borrows: bool = has_borrows\n",
" self.mango_group: PublicKey = mango_group\n",
" self.owner: PublicKey = owner\n",
" self.deposits: typing.List[Decimal] = deposits\n",
" self.borrows: typing.List[Decimal] = borrows\n",
" self.open_orders: typing.List[PublicKey] = open_orders\n",
" self.open_orders_accounts: typing.List[typing.Optional[OpenOrders]] = [None] * NUM_MARKETS\n",
" self.open_orders_accounts: typing.List[typing.Optional[OpenOrders]] = [None] * len(open_orders)\n",
"\n",
" @staticmethod\n",
" def from_layout(layout: layouts.MARGIN_ACCOUNT, account_info: AccountInfo) -> \"MarginAccount\":\n",
" def from_layout(layout: construct.Struct, account_info: AccountInfo, version: Version) -> \"MarginAccount\":\n",
" if version == Version.V1:\n",
" # This is an old-style margin account, with no borrows flag\n",
" has_borrows = False\n",
" else:\n",
" # This is a new-style margin account where we can depend on the presence of the borrows flag\n",
" has_borrows = bool(layout.has_borrows)\n",
"\n",
" account_flags: MangoAccountFlags = MangoAccountFlags.from_layout(layout.account_flags)\n",
" deposits: typing.List[Decimal] = []\n",
" for index, deposit in enumerate(layout.deposits):\n",
@ -1418,23 +1549,29 @@
" for index, borrow in enumerate(layout.borrows):\n",
" borrows += [borrow]\n",
"\n",
" return MarginAccount(account_info, Version.UNSPECIFIED, account_flags, layout.mango_group,\n",
" return MarginAccount(account_info, version, account_flags, has_borrows, layout.mango_group,\n",
" layout.owner, deposits, borrows, list(layout.open_orders))\n",
"\n",
" @staticmethod\n",
" def parse(account_info: AccountInfo) -> \"MarginAccount\":\n",
" data = account_info.data\n",
" if len(data) != layouts.MARGIN_ACCOUNT.sizeof():\n",
" raise Exception(f\"Data length ({len(data)}) does not match expected size ({layouts.MARGIN_ACCOUNT.sizeof()})\")\n",
" if len(data) == layouts.MARGIN_ACCOUNT_V1.sizeof():\n",
" layout = layouts.MARGIN_ACCOUNT_V1.parse(data)\n",
" version: Version = Version.V1\n",
" elif len(data) == layouts.MARGIN_ACCOUNT_V2.sizeof():\n",
" version = Version.V2\n",
" layout = layouts.MARGIN_ACCOUNT_V2.parse(data)\n",
" else:\n",
" raise Exception(f\"Data length ({len(data)}) does not match expected size ({layouts.MARGIN_ACCOUNT_V1.sizeof()} or {layouts.MARGIN_ACCOUNT_V2.sizeof()})\")\n",
"\n",
" layout = layouts.MARGIN_ACCOUNT.parse(data)\n",
" return MarginAccount.from_layout(layout, account_info)\n",
" return MarginAccount.from_layout(layout, account_info, version)\n",
"\n",
" @staticmethod\n",
" def load(context: Context, margin_account_address: PublicKey, group: typing.Optional[Group] = None) -> \"MarginAccount\":\n",
" account_info = AccountInfo.load(context, margin_account_address)\n",
" if account_info is None:\n",
" raise Exception(f\"MarginAccount account not found at address '{margin_account_address}'\")\n",
"\n",
" margin_account = MarginAccount.parse(account_info)\n",
" if group is None:\n",
" group = Group.load(context)\n",
@ -1449,7 +1586,13 @@
" bytes=encode_key(group.address)\n",
" )\n",
" ]\n",
" response = context.client.get_program_accounts(program_id, data_size=layouts.MARGIN_ACCOUNT.sizeof(), memcmp_opts=filters, commitment=Single, encoding=\"base64\")\n",
"\n",
" if group.version == Version.V1:\n",
" parser = layouts.MARGIN_ACCOUNT_V1\n",
" else:\n",
" parser = layouts.MARGIN_ACCOUNT_V2\n",
"\n",
" response = context.client.get_program_accounts(program_id, data_size=parser.sizeof(), memcmp_opts=filters, commitment=Single, encoding=\"base64\")\n",
" margin_accounts = []\n",
" for margin_account_data in response[\"result\"]:\n",
" address = PublicKey(margin_account_data[\"pubkey\"])\n",
@ -1458,10 +1601,13 @@
" margin_accounts += [margin_account]\n",
" return margin_accounts\n",
"\n",
" @staticmethod\n",
" def load_all_for_group_with_open_orders(context: Context, program_id: PublicKey, group: Group) -> typing.List[\"MarginAccount\"]:\n",
" @classmethod\n",
" def load_all_for_group_with_open_orders(cls, context: Context, program_id: PublicKey, group: Group) -> typing.List[\"MarginAccount\"]:\n",
" logger: logging.Logger = logging.getLogger(cls.__name__)\n",
" margin_accounts = MarginAccount.load_all_for_group(context, context.program_id, group)\n",
" logger.info(f\"Fetched {len(margin_accounts)} margin accounts.\")\n",
" open_orders = OpenOrders.load_raw_open_orders_account_infos(context, group)\n",
" logger.info(f\"Fetched {len(open_orders)} openorders accounts.\")\n",
" for margin_account in margin_accounts:\n",
" margin_account.install_open_orders_accounts(group, open_orders)\n",
"\n",
@ -1485,7 +1631,7 @@
" )\n",
" ]\n",
"\n",
" response = context.client.get_program_accounts(context.program_id, data_size=layouts.MARGIN_ACCOUNT.sizeof(), memcmp_opts=filters, commitment=Single, encoding=\"base64\")\n",
" response = context.client.get_program_accounts(context.program_id, memcmp_opts=filters, commitment=Single, encoding=\"base64\")\n",
" margin_accounts = []\n",
" for margin_account_data in response[\"result\"]:\n",
" address = PublicKey(margin_account_data[\"pubkey\"])\n",
@ -1496,16 +1642,9 @@
" return margin_accounts\n",
"\n",
" @classmethod\n",
" def load_all_ripe(cls, context: Context) -> typing.List[\"MarginAccount\"]:\n",
" def filter_out_unripe(cls, margin_accounts: typing.List[\"MarginAccount\"], group: Group, prices: typing.List[TokenValue]) -> typing.List[\"MarginAccount\"]:\n",
" logger: logging.Logger = logging.getLogger(cls.__name__)\n",
"\n",
" started_at = time.time()\n",
"\n",
" group = Group.load(context)\n",
" margin_accounts = MarginAccount.load_all_for_group_with_open_orders(context, context.program_id, group)\n",
" logger.info(f\"Fetched {len(margin_accounts)} margin accounts to process.\")\n",
"\n",
" prices = group.fetch_token_prices()\n",
" nonzero: typing.List[MarginAccountMetadata] = []\n",
" for margin_account in margin_accounts:\n",
" balance_sheet = margin_account.get_balance_sheet_totals(group, prices)\n",
@ -1517,15 +1656,12 @@
" ripe_metadata = filter(lambda mam: mam.balance_sheet.collateral_ratio <= group.init_coll_ratio, nonzero)\n",
" ripe_accounts = list(map(lambda mam: mam.margin_account, ripe_metadata))\n",
" logger.info(f\"Of those {len(nonzero)}, {len(ripe_accounts)} are ripe 🥭.\")\n",
"\n",
" time_taken = time.time() - started_at\n",
" logger.info(f\"Loading ripe 🥭 accounts complete. Time taken: {time_taken:.2f} seconds.\")\n",
" return ripe_accounts\n",
"\n",
" def load_open_orders_accounts(self, context: Context, group: Group) -> None:\n",
" for index, oo in enumerate(self.open_orders):\n",
" key = oo\n",
" if key != SYSTEM_PROGRAM_ADDRESS:\n",
" if key is not None:\n",
" self.open_orders_accounts[index] = OpenOrders.load(context, key, group.basket_tokens[index].token.decimals, group.shared_quote_token.token.decimals)\n",
"\n",
" def install_open_orders_accounts(self, group: Group, all_open_orders_by_address: typing.Dict[str, AccountInfo]) -> None:\n",
@ -1539,22 +1675,21 @@
" self.open_orders_accounts[index] = open_orders\n",
"\n",
" def get_intrinsic_balance_sheets(self, group: Group) -> typing.List[BalanceSheet]:\n",
" settled_assets: typing.List[Decimal] = [Decimal(0)] * NUM_TOKENS\n",
" liabilities: typing.List[Decimal] = [Decimal(0)] * NUM_TOKENS\n",
" for index in range(NUM_TOKENS):\n",
" settled_assets[index] = group.basket_tokens[index].index.deposit * self.deposits[index]\n",
" liabilities[index] = group.basket_tokens[index].index.borrow * self.borrows[index]\n",
" settled_assets: typing.List[Decimal] = [Decimal(0)] * len(group.basket_tokens)\n",
" liabilities: typing.List[Decimal] = [Decimal(0)] * len(group.basket_tokens)\n",
" for index, token in enumerate(group.basket_tokens):\n",
" settled_assets[index] = token.index.deposit * self.deposits[index]\n",
" liabilities[index] = token.index.borrow * self.borrows[index]\n",
"\n",
" unsettled_assets: typing.List[Decimal] = [Decimal(0)] * NUM_TOKENS\n",
" for index in range(NUM_MARKETS):\n",
" open_orders_account = self.open_orders_accounts[index]\n",
" unsettled_assets: typing.List[Decimal] = [Decimal(0)] * len(group.basket_tokens)\n",
" for index, open_orders_account in enumerate(self.open_orders_accounts):\n",
" if open_orders_account is not None:\n",
" unsettled_assets[index] += open_orders_account.base_token_total\n",
" unsettled_assets[NUM_TOKENS - 1] += open_orders_account.quote_token_total\n",
" unsettled_assets[-1] += open_orders_account.quote_token_total\n",
"\n",
" balance_sheets: typing.List[BalanceSheet] = []\n",
" for index in range(NUM_TOKENS):\n",
" balance_sheets += [BalanceSheet(group.basket_tokens[index].token, liabilities[index],\n",
" for index, token in enumerate(group.basket_tokens):\n",
" balance_sheets += [BalanceSheet(token.token, liabilities[index],\n",
" settled_assets[index], unsettled_assets[index])]\n",
"\n",
" return balance_sheets\n",
@ -1618,6 +1753,7 @@
" open_orders = open_orders_unindented.replace(\"\\n\", \"\\n \")\n",
" return f\"\"\"« MarginAccount: {self.address}\n",
" Flags: {self.account_flags}\n",
" Has Borrows: {self.has_borrows}\n",
" Owner: {self.owner}\n",
" Mango Group: {self.mango_group}\n",
" Deposits: [{deposits}]\n",
@ -1628,7 +1764,7 @@
},
{
"cell_type": "markdown",
"id": "peripheral-compensation",
"id": "inappropriate-intensity",
"metadata": {},
"source": [
"## MarginAccountMetadata class"
@ -1637,7 +1773,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "labeled-event",
"id": "planned-beast",
"metadata": {},
"outputs": [],
"source": [
@ -1663,7 +1799,7 @@
},
{
"cell_type": "markdown",
"id": "noted-imperial",
"id": "august-tiger",
"metadata": {},
"source": [
"# Events"
@ -1671,7 +1807,7 @@
},
{
"cell_type": "markdown",
"id": "worthy-insurance",
"id": "nearby-string",
"metadata": {},
"source": [
"## LiquidationEvent"
@ -1680,7 +1816,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "rapid-reader",
"id": "wrong-organic",
"metadata": {},
"outputs": [],
"source": [
@ -1716,7 +1852,7 @@
},
{
"cell_type": "markdown",
"id": "humanitarian-season",
"id": "offshore-browse",
"metadata": {},
"source": [
"# ✅ Testing"
@ -1725,7 +1861,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "lesser-stone",
"id": "amber-medication",
"metadata": {},
"outputs": [],
"source": [
@ -1734,9 +1870,26 @@
" try:\n",
" logging.getLogger().setLevel(logging.CRITICAL)\n",
"\n",
" from Constants import SYSTEM_PROGRAM_ADDRESS\n",
" from Context import default_context\n",
"\n",
" list_to_split = [\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\", \"i\", \"j\"]\n",
" split_3 = _split_list_into_chunks(list_to_split, 3)\n",
" assert(len(split_3) == 4)\n",
" assert(split_3[0] == [\"a\", \"b\", \"c\"])\n",
" assert(split_3[1] == [\"d\", \"e\", \"f\"])\n",
" assert(split_3[2] == [\"g\", \"h\", \"i\"])\n",
" assert(split_3[3] == [\"j\"])\n",
" split_2 = _split_list_into_chunks(list_to_split, 2)\n",
" assert(len(split_2) == 5)\n",
" assert(split_2[0] == [\"a\", \"b\"])\n",
" assert(split_2[1] == [\"c\", \"d\"])\n",
" assert(split_2[2] == [\"e\", \"f\"])\n",
" assert(split_2[3] == [\"g\", \"h\"])\n",
" assert(split_2[4] == [\"i\", \"j\"])\n",
" split_20 = _split_list_into_chunks(list_to_split, 20)\n",
" assert(len(split_20) == 1)\n",
" assert(split_20[0] == [\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\", \"i\", \"j\"])\n",
"\n",
" balances_before = [\n",
" TokenValue(TokenLookup.find_by_name(default_context, \"ETH\"), Decimal(1)),\n",
" TokenValue(TokenLookup.find_by_name(default_context, \"BTC\"), Decimal(\"0.1\")),\n",
@ -1772,7 +1925,7 @@
},
{
"cell_type": "markdown",
"id": "residential-paintball",
"id": "coral-college",
"metadata": {},
"source": [
"# 🏃 Running"
@ -1781,7 +1934,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "weird-anniversary",
"id": "renewable-eugene",
"metadata": {},
"outputs": [],
"source": [
@ -1789,18 +1942,28 @@
" logging.getLogger().setLevel(logging.INFO)\n",
"\n",
" import base64\n",
" from Constants import SYSTEM_PROGRAM_ADDRESS\n",
" from Context import default_context\n",
"\n",
" # Just use any public key here\n",
" fake_public_key = SYSTEM_PROGRAM_ADDRESS\n",
" encoded = \"AwAAAAAAAACCaOmpoURMK6XHelGTaFawcuQ/78/15LAemWI8jrt3SRKLy2R9i60eclDjuDS8+p/ZhvTUd9G7uQVOYCsR6+BhmqGCiO6EPYP2PQkf/VRTvw7JjXvIjPFJy06QR1Cq1WfTonHl0OjCkyEf60SD07+MFJu5pVWNFGGEO/8AiAYfduaKdnFTaZEHPcK5Eq72WWHeHg2yIbBF09kyeOhlCJwOoG8O5SgpPV8QOA64ZNV4aKroFfADg6kEy/wWCdp3fv0O4GJgAAAAAPH6Ud6jtjwAAQAAAAAAAADiDkkCi9UOAAEAAAAAAAAADuBiYAAAAACNS5bSy7soAAEAAAAAAAAACMvgO+2jCwABAAAAAAAAAA7gYmAAAAAAZFeDUBNVhwABAAAAAAAAABtRNytozC8AAQAAAAAAAABIBGiCcyaEZdNhrTyeqUY692vOzzPdHaxAxguht3JQGlkzjtd05dX9LENHkl2z1XvUbTNKZlweypNRetmH0lmQ9VYQAHqylxZVK65gEg85g27YuSyvOBZAjJyRmYU9KdCO1D+4ehdPu9dQB1yI1uh75wShdAaFn2o4qrMYwq3SQQEAAAAAAAAAAiH1PPJKAuh6oGiE35aGhUQhFi/bxgKOudpFv8HEHNCFDy1uAqR6+CTQmradxC1wyyjL+iSft+5XudJWwSdi7wvphsxb96x7Obj/AgAAAAAKlV4LL5ow6r9LMhIAAAAADvsOtqcVFmChDPzPnwAAAE33lx1h8hPFD04AAAAAAAA8YRV3Oa309B2wGwAAAAAA+yPBZRlZz7b605n+AQAAAACgmZmZmZkZAQAAAAAAAAAAMDMzMzMzMwEAAAAAAAAA25D1XcAtRzSuuyx3U+X7aE9vM1EJySU9KprgL0LMJ/vat9+SEEUZuga7O5tTUrcMDYWDg+LYaAWhSQiN2fYk7aCGAQAAAAAAgIQeAAAAAAAA8gUqAQAAAAYGBgICAAAA\"\n",
" decoded = base64.b64decode(encoded)\n",
" group_account_info = AccountInfo(fake_public_key, False, Decimal(0), fake_public_key, Decimal(0), decoded)\n",
"\n",
" group = Group.parse(default_context, group_account_info)\n",
" # Data for group BTC_ETH_USDT\n",
" group_3_public_key = PublicKey(\"7pVYhpKUHw88neQHxgExSH6cerMZ1Axx1ALQP9sxtvQV\")\n",
" owner_3_public_key = PublicKey(\"JD3bq9hGdy38PuWQ4h2YJpELmHVGPPfFSuFkpzAd9zfu\")\n",
" encoded_3 = \"AwAAAAAAAACCaOmpoURMK6XHelGTaFawcuQ/78/15LAemWI8jrt3SRKLy2R9i60eclDjuDS8+p/ZhvTUd9G7uQVOYCsR6+BhzgEOYK/tsicXvWMZL1QUWj+WWjO7gtLHAp6yzh4ggmR5TYxOe+df4LNiUJGSedvZ1K+r6GIzQEosNxNHhh2V7yAW8uStEyfEUTbEEkKgyDlUOVRWgGFbsiOC/Uzmn5ghfd2vMNvykHBB4JNMAUG0WhTyCizezFE3eOWvscJG7VWUUa5gAAAAAO1Ih8hkuwwAAQAAAAAAAADCuVoJcm8AAAEAAAAAAAAAlFGuYAAAAAAq17B5bBMDAAEAAAAAAAAAmLhxEVn+//8AAAAAAAAAAJRRrmAAAAAA+TEN0IhNFAIBAAAAAAAAAOiJg4cDQlkAAQAAAAAAAACjgEchygfXnwdEo4sw2++jvroovFb2BReD7MwO2ycvyWJ1ExP6tUyAXZHgwFt800+q+x6ZXzsCtRysH3ay+vKU9VYQAHqylxZVK65gEg85g27YuSyvOBZAjJyRmYU9KdCO1D+4ehdPu9dQB1yI1uh75wShdAaFn2o4qrMYwq3SQQIAAAAAAAAAT1DEK0hxpSv5VHH5kTlWeJePlGQpPYZ+uqcUELR4sLKFDy1uAqR6+CTQmradxC1wyyjL+iSft+5XudJWwSdi78PjPEJTOBGbyhEgBwAAAAD1dEYfuWMjjZ/rgK0AAAAAnKsRD43mpS3XCLztBAUAAKp2XItMhJ/N4sYBAAAAAABTEKVFyYfPc+mmjQAAAAAAWCug5qta1bqvFvY5uAAAAACgmZmZmZkZAQAAAAAAAAAAMDMzMzMzMwEAAAAAAAAAS153X9szDlbg9dv9VWFE+e6Hzhj8N5Of9zJLVkbx/U3at9+SEEUZuga7O5tTUrcMDYWDg+LYaAWhSQiN2fYk7UBCDwAAAAAAgI1bAAAAAAAAdDukCwAAAAYGBgICAAAA\"\n",
" decoded_3 = base64.b64decode(encoded_3)\n",
" group_3_account_info = AccountInfo(group_3_public_key, False, Decimal(6069120), owner_3_public_key, Decimal(185), decoded_3)\n",
" group_3 = Group.parse(default_context, group_3_account_info)\n",
" print(\"\\n\\nThis is hard-coded, not live information!\")\n",
" print(group)\n",
" print(\"3-token group\", group_3)\n",
"\n",
" # Data for group BTC_ETH_SOL_SRM_USDC\n",
" group_5_context = default_context.new_from_group_name(\"BTC_ETH_SOL_SRM_USDC\")\n",
" group_5_public_key = PublicKey(\"79rqTHePsNnGEX6Re8Xkgf4QEkBfsXncXzyRbyMhHPMU\")\n",
" owner_5_public_key = PublicKey(\"3ZhWpsT19EuBZhG2BvcTVHBtFW4vFacGgLYpXZxotg1P\")\n",
" encoded_5 = \"AwAAAAAAAAAI9f3BfgBs05rqiAwecBatsPMhUbtwy/lssY5IHfz1as3WedpQtXtJeKZndHMtJOx7NoURsW+VQN5PR7WCMJr+BpuIV/6rgYT7aH9jRhjANdrEOdwa6ztVmKDwAAAAAAF6mkC+0kyNlxFPeKUyBpYL11A4a/pUcOYunlw92EFaYe8xN4hyxY/VqgKDw3gpT/JSVB/N/r9zVgAMlmL27VP2E9HOlNA8BU4JrUJL9JQ30DUtHJTFIXaQN1pfDgz/KVX+5r0fIkHxn8CfTzJ6xolktpxI5iPcYED3iDjXX10LTbrYnfWNWX0g3ojdBQP55TgRaxKmM2rKPCR9ZVj9U6ugOKB1oHM+iXvpy89ra32TRE5LVPCRCxDKQW+HAixGJRL+zsGa6VtOWqJYWqNCV5ef3grP0G7ATNeVtXpsms96wQVUrmAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAFVK5gAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABVSuYAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAVUrmAAAAAAPzGbhwwAAAABAAAAAAAAAFtLagYAAAAAAQAAAAAAAAAFVK5gAAAAADi95MmtAAAAAQAAAAAAAADw2EQGAAAAAAEAAAAAAAAAl5zHWv/y5wWxpcFtmFSbBTWMi2Bd8NV+Sr1XVxD132WPgK2ybPoMrgyN4s5T+6J30kS9BrEGnwtaAp4GCD27TVgDGoaPituuWI3sfe9LKjbMNihp3zYxLPyNBlB3cZswVvPrJ9M2SuDyQR+Cyo4YHlucZh37xO6TuuhSDaaRq4hSNqBWdfB5O0wXcsXSAxviIZnpzZb8yVPAJhqxFNViEC+Y1L1+ULx6KHTSRIwn5wXTktPuZEsRtSpUJpqZD9V5y+8i6KVSbQNBUDzf0xeAFH85lOlowgaoBW/MjycjsrXlD0s7jbPTK2SvlK0fgerysq1O8y7bk5yp6001Zr/sLAEAAAAAAAAAAZbGvC2Uwmchkn8Xc2PeT4aBGReM7Kjuqc1DuN5ufbG1vZ7LZ502Gt8wb/WL0dyrOS4eySbPnOiFlVUBjsVtHwAAAAAAAAAA9Mq1OgAAAAAAAAAAAAAAAIDw+gIAAAAAAAAAAAAAAADwf4dkHAAAAAEAAAAAAAAAAMqaOwAAAAAUOu5om0z+/+/n31+gWwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZwqohV9gfgEAAAAAAAAAAOuuBPiBhIG6j5hOWgMAAAAAoJmZmZmZGQEAAAAAAAAAADAzMzMzMzMBAAAAAAAAAGtqUa92w4LGfIDkG7RkvDMOZX+ssXFAKsKq/DKs+TSjBpU3RtcibIXhdSaNSvboTLAKz91Es3OeaoUjHgSxfAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgYJBgkCAgICAAAAAAAAAA==\"\n",
" decoded_5 = base64.b64decode(encoded_5)\n",
" group_5_account_info = AccountInfo(group_5_public_key, False, Decimal(9020160), owner_5_public_key, Decimal(135), decoded_5)\n",
" group_5 = Group.parse(group_5_context, group_5_account_info)\n",
" print(\"\\n\\nThis is hard-coded, not live information!\")\n",
" print(\"5-token group\", group_5)\n",
"\n",
" print(TokenLookup.find_by_name(default_context, \"ETH\"))\n",
" print(TokenLookup.find_by_name(default_context, \"BTC\"))\n",

View File

@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "markdown",
"id": "increasing-fundamental",
"id": "fitting-ticket",
"metadata": {},
"source": [
"# ⚠ Warning\n",
@ -16,7 +16,7 @@
},
{
"cell_type": "markdown",
"id": "lined-maximum",
"id": "historical-grace",
"metadata": {},
"source": [
"# 🥭 Constants\n",
@ -27,7 +27,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "functioning-howard",
"id": "subtle-idaho",
"metadata": {
"jupyter": {
"source_hidden": true
@ -44,7 +44,7 @@
},
{
"cell_type": "markdown",
"id": "brazilian-living",
"id": "institutional-earthquake",
"metadata": {},
"source": [
"## SYSTEM_PROGRAM_ADDRESS\n",
@ -55,7 +55,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "moral-attitude",
"id": "everyday-donna",
"metadata": {},
"outputs": [],
"source": [
@ -64,7 +64,27 @@
},
{
"cell_type": "markdown",
"id": "spanish-wireless",
"id": "motivated-northwest",
"metadata": {},
"source": [
"## SOL_MINT_ADDRESS\n",
"\n",
"The mint address of the SOL token."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "speaking-homework",
"metadata": {},
"outputs": [],
"source": [
"SOL_MINT_ADDRESS = PublicKey(\"So11111111111111111111111111111111111111112\")"
]
},
{
"cell_type": "markdown",
"id": "dated-appeal",
"metadata": {},
"source": [
"## SOL_DECIMALS\n",
@ -75,7 +95,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "disturbed-broadcast",
"id": "ready-tension",
"metadata": {},
"outputs": [],
"source": [
@ -84,7 +104,7 @@
},
{
"cell_type": "markdown",
"id": "billion-maximum",
"id": "artistic-batch",
"metadata": {},
"source": [
"## SOL_DECIMAL_DIVISOR decimal\n",
@ -95,7 +115,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "velvet-induction",
"id": "powerful-warrior",
"metadata": {},
"outputs": [],
"source": [
@ -104,7 +124,7 @@
},
{
"cell_type": "markdown",
"id": "chinese-inquiry",
"id": "mathematical-gender",
"metadata": {},
"source": [
"## NUM_TOKENS\n",
@ -115,7 +135,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "convinced-history",
"id": "isolated-sandwich",
"metadata": {},
"outputs": [],
"source": [
@ -124,7 +144,7 @@
},
{
"cell_type": "markdown",
"id": "statistical-soccer",
"id": "marine-florist",
"metadata": {},
"source": [
"## NUM_MARKETS\n",
@ -135,7 +155,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "british-miracle",
"id": "defensive-jewel",
"metadata": {},
"outputs": [],
"source": [
@ -144,7 +164,7 @@
},
{
"cell_type": "markdown",
"id": "attended-canada",
"id": "optimum-wagner",
"metadata": {},
"source": [
"# WARNING_DISCLAIMER_TEXT\n",
@ -155,7 +175,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "prompt-wright",
"id": "arranged-hypothesis",
"metadata": {},
"outputs": [],
"source": [
@ -175,7 +195,7 @@
},
{
"cell_type": "markdown",
"id": "stupid-glasgow",
"id": "focused-electricity",
"metadata": {},
"source": [
"## MangoConstants\n",
@ -186,7 +206,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "renewable-logan",
"id": "otherwise-pencil",
"metadata": {},
"outputs": [],
"source": [
@ -196,7 +216,7 @@
},
{
"cell_type": "markdown",
"id": "handled-legend",
"id": "dependent-pursuit",
"metadata": {},
"source": [
"# 🏃 Running\n",
@ -207,7 +227,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "elect-southwest",
"id": "primary-graphic",
"metadata": {},
"outputs": [],
"source": [

View File

@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "markdown",
"id": "optical-beijing",
"id": "reasonable-label",
"metadata": {},
"source": [
"# ⚠ Warning\n",
@ -16,7 +16,7 @@
},
{
"cell_type": "markdown",
"id": "powered-elevation",
"id": "spatial-encyclopedia",
"metadata": {},
"source": [
"# 🥭 Context\n",
@ -26,7 +26,7 @@
},
{
"cell_type": "markdown",
"id": "above-password",
"id": "familiar-glasgow",
"metadata": {},
"source": [
"## Environment Variables\n",
@ -41,7 +41,7 @@
},
{
"cell_type": "markdown",
"id": "floppy-aurora",
"id": "spectacular-isolation",
"metadata": {},
"source": [
"## Provided Configured Objects\n",
@ -61,7 +61,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "refined-stuff",
"id": "introductory-score",
"metadata": {
"jupyter": {
"source_hidden": true
@ -86,7 +86,7 @@
},
{
"cell_type": "markdown",
"id": "split-concept",
"id": "celtic-evening",
"metadata": {},
"source": [
"## Context class"
@ -95,7 +95,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "increasing-rally",
"id": "intimate-devices",
"metadata": {},
"outputs": [],
"source": [
@ -169,16 +169,16 @@
" return \"« Unknown Group »\"\n",
"\n",
" def lookup_market_name(self, market_address: PublicKey) -> str:\n",
" return Context._lookup_name_by_address(market_address, MangoConstants[self.cluster][\"spot_markets\"]) or \"« Unknown Market »\"\n",
" return Context._lookup_name_by_address(market_address, MangoConstants[self.cluster][\"mango_groups\"][self.group_name][\"spot_market_symbols\"]) or \"« Unknown Market »\"\n",
"\n",
" def lookup_oracle_name(self, token_address: PublicKey) -> str:\n",
" return Context._lookup_name_by_address(token_address, MangoConstants[self.cluster][\"oracles\"]) or \"« Unknown Oracle »\"\n",
"\n",
" def lookup_token_name(self, token_address: PublicKey) -> typing.Optional[str]:\n",
" return Context._lookup_name_by_address(token_address, MangoConstants[self.cluster][\"symbols\"])\n",
" return Context._lookup_name_by_address(token_address, MangoConstants[self.cluster][\"mango_groups\"][self.group_name][\"symbols\"])\n",
"\n",
" def lookup_token_address(self, token_name: str) -> typing.Optional[PublicKey]:\n",
" return Context._lookup_address_by_name(token_name, MangoConstants[self.cluster][\"symbols\"])\n",
" return Context._lookup_address_by_name(token_name, MangoConstants[self.cluster][\"mango_groups\"][self.group_name][\"symbols\"])\n",
"\n",
" def wait_for_confirmation(self, transaction_id: str, max_wait_in_seconds: int = 60) -> typing.Optional[typing.Dict]:\n",
" self.logger.info(f\"Waiting up to {max_wait_in_seconds} seconds for {transaction_id}.\")\n",
@ -191,6 +191,28 @@
" self.logger.info(f\"Timed out after {wait} seconds waiting on transaction {transaction_id}.\")\n",
" return None\n",
"\n",
" def new_from_cluster(self, cluster: str) -> \"Context\":\n",
" cluster_url = MangoConstants[\"cluster_urls\"][cluster]\n",
" program_id = PublicKey(MangoConstants[cluster][\"mango_program_id\"])\n",
" dex_program_id = PublicKey(MangoConstants[cluster][\"dex_program_id\"])\n",
" group_id = PublicKey(MangoConstants[cluster][\"mango_groups\"][self.group_name][\"mango_group_pk\"])\n",
"\n",
" return Context(cluster, cluster_url, program_id, dex_program_id, self.group_name, group_id)\n",
"\n",
" def new_from_group_name(self, group_name: str) -> \"Context\":\n",
" group_id = PublicKey(MangoConstants[self.cluster][\"mango_groups\"][group_name][\"mango_group_pk\"])\n",
"\n",
" return Context(self.cluster, self.cluster_url, self.program_id, self.dex_program_id, group_name, group_id)\n",
"\n",
" @staticmethod\n",
" def from_cluster_and_group_name(cluster: str, group_name: str) -> \"Context\":\n",
" cluster_url = MangoConstants[\"cluster_urls\"][cluster]\n",
" program_id = PublicKey(MangoConstants[cluster][\"mango_program_id\"])\n",
" dex_program_id = PublicKey(MangoConstants[cluster][\"dex_program_id\"])\n",
" group_id = PublicKey(MangoConstants[cluster][\"mango_groups\"][group_name][\"mango_group_pk\"])\n",
"\n",
" return Context(cluster, cluster_url, program_id, dex_program_id, group_name, group_id)\n",
"\n",
" def __str__(self) -> str:\n",
" return f\"\"\"« Context:\n",
" Cluster: {self.cluster}\n",
@ -207,7 +229,7 @@
},
{
"cell_type": "markdown",
"id": "specified-handbook",
"id": "silver-picking",
"metadata": {},
"source": [
"## default_context object\n",
@ -218,7 +240,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "touched-dominican",
"id": "critical-carbon",
"metadata": {},
"outputs": [],
"source": [
@ -237,7 +259,7 @@
},
{
"cell_type": "markdown",
"id": "bulgarian-milan",
"id": "wired-termination",
"metadata": {},
"source": [
"## solana_context object\n",
@ -248,7 +270,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "affecting-absorption",
"id": "abstract-lighter",
"metadata": {},
"outputs": [],
"source": [
@ -260,7 +282,7 @@
},
{
"cell_type": "markdown",
"id": "colored-alcohol",
"id": "muslim-lodge",
"metadata": {},
"source": [
"## serum_context object\n",
@ -271,7 +293,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "honest-prison",
"id": "patient-berry",
"metadata": {},
"outputs": [],
"source": [
@ -283,7 +305,7 @@
},
{
"cell_type": "markdown",
"id": "standing-thumb",
"id": "sustainable-feature",
"metadata": {},
"source": [
"# 🏃 Running\n",
@ -294,7 +316,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "confused-stable",
"id": "saved-reality",
"metadata": {},
"outputs": [],
"source": [

View File

@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "markdown",
"id": "informative-plane",
"id": "dominant-algeria",
"metadata": {},
"source": [
"# ⚠ Warning\n",
@ -16,7 +16,7 @@
},
{
"cell_type": "markdown",
"id": "demonstrated-advice",
"id": "fresh-northeast",
"metadata": {},
"source": [
"# 🥭 Decoder\n",
@ -27,7 +27,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "posted-attendance",
"id": "further-translation",
"metadata": {
"jupyter": {
"source_hidden": true
@ -45,7 +45,7 @@
},
{
"cell_type": "markdown",
"id": "quarterly-reach",
"id": "individual-vulnerability",
"metadata": {},
"source": [
"## decode_binary() function\n",
@ -64,7 +64,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "second-iceland",
"id": "thermal-aircraft",
"metadata": {},
"outputs": [],
"source": [
@ -79,7 +79,7 @@
},
{
"cell_type": "markdown",
"id": "mediterranean-tuner",
"id": "humanitarian-difference",
"metadata": {},
"source": [
"## encode_binary() function\n",
@ -90,7 +90,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "bulgarian-meditation",
"id": "simple-clearance",
"metadata": {},
"outputs": [],
"source": [
@ -100,7 +100,7 @@
},
{
"cell_type": "markdown",
"id": "crucial-reward",
"id": "covered-amount",
"metadata": {},
"source": [
"## encode_key() function\n",
@ -111,7 +111,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "entertaining-prophet",
"id": "concrete-fossil",
"metadata": {},
"outputs": [],
"source": [
@ -121,7 +121,28 @@
},
{
"cell_type": "markdown",
"id": "mechanical-madonna",
"id": "level-kinase",
"metadata": {},
"source": [
"## encode_int() function\n",
"\n",
"Encodes an `int` in the proper way for RPC calls."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "pediatric-finger",
"metadata": {},
"outputs": [],
"source": [
"def encode_int(value: int) -> str:\n",
" return base58.b58encode_int(value).decode('ascii')\n"
]
},
{
"cell_type": "markdown",
"id": "intelligent-watson",
"metadata": {},
"source": [
"# 🏃 Running\n",
@ -132,7 +153,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "standing-isaac",
"id": "banned-profession",
"metadata": {},
"outputs": [],
"source": [

View File

@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "markdown",
"id": "ancient-diary",
"id": "lucky-arlington",
"metadata": {},
"source": [
"# ⚠ Warning\n",
@ -16,7 +16,7 @@
},
{
"cell_type": "markdown",
"id": "legitimate-rally",
"id": "wired-eclipse",
"metadata": {},
"source": [
"# 🥭 Layouts\n",
@ -35,7 +35,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "excess-spectrum",
"id": "bigger-origin",
"metadata": {
"jupyter": {
"source_hidden": true
@ -45,16 +45,16 @@
"source": [
"import construct\n",
"import datetime\n",
"import itertools\n",
"import typing\n",
"\n",
"from decimal import Decimal\n",
"from solana.publickey import PublicKey\n",
"\n",
"from Constants import NUM_MARKETS, NUM_TOKENS\n"
"from solana.publickey import PublicKey\n"
]
},
{
"cell_type": "markdown",
"id": "sorted-blackberry",
"id": "military-turkish",
"metadata": {},
"source": [
"# Adapters\n",
@ -64,7 +64,7 @@
},
{
"cell_type": "markdown",
"id": "possible-continuity",
"id": "baking-behavior",
"metadata": {},
"source": [
"## DecimalAdapter class\n",
@ -75,7 +75,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "competitive-tours",
"id": "supreme-center",
"metadata": {},
"outputs": [],
"source": [
@ -93,7 +93,7 @@
},
{
"cell_type": "markdown",
"id": "concrete-court",
"id": "interesting-chosen",
"metadata": {},
"source": [
"## FloatAdapter class\n",
@ -115,7 +115,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "other-spanking",
"id": "orange-academy",
"metadata": {},
"outputs": [],
"source": [
@ -142,7 +142,7 @@
},
{
"cell_type": "markdown",
"id": "breeding-chapter",
"id": "selective-scholar",
"metadata": {},
"source": [
"## PublicKeyAdapter\n",
@ -153,7 +153,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "correct-strain",
"id": "tracked-tunnel",
"metadata": {},
"outputs": [],
"source": [
@ -161,7 +161,9 @@
" def __init__(self):\n",
" construct.Adapter.__init__(self, construct.Bytes(32))\n",
"\n",
" def _decode(self, obj, context, path) -> PublicKey:\n",
" def _decode(self, obj, context, path) -> typing.Optional[PublicKey]:\n",
" if (obj is None) or (obj == bytes([0] * 32)):\n",
" return None\n",
" return PublicKey(obj)\n",
"\n",
" def _encode(self, obj, context, path) -> bytes:\n",
@ -170,7 +172,7 @@
},
{
"cell_type": "markdown",
"id": "specified-sleeve",
"id": "reliable-international",
"metadata": {},
"source": [
"## DatetimeAdapter\n",
@ -181,7 +183,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "governing-significance",
"id": "connected-potato",
"metadata": {},
"outputs": [],
"source": [
@ -198,7 +200,7 @@
},
{
"cell_type": "markdown",
"id": "rental-genealogy",
"id": "studied-findings",
"metadata": {},
"source": [
"# Layout Structs"
@ -206,7 +208,7 @@
},
{
"cell_type": "markdown",
"id": "incorrect-combination",
"id": "handed-uruguay",
"metadata": {},
"source": [
"## SERUM_ACCOUNT_FLAGS\n",
@ -233,7 +235,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "private-industry",
"id": "different-slope",
"metadata": {},
"outputs": [],
"source": [
@ -254,7 +256,7 @@
},
{
"cell_type": "markdown",
"id": "authentic-lafayette",
"id": "revised-clinic",
"metadata": {},
"source": [
"## MANGO_ACCOUNT_FLAGS\n",
@ -279,7 +281,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "different-spouse",
"id": "molecular-still",
"metadata": {},
"outputs": [],
"source": [
@ -296,7 +298,7 @@
},
{
"cell_type": "markdown",
"id": "balanced-diamond",
"id": "saved-might",
"metadata": {},
"source": [
"## INDEX\n",
@ -316,7 +318,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "settled-silver",
"id": "breathing-metabolism",
"metadata": {},
"outputs": [],
"source": [
@ -329,7 +331,7 @@
},
{
"cell_type": "markdown",
"id": "neither-commonwealth",
"id": "interested-advancement",
"metadata": {},
"source": [
"## AGGREGATOR_CONFIG\n",
@ -365,7 +367,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "chemical-better",
"id": "concrete-rainbow",
"metadata": {},
"outputs": [],
"source": [
@ -382,7 +384,7 @@
},
{
"cell_type": "markdown",
"id": "respective-latino",
"id": "fresh-expression",
"metadata": {},
"source": [
"## ROUND\n",
@ -401,7 +403,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "signal-german",
"id": "ultimate-multimedia",
"metadata": {},
"outputs": [],
"source": [
@ -414,7 +416,7 @@
},
{
"cell_type": "markdown",
"id": "broke-thirty",
"id": "dying-prescription",
"metadata": {},
"source": [
"## ANSWER\n",
@ -434,7 +436,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "known-guard",
"id": "surgical-indie",
"metadata": {},
"outputs": [],
"source": [
@ -448,7 +450,7 @@
},
{
"cell_type": "markdown",
"id": "breeding-crystal",
"id": "complete-reproduction",
"metadata": {},
"source": [
"## AGGREGATOR\n",
@ -475,7 +477,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "disabled-relation",
"id": "progressive-multiple",
"metadata": {},
"outputs": [],
"source": [
@ -492,10 +494,14 @@
},
{
"cell_type": "markdown",
"id": "expensive-university",
"id": "revolutionary-toilet",
"metadata": {},
"source": [
"## GROUP\n",
"## GROUP_V1\n",
"\n",
"Groups have a common quote currency, and it's always the last token in the tokens.\n",
"\n",
"That means the number of markets is number_of_tokens - 1.\n",
"\n",
"Here's the [Mango Rust structure](https://github.com/blockworks-foundation/mango/blob/master/program/src/state.rs):\n",
"```\n",
@ -531,45 +537,131 @@
" pub oracle_decimals: [u8; NUM_MARKETS],\n",
" pub padding: [u8; MANGO_GROUP_PADDING]\n",
"}\n",
"impl_loadable!(MangoGr\n",
"impl_loadable!(MangoGroup);\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "steady-graphic",
"id": "billion-headline",
"metadata": {},
"outputs": [],
"source": [
"GROUP_PADDING = 8 - (NUM_TOKENS + NUM_MARKETS) % 8\n",
"\n",
"GROUP = construct.Struct(\n",
"GROUP_V1_NUM_TOKENS = 3\n",
"GROUP_V1_NUM_MARKETS = GROUP_V1_NUM_TOKENS - 1\n",
"GROUP_V1_PADDING = 8 - (GROUP_V1_NUM_TOKENS + GROUP_V1_NUM_MARKETS) % 8\n",
"GROUP_V1 = construct.Struct(\n",
" \"account_flags\" / MANGO_ACCOUNT_FLAGS,\n",
" \"tokens\" / construct.Array(NUM_TOKENS, PublicKeyAdapter()),\n",
" \"vaults\" / construct.Array(NUM_TOKENS, PublicKeyAdapter()),\n",
" \"indexes\" / construct.Array(NUM_TOKENS, INDEX),\n",
" \"spot_markets\" / construct.Array(NUM_MARKETS, PublicKeyAdapter()),\n",
" \"oracles\" / construct.Array(NUM_MARKETS, PublicKeyAdapter()),\n",
" \"tokens\" / construct.Array(GROUP_V1_NUM_TOKENS, PublicKeyAdapter()),\n",
" \"vaults\" / construct.Array(GROUP_V1_NUM_TOKENS, PublicKeyAdapter()),\n",
" \"indexes\" / construct.Array(GROUP_V1_NUM_TOKENS, INDEX),\n",
" \"spot_markets\" / construct.Array(GROUP_V1_NUM_MARKETS, PublicKeyAdapter()),\n",
" \"oracles\" / construct.Array(GROUP_V1_NUM_MARKETS, PublicKeyAdapter()),\n",
" \"signer_nonce\" / DecimalAdapter(),\n",
" \"signer_key\" / PublicKeyAdapter(),\n",
" \"dex_program_id\" / PublicKeyAdapter(),\n",
" \"total_deposits\" / construct.Array(NUM_TOKENS, FloatAdapter()),\n",
" \"total_borrows\" / construct.Array(NUM_TOKENS, FloatAdapter()),\n",
" \"total_deposits\" / construct.Array(GROUP_V1_NUM_TOKENS, FloatAdapter()),\n",
" \"total_borrows\" / construct.Array(GROUP_V1_NUM_TOKENS, FloatAdapter()),\n",
" \"maint_coll_ratio\" / FloatAdapter(),\n",
" \"init_coll_ratio\" / FloatAdapter(),\n",
" \"srm_vault\" / PublicKeyAdapter(),\n",
" \"admin\" / PublicKeyAdapter(),\n",
" \"borrow_limits\" / construct.Array(NUM_TOKENS, DecimalAdapter()),\n",
" \"mint_decimals\" / construct.Array(NUM_TOKENS, DecimalAdapter(1)),\n",
" \"oracle_decimals\" / construct.Array(NUM_MARKETS, DecimalAdapter(1)),\n",
" \"padding\" / construct.Array(GROUP_PADDING, construct.Padding(1))\n",
")\n"
" \"borrow_limits\" / construct.Array(GROUP_V1_NUM_TOKENS, DecimalAdapter()),\n",
" \"mint_decimals\" / construct.Array(GROUP_V1_NUM_TOKENS, DecimalAdapter(1)),\n",
" \"oracle_decimals\" / construct.Array(GROUP_V1_NUM_MARKETS, DecimalAdapter(1)),\n",
" \"padding\" / construct.Array(GROUP_V1_PADDING, construct.Padding(1))\n",
")"
]
},
{
"cell_type": "markdown",
"id": "early-grounds",
"id": "different-phrase",
"metadata": {},
"source": [
"## GROUP_V2\n",
"\n",
"Groups have a common quote currency, and it's always the last token in the tokens.\n",
"\n",
"That means the number of markets is number_of_tokens - 1.\n",
"\n",
"There is no difference between the V1 and V2 structures except for the number of tokens. We handle things this way to be consistent with how we handle V1 and V2 `MarginAccount`s.\n",
"\n",
"Here's the [Mango Rust structure](https://github.com/blockworks-foundation/mango/blob/master/program/src/state.rs):\n",
"```\n",
"#[derive(Copy, Clone)]\n",
"#[repr(C)]\n",
"pub struct MangoGroup {\n",
" pub account_flags: u64,\n",
" pub tokens: [Pubkey; NUM_TOKENS], // Last token is shared quote currency\n",
" pub vaults: [Pubkey; NUM_TOKENS], // where funds are stored\n",
" pub indexes: [MangoIndex; NUM_TOKENS], // to keep track of interest\n",
" pub spot_markets: [Pubkey; NUM_MARKETS], // pubkeys to MarketState of serum dex\n",
" pub oracles: [Pubkey; NUM_MARKETS], // oracles that give price of each base currency in quote currency\n",
" pub signer_nonce: u64,\n",
" pub signer_key: Pubkey,\n",
" pub dex_program_id: Pubkey, // serum dex program id\n",
"\n",
" // denominated in Mango index adjusted terms\n",
" pub total_deposits: [U64F64; NUM_TOKENS],\n",
" pub total_borrows: [U64F64; NUM_TOKENS],\n",
"\n",
" pub maint_coll_ratio: U64F64, // 1.10\n",
" pub init_coll_ratio: U64F64, // 1.20\n",
"\n",
" pub srm_vault: Pubkey, // holds users SRM for fee reduction\n",
"\n",
" /// This admin key is only for alpha release and the only power it has is to amend borrow limits\n",
" /// If users borrow too much too quickly before liquidators are able to handle the volume,\n",
" /// lender funds will be at risk. Hence these borrow limits will be raised slowly\n",
" /// UPDATE: 4/15/2021 - this admin key is now useless, borrow limits are removed\n",
" pub admin: Pubkey,\n",
" pub borrow_limits: [u64; NUM_TOKENS],\n",
"\n",
" pub mint_decimals: [u8; NUM_TOKENS],\n",
" pub oracle_decimals: [u8; NUM_MARKETS],\n",
" pub padding: [u8; MANGO_GROUP_PADDING]\n",
"}\n",
"\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "requested-norfolk",
"metadata": {},
"outputs": [],
"source": [
"GROUP_V2_NUM_TOKENS = 5\n",
"GROUP_V2_NUM_MARKETS = GROUP_V2_NUM_TOKENS - 1\n",
"GROUP_V2_PADDING = 8 - (GROUP_V2_NUM_TOKENS + GROUP_V2_NUM_MARKETS) % 8\n",
"GROUP_V2 = construct.Struct(\n",
" \"account_flags\" / MANGO_ACCOUNT_FLAGS,\n",
" \"tokens\" / construct.Array(GROUP_V2_NUM_TOKENS, PublicKeyAdapter()),\n",
" \"vaults\" / construct.Array(GROUP_V2_NUM_TOKENS, PublicKeyAdapter()),\n",
" \"indexes\" / construct.Array(GROUP_V2_NUM_TOKENS, INDEX),\n",
" \"spot_markets\" / construct.Array(GROUP_V2_NUM_MARKETS, PublicKeyAdapter()),\n",
" \"oracles\" / construct.Array(GROUP_V2_NUM_MARKETS, PublicKeyAdapter()),\n",
" \"signer_nonce\" / DecimalAdapter(),\n",
" \"signer_key\" / PublicKeyAdapter(),\n",
" \"dex_program_id\" / PublicKeyAdapter(),\n",
" \"total_deposits\" / construct.Array(GROUP_V2_NUM_TOKENS, FloatAdapter()),\n",
" \"total_borrows\" / construct.Array(GROUP_V2_NUM_TOKENS, FloatAdapter()),\n",
" \"maint_coll_ratio\" / FloatAdapter(),\n",
" \"init_coll_ratio\" / FloatAdapter(),\n",
" \"srm_vault\" / PublicKeyAdapter(),\n",
" \"admin\" / PublicKeyAdapter(),\n",
" \"borrow_limits\" / construct.Array(GROUP_V2_NUM_TOKENS, DecimalAdapter()),\n",
" \"mint_decimals\" / construct.Array(GROUP_V2_NUM_TOKENS, DecimalAdapter(1)),\n",
" \"oracle_decimals\" / construct.Array(GROUP_V2_NUM_MARKETS, DecimalAdapter(1)),\n",
" \"padding\" / construct.Array(GROUP_V2_PADDING, construct.Padding(1))\n",
")"
]
},
{
"cell_type": "markdown",
"id": "curious-atmosphere",
"metadata": {},
"source": [
"## TOKEN_ACCOUNT"
@ -578,7 +670,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "ranging-extension",
"id": "solar-victorian",
"metadata": {},
"outputs": [],
"source": [
@ -592,7 +684,7 @@
},
{
"cell_type": "markdown",
"id": "above-summer",
"id": "meaning-moses",
"metadata": {},
"source": [
"## OPEN_ORDERS\n",
@ -603,7 +695,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "identified-underground",
"id": "structured-jumping",
"metadata": {},
"outputs": [],
"source": [
@ -627,12 +719,12 @@
},
{
"cell_type": "markdown",
"id": "silent-neutral",
"id": "intense-packaging",
"metadata": {},
"source": [
"## MARGIN_ACCOUNT\n",
"## MARGIN_ACCOUNT_V1\n",
"\n",
"Here's the [Mango Rust structure](https://github.com/blockworks-foundation/mango/blob/master/program/src/state.rs):\n",
"Here's the V1 [Mango Rust structure](https://github.com/blockworks-foundation/mango/blob/master/program/src/state.rs):\n",
"```\n",
"#[derive(Copy, Clone)]\n",
"#[repr(C)]\n",
@ -659,24 +751,142 @@
{
"cell_type": "code",
"execution_count": null,
"id": "pending-sandwich",
"id": "refined-asset",
"metadata": {},
"outputs": [],
"source": [
"MARGIN_ACCOUNT = construct.Struct(\n",
"MARGIN_ACCOUNT_V1_NUM_TOKENS = 3\n",
"MARGIN_ACCOUNT_V1_NUM_MARKETS = MARGIN_ACCOUNT_V1_NUM_TOKENS - 1\n",
"MARGIN_ACCOUNT_V1 = construct.Struct(\n",
" \"account_flags\" / MANGO_ACCOUNT_FLAGS,\n",
" \"mango_group\" / PublicKeyAdapter(),\n",
" \"owner\" / PublicKeyAdapter(),\n",
" \"deposits\" / construct.Array(NUM_TOKENS, FloatAdapter()),\n",
" \"borrows\" / construct.Array(NUM_TOKENS, FloatAdapter()),\n",
" \"open_orders\" / construct.Array(NUM_MARKETS, PublicKeyAdapter()),\n",
" \"padding\" / construct.Padding(8)\n",
")\n"
" \"deposits\" / construct.Array(MARGIN_ACCOUNT_V1_NUM_TOKENS, FloatAdapter()),\n",
" \"borrows\" / construct.Array(MARGIN_ACCOUNT_V1_NUM_TOKENS, FloatAdapter()),\n",
" \"open_orders\" / construct.Array(MARGIN_ACCOUNT_V1_NUM_MARKETS, PublicKeyAdapter()),\n",
" \"being_liquidated\" / DecimalAdapter(1),\n",
" \"padding\" / construct.Padding(7)\n",
")"
]
},
{
"cell_type": "markdown",
"id": "weird-somalia",
"id": "brave-fluid",
"metadata": {},
"source": [
"## MARGIN_ACCOUNT_V2\n",
"\n",
"Here's the V2 [Mango Rust structure](https://github.com/blockworks-foundation/mango/blob/master/program/src/state.rs):\n",
"```\n",
"#[derive(Copy, Clone)]\n",
"#[repr(C)]\n",
"pub struct MarginAccount {\n",
" pub account_flags: u64,\n",
" pub mango_group: Pubkey,\n",
" pub owner: Pubkey, // solana pubkey of owner\n",
"\n",
" // assets and borrows are denominated in Mango adjusted terms\n",
" pub deposits: [U64F64; NUM_TOKENS], // assets being lent out and gaining interest, including collateral\n",
"\n",
" // this will be incremented every time an order is opened and decremented when order is closed\n",
" pub borrows: [U64F64; NUM_TOKENS], // multiply by current index to get actual value\n",
"\n",
" pub open_orders: [Pubkey; NUM_MARKETS], // owned by Mango\n",
"\n",
" pub being_liquidated: bool,\n",
" pub has_borrows: bool, // does the account have any open borrows? set by checked_add_borrow and checked_sub_borrow\n",
" pub padding: [u8; 64] // padding\n",
"}\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "forbidden-italy",
"metadata": {},
"outputs": [],
"source": [
"MARGIN_ACCOUNT_V2_NUM_TOKENS = 5\n",
"MARGIN_ACCOUNT_V2_NUM_MARKETS = MARGIN_ACCOUNT_V2_NUM_TOKENS - 1\n",
"MARGIN_ACCOUNT_V2 = construct.Struct(\n",
" \"account_flags\" / MANGO_ACCOUNT_FLAGS,\n",
" \"mango_group\" / PublicKeyAdapter(),\n",
" \"owner\" / PublicKeyAdapter(),\n",
" \"deposits\" / construct.Array(MARGIN_ACCOUNT_V2_NUM_TOKENS, FloatAdapter()),\n",
" \"borrows\" / construct.Array(MARGIN_ACCOUNT_V2_NUM_TOKENS, FloatAdapter()),\n",
" \"open_orders\" / construct.Array(MARGIN_ACCOUNT_V2_NUM_MARKETS, PublicKeyAdapter()),\n",
" \"being_liquidated\" / DecimalAdapter(1),\n",
" \"has_borrows\" / DecimalAdapter(1),\n",
" \"padding\" / construct.Padding(70)\n",
")"
]
},
{
"cell_type": "markdown",
"id": "buried-mills",
"metadata": {},
"source": [
"## build_margin_account_parser_for_num_tokens() function\n",
"\n",
"This function builds a `construct.Struct` that can load a `MarginAccount` with a specific number of tokens. The number of markets and size of padding are derived from the number of tokens."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "danish-mainland",
"metadata": {},
"outputs": [],
"source": [
"def build_margin_account_parser_for_num_tokens(num_tokens: int) -> construct.Struct:\n",
" num_markets = num_tokens - 1\n",
"\n",
" return construct.Struct(\n",
" \"account_flags\" / MANGO_ACCOUNT_FLAGS,\n",
" \"mango_group\" / PublicKeyAdapter(),\n",
" \"owner\" / PublicKeyAdapter(),\n",
" \"deposits\" / construct.Array(num_tokens, FloatAdapter()),\n",
" \"borrows\" / construct.Array(num_tokens, FloatAdapter()),\n",
" \"open_orders\" / construct.Array(num_markets, PublicKeyAdapter()),\n",
" \"padding\" / construct.Padding(8)\n",
" )\n"
]
},
{
"cell_type": "markdown",
"id": "dietary-certification",
"metadata": {},
"source": [
"## build_margin_account_parser_for_length() function\n",
"\n",
"This function takes a data length (presumably the size of the structure returned from the `AccountInfo`) and returns a `MarginAccount` structure that can parse it.\n",
"\n",
"If the size doesn't _exactly_ match the size of the `Struct`, and Exception is raised."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "capable-compiler",
"metadata": {},
"outputs": [],
"source": [
"def build_margin_account_parser_for_length(length: int) -> construct.Struct:\n",
" tried_sizes: typing.List[int] = []\n",
" for num_tokens in itertools.count(start=2):\n",
" parser = build_margin_account_parser_for_num_tokens(num_tokens)\n",
" if parser.sizeof() == length:\n",
" return parser\n",
"\n",
" tried_sizes += [parser.sizeof()]\n",
" if parser.sizeof() > length:\n",
" raise Exception(f\"Could not create MarginAccount parser for length ({length}) - tried sizes ({tried_sizes})\")\n"
]
},
{
"cell_type": "markdown",
"id": "dried-batch",
"metadata": {},
"source": [
"# Instruction Structs"
@ -684,7 +894,7 @@
},
{
"cell_type": "markdown",
"id": "falling-provincial",
"id": "neutral-ambassador",
"metadata": {},
"source": [
"## MANGO_INSTRUCTION_VARIANT_FINDER\n",
@ -697,7 +907,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "spoken-treaty",
"id": "sized-banana",
"metadata": {},
"outputs": [],
"source": [
@ -708,7 +918,7 @@
},
{
"cell_type": "markdown",
"id": "bibliographic-behavior",
"id": "floating-share",
"metadata": {},
"source": [
"## Variant 0: INIT_MANGO_GROUP\n",
@ -729,7 +939,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "other-president",
"id": "moved-catering",
"metadata": {},
"outputs": [],
"source": [
@ -744,7 +954,7 @@
},
{
"cell_type": "markdown",
"id": "nutritional-headquarters",
"id": "assumed-custom",
"metadata": {},
"source": [
"## Variant 1: INIT_MARGIN_ACCOUNT\n",
@ -760,7 +970,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "handed-income",
"id": "stainless-exception",
"metadata": {},
"outputs": [],
"source": [
@ -771,7 +981,7 @@
},
{
"cell_type": "markdown",
"id": "incomplete-component",
"id": "developing-mother",
"metadata": {},
"source": [
"## Variant 2: DEPOSIT\n",
@ -789,7 +999,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "mounted-billy",
"id": "variable-frontier",
"metadata": {},
"outputs": [],
"source": [
@ -801,7 +1011,7 @@
},
{
"cell_type": "markdown",
"id": "representative-balance",
"id": "seasonal-cuisine",
"metadata": {},
"source": [
"## Variant 3: WITHDRAW\n",
@ -819,7 +1029,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "desperate-quantity",
"id": "exempt-private",
"metadata": {},
"outputs": [],
"source": [
@ -831,7 +1041,7 @@
},
{
"cell_type": "markdown",
"id": "patient-harrison",
"id": "spiritual-visit",
"metadata": {},
"source": [
"## Variant 4: BORROW\n",
@ -850,7 +1060,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "timely-haven",
"id": "civilian-handy",
"metadata": {},
"outputs": [],
"source": [
@ -863,7 +1073,7 @@
},
{
"cell_type": "markdown",
"id": "earlier-experiment",
"id": "duplicate-lighting",
"metadata": {},
"source": [
"## Variant 5: SETTLE_BORROW\n",
@ -882,7 +1092,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "every-median",
"id": "realistic-paint",
"metadata": {},
"outputs": [],
"source": [
@ -895,7 +1105,7 @@
},
{
"cell_type": "markdown",
"id": "informational-equilibrium",
"id": "surprising-uncertainty",
"metadata": {},
"source": [
"## Variant 6: LIQUIDATE\n",
@ -914,19 +1124,20 @@
{
"cell_type": "code",
"execution_count": null,
"id": "decreased-lender",
"id": "tested-grant",
"metadata": {},
"outputs": [],
"source": [
"_LIQUIDATE_NUM_TOKENS = 3 # Liquidate is deprecated and was only used with 3 tokens.\n",
"LIQUIDATE = construct.Struct(\n",
" \"variant\" / construct.Const(0x6, construct.BytesInteger(4, swapped=True)),\n",
" \"deposit_quantities\" / construct.Array(NUM_TOKENS, DecimalAdapter())\n",
" \"deposit_quantities\" / construct.Array(_LIQUIDATE_NUM_TOKENS, DecimalAdapter())\n",
")\n"
]
},
{
"cell_type": "markdown",
"id": "noticed-prior",
"id": "bibliographic-shanghai",
"metadata": {},
"source": [
"## Variant 7: DEPOSIT_SRM\n",
@ -948,7 +1159,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "fifth-container",
"id": "administrative-semiconductor",
"metadata": {},
"outputs": [],
"source": [
@ -960,7 +1171,7 @@
},
{
"cell_type": "markdown",
"id": "fabulous-weight",
"id": "acting-louis",
"metadata": {},
"source": [
"## Variant 8: WITHDRAW_SRM\n",
@ -978,7 +1189,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "earlier-vietnamese",
"id": "transparent-heath",
"metadata": {},
"outputs": [],
"source": [
@ -990,7 +1201,7 @@
},
{
"cell_type": "markdown",
"id": "fifteen-aquarium",
"id": "digital-skill",
"metadata": {},
"source": [
"## Variant 9: PLACE_ORDER\n",
@ -1008,7 +1219,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "starting-architecture",
"id": "powered-professor",
"metadata": {},
"outputs": [],
"source": [
@ -1020,7 +1231,7 @@
},
{
"cell_type": "markdown",
"id": "graduate-louisiana",
"id": "accessory-disability",
"metadata": {},
"source": [
"## Variant 10: SETTLE_FUNDS\n",
@ -1036,7 +1247,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "sixth-kennedy",
"id": "metallic-opposition",
"metadata": {},
"outputs": [],
"source": [
@ -1047,7 +1258,7 @@
},
{
"cell_type": "markdown",
"id": "searching-agent",
"id": "applicable-penny",
"metadata": {},
"source": [
"## Variant 11: CANCEL_ORDER\n",
@ -1065,7 +1276,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "under-ecuador",
"id": "curious-utility",
"metadata": {},
"outputs": [],
"source": [
@ -1077,7 +1288,7 @@
},
{
"cell_type": "markdown",
"id": "floating-discrimination",
"id": "certain-strike",
"metadata": {},
"source": [
"## Variant 12: CANCEL_ORDER_BY_CLIENT_ID\n",
@ -1095,7 +1306,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "swedish-sauce",
"id": "suspended-edition",
"metadata": {},
"outputs": [],
"source": [
@ -1107,7 +1318,7 @@
},
{
"cell_type": "markdown",
"id": "after-chester",
"id": "middle-cherry",
"metadata": {},
"source": [
"## Variant 13: CHANGE_BORROW_LIMIT\n",
@ -1128,7 +1339,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "greater-amsterdam",
"id": "single-nature",
"metadata": {},
"outputs": [],
"source": [
@ -1141,7 +1352,7 @@
},
{
"cell_type": "markdown",
"id": "ecological-cathedral",
"id": "divided-colorado",
"metadata": {},
"source": [
"## Variant 14: PLACE_AND_SETTLE\n",
@ -1159,7 +1370,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "changed-choir",
"id": "green-addiction",
"metadata": {},
"outputs": [],
"source": [
@ -1171,7 +1382,7 @@
},
{
"cell_type": "markdown",
"id": "equal-smart",
"id": "genetic-chile",
"metadata": {},
"source": [
"## Variant 15: FORCE_CANCEL_ORDERS\n",
@ -1192,7 +1403,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "monthly-ethics",
"id": "confident-paradise",
"metadata": {},
"outputs": [],
"source": [
@ -1204,7 +1415,7 @@
},
{
"cell_type": "markdown",
"id": "sublime-acting",
"id": "short-keeping",
"metadata": {},
"source": [
"## Variant 16: PARTIAL_LIQUIDATE\n",
@ -1224,7 +1435,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "mediterranean-plasma",
"id": "mediterranean-stake",
"metadata": {},
"outputs": [],
"source": [
@ -1236,7 +1447,7 @@
},
{
"cell_type": "markdown",
"id": "rapid-directory",
"id": "acceptable-founder",
"metadata": {},
"source": [
"## InstructionParsersByVariant dictionary\n",
@ -1247,7 +1458,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "capital-girlfriend",
"id": "funny-traffic",
"metadata": {},
"outputs": [],
"source": [
@ -1274,7 +1485,7 @@
},
{
"cell_type": "markdown",
"id": "outer-female",
"id": "perceived-money",
"metadata": {},
"source": [
"# 🏃 Running"
@ -1283,7 +1494,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "cooked-diagnosis",
"id": "covered-ukraine",
"metadata": {},
"outputs": [],
"source": [
@ -1293,12 +1504,20 @@
"\n",
" logging.getLogger().setLevel(logging.INFO)\n",
"\n",
" encoded = \"AwAAAAAAAACCaOmpoURMK6XHelGTaFawcuQ/78/15LAemWI8jrt3SRKLy2R9i60eclDjuDS8+p/ZhvTUd9G7uQVOYCsR6+BhmqGCiO6EPYP2PQkf/VRTvw7JjXvIjPFJy06QR1Cq1WfTonHl0OjCkyEf60SD07+MFJu5pVWNFGGEO/8AiAYfduaKdnFTaZEHPcK5Eq72WWHeHg2yIbBF09kyeOhlCJwOoG8O5SgpPV8QOA64ZNV4aKroFfADg6kEy/wWCdp3fv0O4GJgAAAAAPH6Ud6jtjwAAQAAAAAAAADiDkkCi9UOAAEAAAAAAAAADuBiYAAAAACNS5bSy7soAAEAAAAAAAAACMvgO+2jCwABAAAAAAAAAA7gYmAAAAAAZFeDUBNVhwABAAAAAAAAABtRNytozC8AAQAAAAAAAABIBGiCcyaEZdNhrTyeqUY692vOzzPdHaxAxguht3JQGlkzjtd05dX9LENHkl2z1XvUbTNKZlweypNRetmH0lmQ9VYQAHqylxZVK65gEg85g27YuSyvOBZAjJyRmYU9KdCO1D+4ehdPu9dQB1yI1uh75wShdAaFn2o4qrMYwq3SQQEAAAAAAAAAAiH1PPJKAuh6oGiE35aGhUQhFi/bxgKOudpFv8HEHNCFDy1uAqR6+CTQmradxC1wyyjL+iSft+5XudJWwSdi7wvphsxb96x7Obj/AgAAAAAKlV4LL5ow6r9LMhIAAAAADvsOtqcVFmChDPzPnwAAAE33lx1h8hPFD04AAAAAAAA8YRV3Oa309B2wGwAAAAAA+yPBZRlZz7b605n+AQAAAACgmZmZmZkZAQAAAAAAAAAAMDMzMzMzMwEAAAAAAAAA25D1XcAtRzSuuyx3U+X7aE9vM1EJySU9KprgL0LMJ/vat9+SEEUZuga7O5tTUrcMDYWDg+LYaAWhSQiN2fYk7aCGAQAAAAAAgIQeAAAAAAAA8gUqAQAAAAYGBgICAAAA\"\n",
" decoded = base64.b64decode(encoded)\n",
" encoded_3_token_group = \"AwAAAAAAAACCaOmpoURMK6XHelGTaFawcuQ/78/15LAemWI8jrt3SRKLy2R9i60eclDjuDS8+p/ZhvTUd9G7uQVOYCsR6+BhmqGCiO6EPYP2PQkf/VRTvw7JjXvIjPFJy06QR1Cq1WfTonHl0OjCkyEf60SD07+MFJu5pVWNFGGEO/8AiAYfduaKdnFTaZEHPcK5Eq72WWHeHg2yIbBF09kyeOhlCJwOoG8O5SgpPV8QOA64ZNV4aKroFfADg6kEy/wWCdp3fv0O4GJgAAAAAPH6Ud6jtjwAAQAAAAAAAADiDkkCi9UOAAEAAAAAAAAADuBiYAAAAACNS5bSy7soAAEAAAAAAAAACMvgO+2jCwABAAAAAAAAAA7gYmAAAAAAZFeDUBNVhwABAAAAAAAAABtRNytozC8AAQAAAAAAAABIBGiCcyaEZdNhrTyeqUY692vOzzPdHaxAxguht3JQGlkzjtd05dX9LENHkl2z1XvUbTNKZlweypNRetmH0lmQ9VYQAHqylxZVK65gEg85g27YuSyvOBZAjJyRmYU9KdCO1D+4ehdPu9dQB1yI1uh75wShdAaFn2o4qrMYwq3SQQEAAAAAAAAAAiH1PPJKAuh6oGiE35aGhUQhFi/bxgKOudpFv8HEHNCFDy1uAqR6+CTQmradxC1wyyjL+iSft+5XudJWwSdi7wvphsxb96x7Obj/AgAAAAAKlV4LL5ow6r9LMhIAAAAADvsOtqcVFmChDPzPnwAAAE33lx1h8hPFD04AAAAAAAA8YRV3Oa309B2wGwAAAAAA+yPBZRlZz7b605n+AQAAAACgmZmZmZkZAQAAAAAAAAAAMDMzMzMzMwEAAAAAAAAA25D1XcAtRzSuuyx3U+X7aE9vM1EJySU9KprgL0LMJ/vat9+SEEUZuga7O5tTUrcMDYWDg+LYaAWhSQiN2fYk7aCGAQAAAAAAgIQeAAAAAAAA8gUqAQAAAAYGBgICAAAA\"\n",
" decoded_3_token_group = base64.b64decode(encoded_3_token_group)\n",
"\n",
" group = GROUP.parse(decoded)\n",
" group_with_3_tokens = GROUP_V1.parse(decoded_3_token_group)\n",
" print(\"\\n\\nThis is hard-coded, not live information!\")\n",
" print(group)\n"
" print(group_with_3_tokens)\n",
"\n",
" encoded_5_token_group = \"AwAAAAAAAACk6bHzfLvX/YZNskK+2brLGvXTPR3P4qF2Hkc2HZANL3QVQJS5HzYh3sTbcf99JgISg7g07yK6MxP5nzzTyEy8BpuIV/6rgYT7aH9jRhjANdrEOdwa6ztVmKDwAAAAAAF6mkC+0kyNlxFPeKUyBpYL11A4a/pUcOYunlw92EFaYV3OcRQkdPidUvY2YtaR52uqW752f+Ufcci7ei8SWZYWgwYD6XWYDToBxFf2L2JoXCKqjFDfQ8+dfYUGnLBupwJdSa0UH20tWtHJh6VlhzFcMCQSU8LYiS6z6cR/GWXcWKd36DfFLhkULOxma3DDEBmYIqDsEZC02N3Vm5lJSc+okeVxcJzdQ48XVJuyTEli9TP2HAbrNqFJSyFbQo0f+dKsTopavA5ndrNOk9So4ANgHdwBGUVGY0SLS7+TVXkcCgwgqGAAAAAALYxEAuIBAAABAAAAAAAAAErLeL8QAAAAAQAAAAAAAAAMIKhgAAAAACwC4BEHAAAAAQAAAAAAAACByD0AAAAAAAEAAAAAAAAADCCoYAAAAABJpmxt87MCAAEAAAAAAAAArwaaHqGSAgABAAAAAAAAAAwgqGAAAAAANHqKx8PiAwABAAAAAAAAACyFpt5myQMAAQAAAAAAAAAMIKhgAAAAAIrCIhLj2gUAAQAAAAAAAACd0UEiFPsAAAEAAAAAAAAATVH2gvK/JiB7fCRmoBvLuTcba2/qHCRnzOjDFFYNStgzmK/Q8Wk0MLLXAIvgBrVJW1RiSvwiqrTezEkb3zUF4m47JYDk9DZOvmCGR63JwGv8BXI/DLCdMIagQ6MUC4krqaznZiQhDOVuXXpiS+7Q4PgE8V9wkGVQHFpDgVQVx6VSNqBWdfB5O0wXcsXSAxviIZnpzZb8yVPAJhqxFNViEC+Y1L1+ULx6KHTSRIwn5wXTktPuZEsRtSpUJpqZD9V5y+8i6KVSbQNBUDzf0xeAFH85lOlowgaoBW/MjycjsrXlD0s7jbPTK2SvlK0fgerysq1O8y7bk5yp6001Zr/sLAEAAAAAAAAAkE0bF+zrix2vIYYIDbjjQiF/B5h1fwDNpfltVuG2QD61vZ7LZ502Gt8wb/WL0dyrOS4eySbPnOiFlVUBjsVtHweAuy088r4ZtPPGxCoAAABG2qkYb53x/zKdhfmqjgMA+NOnThFN5Rte4iNxcwIAACjnpx/Yisq5DiDQQgAAAAAGSKNb2bYXz1S4odlZDwAAxfiZZdXRLGMAAAAAAAAAALNmZBB7GyHIDgAAAAAAAABPGsO1PmFjqp3jcQAAAAAAil3Lhe3fP1HPAgAAAAAAANnFw5TROkYQ1B82OwkAAAAAoJmZmZmZGQEAAAAAAAAAADAzMzMzMzMBAAAAAAAAAIOo484GpuWM2iV176h0nKGu2lHIio/GeSYKkkptjxvYBpU3RtcibIXhdSaNSvboTLAKz91Es3OeaoUjHgSxfAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQkJBgYCAgICAAAAAAAAAA==\"\n",
" decoded_5_token_group = base64.b64decode(encoded_5_token_group)\n",
"\n",
" group_with_5_tokens = GROUP_V2.parse(decoded_5_token_group)\n",
" print(\"\\n\\nThis is hard-coded, not live information!\")\n",
" print(group_with_5_tokens)\n",
"\n"
]
}
],

View File

@ -2,21 +2,21 @@
"cells": [
{
"cell_type": "markdown",
"id": "preliminary-walker",
"id": "worst-dragon",
"metadata": {},
"source": [
"# ⚠ Warning\n",
"\n",
"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.\n",
"\n",
"[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gl/OpinionatedGeek%2Fmango-explorer/HEAD?filepath=ShowMyAccounts.ipynb) _🏃 To run this notebook press the ⏩ icon in the toolbar above._\n",
"[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gl/OpinionatedGeek%2Fmango-explorer/HEAD?filepath=ShowAccount.ipynb) _🏃 To run this notebook press the ⏩ icon in the toolbar above._\n",
"\n",
"[🥭 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)"
]
},
{
"cell_type": "markdown",
"id": "transparent-danish",
"id": "established-stress",
"metadata": {},
"source": [
"# 🥭 Show My Accounts\n",
@ -28,7 +28,7 @@
},
{
"cell_type": "markdown",
"id": "angry-michael",
"id": "confidential-words",
"metadata": {},
"source": [
"## How To Use This Page\n",
@ -39,7 +39,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "substantial-petersburg",
"id": "approved-dress",
"metadata": {
"jupyter": {
"source_hidden": true
@ -52,6 +52,7 @@
"\n",
"from solana.publickey import PublicKey\n",
"\n",
"from AccountScout import AccountScout\n",
"from BaseModel import AccountInfo, Group, MarginAccount, TokenValue\n",
"from Constants import SYSTEM_PROGRAM_ADDRESS\n",
"from Context import default_context\n"
@ -60,7 +61,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "patient-atlantic",
"id": "ethical-eating",
"metadata": {},
"outputs": [],
"source": [
@ -70,41 +71,40 @@
{
"cell_type": "code",
"execution_count": null,
"id": "grateful-technique",
"id": "historic-question",
"metadata": {},
"outputs": [],
"source": [
"if ACCOUNT_TO_LOOK_UP == \"\":\n",
" raise Exception(\"No account to look up - try setting the variable ACCOUNT_TO_LOOK_UP to an account public key.\")\n",
"\n",
"print(\"Context:\", default_context)\n",
"default_context = default_context.new_from_cluster(\"devnet\").new_from_group_name(\"BTC_ETH_SOL_SRM_USDC\")\n",
"# print(\"Context:\", default_context)\n",
"\n",
"root_account_key = PublicKey(ACCOUNT_TO_LOOK_UP)\n",
"root_account = AccountInfo.load(default_context, root_account_key)\n",
"if root_account is None:\n",
" raise Exception(f\"Account '{root_account_key}' could not be found.\")\n",
"\n",
"print(\"My account:\", root_account)\n",
"print(\"Account:\", root_account)\n",
"if root_account.owner != SYSTEM_PROGRAM_ADDRESS:\n",
" raise Exception(f\"Account '{root_account_key}' is not a root user account.\")\n",
"\n",
"scout = AccountScout()\n",
"group = Group.load(default_context)\n",
"# print(\"Group:\", group)\n",
"scout_report = scout.verify_account_prepared_for_group(default_context, group, root_account_key)\n",
"print(scout_report)\n",
"\n",
"print(\"Balances:\")\n",
"print(f\" SOL balance: {default_context.fetch_sol_balance(root_account_key):>18,.8f}\")\n",
"group = Group.load(default_context)\n",
"for basket_token in group.basket_tokens:\n",
" balance = TokenValue.fetch_total_value(default_context, root_account_key, basket_token.token)\n",
" print(f\"{basket_token.token.name:>7} balance: {balance.value:>18,.8f}\")\n",
"TokenValue.report(print, group.fetch_balances(root_account_key))\n",
"\n",
"prices = group.fetch_token_prices()\n",
"print(prices)\n",
"\n",
"my_margin_accounts = MarginAccount.load_all_for_owner(default_context, root_account_key, group)\n",
"for my_margin_account in my_margin_accounts:\n",
" print(\"My margin account:\", my_margin_account)\n",
" print(\"Balance sheet totals\", my_margin_account.get_balance_sheet_totals(group, prices))\n"
"margin_accounts = MarginAccount.load_all_for_owner(default_context, root_account_key, group)\n",
"print(f\"Account has {len(margin_accounts)} margin account(s).\")\n",
"for margin_account in margin_accounts:\n",
" print(\"Margin account:\", margin_account)\n",
" print(\"Balance sheet totals\", margin_account.get_balance_sheet_totals(group, prices))\n"
]
}
],

View File

@ -2,33 +2,35 @@
"cells": [
{
"cell_type": "markdown",
"id": "stylish-fetish",
"id": "appreciated-freeze",
"metadata": {},
"source": [
"# ⚠ Warning\n",
"\n",
"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.\n",
"\n",
"[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gl/OpinionatedGeek%2Fmango-explorer/HEAD?filepath=ShowAllAccounts.ipynb) _🏃 To run this notebook press the ⏩ icon in the toolbar above._\n",
"[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gl/OpinionatedGeek%2Fmango-explorer/HEAD?filepath=ShowAllMarginAccounts.ipynb) _🏃 To run this notebook press the ⏩ icon in the toolbar above._\n",
"\n",
"[🥭 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)"
]
},
{
"cell_type": "markdown",
"id": "smart-woman",
"id": "mighty-helena",
"metadata": {},
"source": [
"# 🥭 Show All Accounts\n",
"# 🥭 Show All Margin Accounts\n",
"\n",
"This notebook tries to display information about all Mango margin accounts.\n",
"This notebook tries to display information about all Mango Markets margin accounts.\n",
"\n",
"It fetches the data from Solana, parses it, and then prints it.\n"
"It fetches the data from Solana, parses it, and then prints it.\n",
"\n",
"Note: this can take a long time to run."
]
},
{
"cell_type": "markdown",
"id": "brutal-imagination",
"id": "spanish-sheep",
"metadata": {},
"source": [
"## How To Use This Page\n",
@ -39,7 +41,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "involved-lafayette",
"id": "determined-blair",
"metadata": {
"jupyter": {
"source_hidden": true
@ -50,47 +52,35 @@
"import logging\n",
"logging.getLogger().setLevel(logging.ERROR)\n",
"\n",
"from pyserum.market import Market\n",
"\n",
"from BaseModel import AccountInfo, Group, MarginAccount, TokenAccount\n",
"from Context import default_context\n"
"from BaseModel import Group, MarginAccount\n",
"from Context import Context, default_context\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "jewish-miami",
"id": "sufficient-parent",
"metadata": {},
"outputs": [],
"source": [
"def show_all_accounts():\n",
" print(\"Context:\", default_context)\n",
"\n",
" group = Group.load(default_context)\n",
" print(\"Group:\", group)\n",
"\n",
" markets = list(map(lambda market: Market.load(default_context.client, market.spot), group.markets))\n",
" print(\"Markets:\", markets)\n",
"\n",
" vaults = AccountInfo.load_multiple(default_context, [token.vault for token in group.basket_tokens])\n",
" print(\"Vaults:\", vaults)\n",
"\n",
" for index, vault in enumerate(vaults):\n",
" token = TokenAccount.parse(vault)\n",
" decimals = group.basket_tokens[index].token.decimals\n",
" amount = token.amount / (10 ** decimals)\n",
" print(f\"Vault token amount[{index}]: {amount:,.8f}\")\n",
"\n",
"def show_all_margin_accounts(context: Context):\n",
" import time\n",
" start_time = time.time()\n",
"\n",
" print(\"Loading group...\")\n",
" group = Group.load(context)\n",
" print(f\"Done loading group. Time taken: {time.time() - start_time}\")\n",
"\n",
" print(\"Loading margin accounts...\")\n",
" margin_accounts = MarginAccount.load_all_for_group_with_open_orders(default_context, default_context.program_id, group)\n",
" print(f\"Done. Time taken: {time.time() - start_time}\")\n",
"# margin_accounts = MarginAccount.load_all_for_group(default_context, default_context.program_id, group)\n",
" margin_accounts = MarginAccount.load_all_for_group_with_open_orders(context, context.program_id, group)\n",
"# margin_accounts = group.load_ripe_margin_accounts()\n",
" print(f\"Done loading {len(margin_accounts)} account(s). Total time taken: {time.time() - start_time}\")\n",
"\n",
" print(margin_accounts)\n",
" print(*margin_accounts, sep=\"\\n\")\n",
"\n",
"\n",
"show_all_accounts()\n",
"show_all_margin_accounts(default_context)\n",
"# import cProfile\n",
"# import pstats\n",
"# cProfile.run(\"show_all_accounts()\", sort=pstats.SortKey.TIME)\n"

141
ShowGroup.ipynb Normal file
View File

@ -0,0 +1,141 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "featured-school",
"metadata": {},
"source": [
"# ⚠ Warning\n",
"\n",
"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.\n",
"\n",
"[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gl/OpinionatedGeek%2Fmango-explorer/HEAD?filepath=ShowGroup.ipynb) _🏃 To run this notebook press the ⏩ icon in the toolbar above._\n",
"\n",
"[🥭 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)"
]
},
{
"cell_type": "markdown",
"id": "administrative-malta",
"metadata": {},
"source": [
"# 🥭 Show Group\n",
"\n",
"This notebook tries to display information about a specific Mango Markets group.\n",
"\n",
"It fetches the data from Solana, parses it, and then prints it.\n"
]
},
{
"cell_type": "markdown",
"id": "deadly-density",
"metadata": {},
"source": [
"## How To Use This Page\n",
"\n",
"Enter the name of the group you want to check in the value for `GROUP_TO_LOOK_UP` in the box below, between the double-quote marks. Then run the notebook by choosing 'Run > Run All Cells' from the notebook menu at the top of the page."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "composed-delaware",
"metadata": {},
"outputs": [],
"source": [
"import logging\n",
"logging.getLogger().setLevel(logging.ERROR)\n",
"\n",
"from BaseModel import Group\n",
"from Context import default_context\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "prompt-chart",
"metadata": {},
"outputs": [],
"source": [
"GROUP_TO_LOOK_UP = \"\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "sitting-america",
"metadata": {},
"outputs": [],
"source": [
"if GROUP_TO_LOOK_UP == \"\":\n",
" raise Exception(\"No group to look up - try setting the variable GROUP_TO_LOOK_UP to an group's public key.\")\n",
"\n",
"group = Group.load(default_context)\n",
"print(group)\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.6"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": true,
"toc_window_display": true
},
"varInspector": {
"cols": {
"lenName": 16,
"lenType": 16,
"lenVar": 40
},
"kernels_config": {
"python": {
"delete_cmd_postfix": "",
"delete_cmd_prefix": "del ",
"library": "var_list.py",
"varRefreshCmd": "print(var_dic_list())"
},
"r": {
"delete_cmd_postfix": ") ",
"delete_cmd_prefix": "rm(",
"library": "var_list.r",
"varRefreshCmd": "cat(var_dic_list()) "
}
},
"types_to_exclude": [
"module",
"function",
"builtin_function_or_method",
"instance",
"_Feature"
],
"window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 5
}

148
ShowMarginAccount.ipynb Normal file
View File

@ -0,0 +1,148 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "literary-grace",
"metadata": {},
"source": [
"# ⚠ Warning\n",
"\n",
"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.\n",
"\n",
"[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gl/OpinionatedGeek%2Fmango-explorer/HEAD?filepath=ShowMarginAccount.ipynb) _🏃 To run this notebook press the ⏩ icon in the toolbar above._\n",
"\n",
"[🥭 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)"
]
},
{
"cell_type": "markdown",
"id": "broad-brazilian",
"metadata": {},
"source": [
"# 🥭 Show Margin Account\n",
"\n",
"This notebook tries to display information about a specific Mango Markets margin account.\n",
"\n",
"It fetches the data from Solana, parses it, and then prints it.\n"
]
},
{
"cell_type": "markdown",
"id": "artistic-arthritis",
"metadata": {},
"source": [
"## How To Use This Page\n",
"\n",
"Enter the public key of the margin account you want to check in the value for `MARGIN_ACCOUNT_TO_LOOK_UP` in the box below, between the double-quote marks. Then run the notebook by choosing 'Run > Run All Cells' from the notebook menu at the top of the page."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "crucial-allergy",
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"import logging\n",
"logging.getLogger().setLevel(logging.ERROR)\n",
"\n",
"from solana.publickey import PublicKey\n",
"\n",
"from BaseModel import Group, MarginAccount\n",
"from Context import default_context\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "adopted-capital",
"metadata": {},
"outputs": [],
"source": [
"MARGIN_ACCOUNT_TO_LOOK_UP = \"\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "surprised-occasion",
"metadata": {},
"outputs": [],
"source": [
"if MARGIN_ACCOUNT_TO_LOOK_UP == \"\":\n",
" raise Exception(\"No account to look up - try setting the variable ACCOUNT_TO_LOOK_UP to an account public key.\")\n",
"\n",
"group = Group.load(default_context)\n",
"margin_account = MarginAccount.load(default_context, PublicKey(MARGIN_ACCOUNT_TO_LOOK_UP), group)\n",
"print(margin_account)\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.6"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": true,
"toc_window_display": true
},
"varInspector": {
"cols": {
"lenName": 16,
"lenType": 16,
"lenVar": 40
},
"kernels_config": {
"python": {
"delete_cmd_postfix": "",
"delete_cmd_prefix": "del ",
"library": "var_list.py",
"varRefreshCmd": "print(var_dic_list())"
},
"r": {
"delete_cmd_postfix": ") ",
"delete_cmd_prefix": "rm(",
"library": "var_list.r",
"varRefreshCmd": "cat(var_dic_list()) "
}
},
"types_to_exclude": [
"module",
"function",
"builtin_function_or_method",
"instance",
"_Feature"
],
"window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,140 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "identical-ivory",
"metadata": {},
"source": [
"# ⚠ Warning\n",
"\n",
"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.\n",
"\n",
"[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gl/OpinionatedGeek%2Fmango-explorer/HEAD?filepath=ShowRipeMarginAccounts.ipynb) _🏃 To run this notebook press the ⏩ icon in the toolbar above._\n",
"\n",
"[🥭 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)"
]
},
{
"cell_type": "markdown",
"id": "express-bottom",
"metadata": {},
"source": [
"# 🥭 Show Ripe Margin Accounts\n",
"\n",
"This notebook tries to display information about all 'ripe' Mango Markets margin accounts. A 'ripe' margin account is one where the collateral ratio is less than the Initial Collateral Ratio but more than the Maintenance Collateral Ratio.\n",
"\n",
"It fetches the data from Solana, parses it, and then prints it.\n"
]
},
{
"cell_type": "markdown",
"id": "quantitative-natural",
"metadata": {},
"source": [
"## How To Use This Page\n",
"\n",
"Configure the `Context` for the `Group` you want to load, or just use as-is for the default `Group`.\n",
"\n",
"For example, to change to the _BTC_ETH_USDT_ `Group`, use:\n",
"```\n",
"default_context = default_context.new_from_group_name(\"BTC_ETH_USDT\")\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "assigned-headquarters",
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"import logging\n",
"logging.getLogger().setLevel(logging.ERROR)\n",
"\n",
"from BaseModel import Group\n",
"from Context import default_context\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "sticky-button",
"metadata": {},
"outputs": [],
"source": [
"group = Group.load(default_context)\n",
"\n",
"ripe_margin_accounts = group.load_ripe_margin_accounts()\n",
"print(f\"Fetched {len(ripe_margin_accounts)} ripe margin account(s).\")\n",
"print(*ripe_margin_accounts, sep=\"\\n\")\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.6"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": true,
"toc_window_display": true
},
"varInspector": {
"cols": {
"lenName": 16,
"lenType": 16,
"lenVar": 40
},
"kernels_config": {
"python": {
"delete_cmd_postfix": "",
"delete_cmd_prefix": "del ",
"library": "var_list.py",
"varRefreshCmd": "print(var_dic_list())"
},
"r": {
"delete_cmd_postfix": ") ",
"delete_cmd_prefix": "rm(",
"library": "var_list.r",
"varRefreshCmd": "cat(var_dic_list()) "
}
},
"types_to_exclude": [
"module",
"function",
"builtin_function_or_method",
"instance",
"_Feature"
],
"window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -31,7 +31,7 @@ from decimal import Decimal
from AccountScout import AccountScout
from AccountLiquidator import AccountLiquidator, ForceCancelOrdersAccountLiquidator, NullAccountLiquidator, ReportingAccountLiquidator
from BaseModel import Group, LiquidationEvent, MarginAccount
from BaseModel import Group, LiquidationEvent
from Constants import WARNING_DISCLAIMER_TEXT
from Context import Context, default_cluster, default_cluster_url, default_program_id, default_dex_program_id, default_group_name, default_group_id
from LiquidationProcessor import LiquidationProcessor
@ -63,9 +63,9 @@ parser.add_argument("--name", type=str, default="Mango Markets Liquidator",
help="Name of the liquidator (used in reports and alerts)")
parser.add_argument("--log-level", default=logging.INFO, type=lambda level: getattr(logging, level),
help="level of verbosity to log (possible values: DEBUG, INFO, WARNING, ERROR, CRITICAL)")
parser.add_argument("--throttle-reload-to-seconds", type=int, default=60,
parser.add_argument("--throttle-reload-to-seconds", type=Decimal, default=Decimal(60),
help="minimum number of seconds between each full margin account reload loop (including time taken processing accounts)")
parser.add_argument("--throttle-ripe-update-to-seconds", type=int, default=5,
parser.add_argument("--throttle-ripe-update-to-seconds", type=Decimal, default=Decimal(5),
help="minimum number of seconds between each ripe update loop (including time taken processing accounts)")
parser.add_argument("--ripe-update-iterations", type=int, default=10,
help="number of iterations of ripe updates before performing a full reload of all margin accounts")
@ -162,7 +162,7 @@ try:
while not stop:
try:
margin_account_loop_started_at = time.time()
ripe = MarginAccount.load_all_ripe(context)
ripe = group.load_ripe_margin_accounts()
liquidation_processor.update_margin_accounts(ripe)
@ -176,14 +176,14 @@ try:
liquidation_processor.update_prices(prices)
price_loop_time_taken = time.time() - price_loop_started_at
price_loop_should_sleep_for = throttle_ripe_update_to_seconds - int(price_loop_time_taken)
price_loop_sleep_for = max(price_loop_should_sleep_for, 0)
price_loop_should_sleep_for = float(throttle_ripe_update_to_seconds) - price_loop_time_taken
price_loop_sleep_for = max(price_loop_should_sleep_for, 0.0)
logging.info(f"Price fetch and check of all ripe 🥭 accounts complete. Time taken: {price_loop_time_taken:.2f} seconds, sleeping for {price_loop_sleep_for} seconds...")
time.sleep(price_loop_sleep_for)
margin_account_loop_time_taken = time.time() - margin_account_loop_started_at
margin_account_should_sleep_for = throttle_reload_to_seconds - int(margin_account_loop_time_taken)
margin_account_sleep_for = max(margin_account_should_sleep_for, 0)
margin_account_should_sleep_for = float(throttle_reload_to_seconds) - int(margin_account_loop_time_taken)
margin_account_sleep_for = max(margin_account_should_sleep_for, 0.0)
logging.info(f"Check of all margin accounts complete. Time taken: {margin_account_loop_time_taken:.2f} seconds, sleeping for {margin_account_sleep_for} seconds...")
time.sleep(margin_account_sleep_for)
except KeyboardInterrupt:

View File

@ -29,7 +29,7 @@ import traceback
from AccountScout import AccountScout
from AccountLiquidator import AccountLiquidator, ForceCancelOrdersAccountLiquidator, NullAccountLiquidator, ReportingAccountLiquidator
from BaseModel import Group, LiquidationEvent, MarginAccount
from BaseModel import Group, LiquidationEvent
from Constants import WARNING_DISCLAIMER_TEXT
from Context import Context, default_cluster, default_cluster_url, default_program_id, default_dex_program_id, default_group_name, default_group_id
from LiquidationProcessor import LiquidationProcessor
@ -132,7 +132,7 @@ try:
liquidation_processor = LiquidationProcessor(context, account_liquidator, wallet_balancer)
started_at = time.time()
ripe = MarginAccount.load_all_ripe(context)
ripe = group.load_ripe_margin_accounts()
liquidation_processor.update_margin_accounts(ripe)
prices = group.fetch_token_prices()

194
ids.json
View File

@ -8,16 +8,102 @@
"devnet": {
"dex_program_id": "DESVgJVGajEgKGXhb6XmqDHGz3VjdgP7rEVESBgxmroY",
"faucets": {
"BTC": "97z3NzcDxqRMyE7F73PuHEmAbA72S7eDopjhe7GTymTk",
"ETH": "CvRouhBrimBuSyLd8zHxduNJDtV4LWtoowoF62FCwK7V",
"BTC": "454w2aqqmu3tzY3dgCh8gCk6jwQcxdo6ojvqj2JcLJqh",
"ETH": "346dJro4LN5mBVHXYNAkLnritKkbXwYKvJQep1aSUB8K",
"MSRM": "9ysywkpvyvxaaezq2Dapj1p1gHPP3U3D3ccTTecVfYHe",
"SRM": "9NzrM7CZ1jq46mX2JGcqyUxBupQEn614A5sZrvg3TrCU",
"USDC": "",
"USDC": "53g5HU8wHmSCmc54VEqFS5heNAW5sNC8TDzZPAFVdCnQ",
"USDT": "AS1EfwXvpejkkLrEPdz9J84By9kPvVBzYaD6Ks8ya1A6",
"WUSDT": "DV8YAUHc4CiadQoFCHriTjNXbtwCw1Rt834EBYeCyvGt"
},
"fee_symbol": "SRM",
"mango_groups": {
"BTC_ETH_SOL_SRM_USDC": {
"mango_group_pk": "79rqTHePsNnGEX6Re8Xkgf4QEkBfsXncXzyRbyMhHPMU",
"mint_pks": [
"bypQzRBaSDWiKhoAw3hNkf35eF3z3AZCU8Sxks6mTPP",
"ErWGBLBQMwdyC4H3MR8ef6pFK6gyHAyBxy4o1mHoqKzm",
"So11111111111111111111111111111111111111112",
"9FbAMDvXqNjPqZSYt4EWTguJuDrGkfvwr3gSFpiSbX9S",
"H6hy7Ykzc43EuGivv7VVuUKNpKgUoFAfUY3wdPr4UyRX"
],
"oracle_pks": [
"6Xvk6VC423bbhwnCfMyPfE4C1vytoqsVMUY1Lbqeh6pf",
"4CoKvk3NUXYiHKGbQvihadw6TC8LTN1qjfadPcsaURbW",
"Ej5FrNjhXaePK7cVMZtSooatzXMeunNsjxrnubefEyNC",
"GR9tYpi8CM8u8sdRaJZoP32KoWBphyoWV3xoNt4XwmRV"
],
"spot_market_pks": [
"BCqDfFd119UyNEC2HavKdy3F4qhy6EMGirSurNWKgioW",
"AfB75DQs1E2VoUAMRorUxAz68b18kWZ1uqQuRibGk212",
"6vZd6Ghwkuzpbp7qNzBuRkhcfA9H3S7BJ2LCWSYrjfzo",
"6rRnXBLGzcD5v1q4NfWWZQdgBfqzEuD3g4GqDWVU8yhH"
],
"spot_market_symbols": {
"BTC/USDC": "BCqDfFd119UyNEC2HavKdy3F4qhy6EMGirSurNWKgioW",
"ETH/USDC": "AfB75DQs1E2VoUAMRorUxAz68b18kWZ1uqQuRibGk212",
"SOL/USDC": "6vZd6Ghwkuzpbp7qNzBuRkhcfA9H3S7BJ2LCWSYrjfzo",
"SRM/USDC": "6rRnXBLGzcD5v1q4NfWWZQdgBfqzEuD3g4GqDWVU8yhH"
},
"srm_vault_pk": "8EJes1XNUL3ZodtjdAQMvo66V7LkijbDS5XmPRViM8sc",
"symbols": {
"BTC": "bypQzRBaSDWiKhoAw3hNkf35eF3z3AZCU8Sxks6mTPP",
"ETH": "ErWGBLBQMwdyC4H3MR8ef6pFK6gyHAyBxy4o1mHoqKzm",
"SOL": "So11111111111111111111111111111111111111112",
"SRM": "9FbAMDvXqNjPqZSYt4EWTguJuDrGkfvwr3gSFpiSbX9S",
"USDC": "H6hy7Ykzc43EuGivv7VVuUKNpKgUoFAfUY3wdPr4UyRX"
},
"vault_pks": [
"2LNJSCfxktCvFG9ouBKbArMbQsRf8t2vqK8gBaXSKaQU",
"JA2d4bHESoAsXYG7yhQmG5rPU2T4sfAC4Wf54ERLfswi",
"DaNQipAgf1EAVfHFRUQKY1qBqLMzrVtUyWQZKQB9souH",
"4p3k3wDz6hEcCWBiqLj4egt2NvBiHyE1FUkQUuHvsGL9",
"J9fQsNQRSUyhC2kngGgda4yGKBSgcxQX8WrdcFTcnciL"
]
},
"BTC_ETH_SOL_SRM_USDT": {
"mango_group_pk": "3pxcwMxrQ8xmERAFWkMmNmKLquWcdF143ns4XjZ2P9zd",
"mint_pks": [
"C6kYXcaRUMqeBF5fhg165RWU7AnpT9z92fvKNoMqjmz6",
"8p968u9m7jZzKSsqxFDqki69MjqdFkwPM9FN4AN8hvHR",
"So11111111111111111111111111111111111111112",
"9FbAMDvXqNjPqZSYt4EWTguJuDrGkfvwr3gSFpiSbX9S",
"7KBVenLz5WNH4PA5MdGkJNpDDyNKnBQTwnz1UqJv9GUm"
],
"oracle_pks": [
"6Xvk6VC423bbhwnCfMyPfE4C1vytoqsVMUY1Lbqeh6pf",
"4CoKvk3NUXYiHKGbQvihadw6TC8LTN1qjfadPcsaURbW",
"Ej5FrNjhXaePK7cVMZtSooatzXMeunNsjxrnubefEyNC",
"GR9tYpi8CM8u8sdRaJZoP32KoWBphyoWV3xoNt4XwmRV"
],
"spot_market_pks": [
"6Cpt7EYmzUcHLBQzZuYNqnyKQKieofZXw6bpCWtmwZM1",
"4UQq7c8FdwGkb2TghHVgJShHMJwS4YzjvA3yiF6zArJD",
"8RJA4WhY2Ei48c4xANSgPoqw7DU7mRgvg6eqJS3tvLEN",
"CRLpSnSf7JkoJi9tUnz55R2FoTCrDDkWxQMU6uSVBQgc"
],
"spot_market_symbols": {
"BTC/USDT": "6Cpt7EYmzUcHLBQzZuYNqnyKQKieofZXw6bpCWtmwZM1",
"ETH/USDT": "4UQq7c8FdwGkb2TghHVgJShHMJwS4YzjvA3yiF6zArJD",
"SOL/USDT": "8RJA4WhY2Ei48c4xANSgPoqw7DU7mRgvg6eqJS3tvLEN",
"SRM/USDT": "CRLpSnSf7JkoJi9tUnz55R2FoTCrDDkWxQMU6uSVBQgc"
},
"srm_vault_pk": "9rwmye1qcsD2txMxG7ZKdyJwyTyHXPwkB7y4K1vX9LXZ",
"symbols": {
"BTC": "C6kYXcaRUMqeBF5fhg165RWU7AnpT9z92fvKNoMqjmz6",
"ETH": "8p968u9m7jZzKSsqxFDqki69MjqdFkwPM9FN4AN8hvHR",
"SOL": "So11111111111111111111111111111111111111112",
"SRM": "9FbAMDvXqNjPqZSYt4EWTguJuDrGkfvwr3gSFpiSbX9S",
"USDT": "7KBVenLz5WNH4PA5MdGkJNpDDyNKnBQTwnz1UqJv9GUm"
},
"vault_pks": [
"9pTjFBB3xheuqR9iDG63x2TLZjeb6f3yCBZE6EjYtqV3",
"7HA5Ne1g2t8cRvzEYdoMwGJch1AneMQLiJRJccm1tw9y",
"CGj8exjKg88byyjRCEuYGB5CXvAqB1YzHEHrDiUFLwYK",
"ApX38vWvRybQHKoj6AsQHQDa7gQPChYkNHgqAj2kDxDo",
"CbcaxuYfe53NTX5eRUaRzxGyRyMLTt7JT6p2p6VZVnh7"
]
},
"BTC_ETH_USDC": {
"mango_group_pk": "C9ZtsC1wmqMzbyCUTeBppZSKH82FsKrGnaWjv5BtWvvo",
"mint_pks": [
@ -143,7 +229,7 @@
]
}
},
"mango_program_id": "32YaLZeyUHhdFaGSemTncUEcZEEGjKart8NX1XG2k3fs",
"mango_program_id": "3ZhWpsT19EuBZhG2BvcTVHBtFW4vFacGgLYpXZxotg1P",
"oracles": {
"BTC/USDC": "6Xvk6VC423bbhwnCfMyPfE4C1vytoqsVMUY1Lbqeh6pf",
"BTC/USDT": "6Xvk6VC423bbhwnCfMyPfE4C1vytoqsVMUY1Lbqeh6pf",
@ -151,26 +237,30 @@
"ETH/USDC": "4CoKvk3NUXYiHKGbQvihadw6TC8LTN1qjfadPcsaURbW",
"ETH/USDT": "4CoKvk3NUXYiHKGbQvihadw6TC8LTN1qjfadPcsaURbW",
"ETH/WUSDT": "4CoKvk3NUXYiHKGbQvihadw6TC8LTN1qjfadPcsaURbW",
"SOL/USDC": "Ej5FrNjhXaePK7cVMZtSooatzXMeunNsjxrnubefEyNC",
"SOL/USDT": "Ej5FrNjhXaePK7cVMZtSooatzXMeunNsjxrnubefEyNC",
"SRM/USDC": "GR9tYpi8CM8u8sdRaJZoP32KoWBphyoWV3xoNt4XwmRV",
"SRM/USDT": "GR9tYpi8CM8u8sdRaJZoP32KoWBphyoWV3xoNt4XwmRV"
},
"spot_markets": {
"BTC/USDC": "FKysSZkCCh41G1SCxpE7Cb7eaLofYBhEneLzHFz6JvjH",
"BTC/USDC": "BCqDfFd119UyNEC2HavKdy3F4qhy6EMGirSurNWKgioW",
"BTC/USDT": "6Cpt7EYmzUcHLBQzZuYNqnyKQKieofZXw6bpCWtmwZM1",
"BTC/WUSDT": "ELXP9wTE4apvK9sxAqtCtMidbAvJJDrNVg4wL6jqQEBA",
"ETH/USDC": "BYz5dJegg11x94jS2R7ZTCgaJwimvupmkjeYDm9Y3UwP",
"ETH/USDC": "AfB75DQs1E2VoUAMRorUxAz68b18kWZ1uqQuRibGk212",
"ETH/USDT": "4UQq7c8FdwGkb2TghHVgJShHMJwS4YzjvA3yiF6zArJD",
"ETH/WUSDT": "97mbLfi4S56y5Vg2LCF4Z7ru8jD1QjHa5SH3eyFYrMdg",
"SOL/USDC": "6vZd6Ghwkuzpbp7qNzBuRkhcfA9H3S7BJ2LCWSYrjfzo",
"SOL/USDT": "8RJA4WhY2Ei48c4xANSgPoqw7DU7mRgvg6eqJS3tvLEN",
"SRM/USDC": "6rRnXBLGzcD5v1q4NfWWZQdgBfqzEuD3g4GqDWVU8yhH",
"SRM/USDT": "CRLpSnSf7JkoJi9tUnz55R2FoTCrDDkWxQMU6uSVBQgc"
},
"symbols": {
"BTC": "C6kYXcaRUMqeBF5fhg165RWU7AnpT9z92fvKNoMqjmz6",
"ETH": "8p968u9m7jZzKSsqxFDqki69MjqdFkwPM9FN4AN8hvHR",
"BTC": "bypQzRBaSDWiKhoAw3hNkf35eF3z3AZCU8Sxks6mTPP",
"ETH": "ErWGBLBQMwdyC4H3MR8ef6pFK6gyHAyBxy4o1mHoqKzm",
"MSRM": "934bNdNw9QfE8dXD4mKQiKajYURfSkPhxfYZzpvmygca",
"SOL": "So11111111111111111111111111111111111111112",
"SRM": "9FbAMDvXqNjPqZSYt4EWTguJuDrGkfvwr3gSFpiSbX9S",
"USDC": "Fq939Y5hycK62ZGwBjftLY2VyxqAQ8f1MxRqBMdAaBS7",
"USDC": "H6hy7Ykzc43EuGivv7VVuUKNpKgUoFAfUY3wdPr4UyRX",
"USDT": "7KBVenLz5WNH4PA5MdGkJNpDDyNKnBQTwnz1UqJv9GUm",
"WUSDT": "7tSPGVhneTBWZjLGJGZb9V2UntC7T98cwtSLtgcXjeSs"
}
@ -196,6 +286,92 @@
"dex_program_id_v2": "EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o",
"fee_token": "SRM",
"mango_groups": {
"BTC_ETH_SOL_SRM_USDC": {
"mango_group_pk": "79rqTHePsNnGEX6Re8Xkgf4QEkBfsXncXzyRbyMhHPMU",
"mint_pks": [
"bypQzRBaSDWiKhoAw3hNkf35eF3z3AZCU8Sxks6mTPP",
"ErWGBLBQMwdyC4H3MR8ef6pFK6gyHAyBxy4o1mHoqKzm",
"So11111111111111111111111111111111111111112",
"9FbAMDvXqNjPqZSYt4EWTguJuDrGkfvwr3gSFpiSbX9S",
"H6hy7Ykzc43EuGivv7VVuUKNpKgUoFAfUY3wdPr4UyRX"
],
"oracle_pks": [
"6Xvk6VC423bbhwnCfMyPfE4C1vytoqsVMUY1Lbqeh6pf",
"4CoKvk3NUXYiHKGbQvihadw6TC8LTN1qjfadPcsaURbW",
"Ej5FrNjhXaePK7cVMZtSooatzXMeunNsjxrnubefEyNC",
"GR9tYpi8CM8u8sdRaJZoP32KoWBphyoWV3xoNt4XwmRV"
],
"spot_market_pks": [
"BCqDfFd119UyNEC2HavKdy3F4qhy6EMGirSurNWKgioW",
"AfB75DQs1E2VoUAMRorUxAz68b18kWZ1uqQuRibGk212",
"6vZd6Ghwkuzpbp7qNzBuRkhcfA9H3S7BJ2LCWSYrjfzo",
"6rRnXBLGzcD5v1q4NfWWZQdgBfqzEuD3g4GqDWVU8yhH"
],
"spot_market_symbols": {
"BTC/USDC": "BCqDfFd119UyNEC2HavKdy3F4qhy6EMGirSurNWKgioW",
"ETH/USDC": "AfB75DQs1E2VoUAMRorUxAz68b18kWZ1uqQuRibGk212",
"SOL/USDC": "6vZd6Ghwkuzpbp7qNzBuRkhcfA9H3S7BJ2LCWSYrjfzo",
"SRM/USDC": "6rRnXBLGzcD5v1q4NfWWZQdgBfqzEuD3g4GqDWVU8yhH"
},
"srm_vault_pk": "8EJes1XNUL3ZodtjdAQMvo66V7LkijbDS5XmPRViM8sc",
"symbols": {
"BTC": "bypQzRBaSDWiKhoAw3hNkf35eF3z3AZCU8Sxks6mTPP",
"ETH": "ErWGBLBQMwdyC4H3MR8ef6pFK6gyHAyBxy4o1mHoqKzm",
"SOL": "So11111111111111111111111111111111111111112",
"SRM": "9FbAMDvXqNjPqZSYt4EWTguJuDrGkfvwr3gSFpiSbX9S",
"USDC": "H6hy7Ykzc43EuGivv7VVuUKNpKgUoFAfUY3wdPr4UyRX"
},
"vault_pks": [
"2LNJSCfxktCvFG9ouBKbArMbQsRf8t2vqK8gBaXSKaQU",
"JA2d4bHESoAsXYG7yhQmG5rPU2T4sfAC4Wf54ERLfswi",
"DaNQipAgf1EAVfHFRUQKY1qBqLMzrVtUyWQZKQB9souH",
"4p3k3wDz6hEcCWBiqLj4egt2NvBiHyE1FUkQUuHvsGL9",
"J9fQsNQRSUyhC2kngGgda4yGKBSgcxQX8WrdcFTcnciL"
]
},
"BTC_ETH_SOL_SRM_USDT": {
"mango_group_pk": "3pxcwMxrQ8xmERAFWkMmNmKLquWcdF143ns4XjZ2P9zd",
"mint_pks": [
"C6kYXcaRUMqeBF5fhg165RWU7AnpT9z92fvKNoMqjmz6",
"8p968u9m7jZzKSsqxFDqki69MjqdFkwPM9FN4AN8hvHR",
"So11111111111111111111111111111111111111112",
"9FbAMDvXqNjPqZSYt4EWTguJuDrGkfvwr3gSFpiSbX9S",
"7KBVenLz5WNH4PA5MdGkJNpDDyNKnBQTwnz1UqJv9GUm"
],
"oracle_pks": [
"6Xvk6VC423bbhwnCfMyPfE4C1vytoqsVMUY1Lbqeh6pf",
"4CoKvk3NUXYiHKGbQvihadw6TC8LTN1qjfadPcsaURbW",
"Ej5FrNjhXaePK7cVMZtSooatzXMeunNsjxrnubefEyNC",
"GR9tYpi8CM8u8sdRaJZoP32KoWBphyoWV3xoNt4XwmRV"
],
"spot_market_pks": [
"6Cpt7EYmzUcHLBQzZuYNqnyKQKieofZXw6bpCWtmwZM1",
"4UQq7c8FdwGkb2TghHVgJShHMJwS4YzjvA3yiF6zArJD",
"8RJA4WhY2Ei48c4xANSgPoqw7DU7mRgvg6eqJS3tvLEN",
"CRLpSnSf7JkoJi9tUnz55R2FoTCrDDkWxQMU6uSVBQgc"
],
"spot_market_symbols": {
"BTC/USDT": "6Cpt7EYmzUcHLBQzZuYNqnyKQKieofZXw6bpCWtmwZM1",
"ETH/USDT": "4UQq7c8FdwGkb2TghHVgJShHMJwS4YzjvA3yiF6zArJD",
"SOL/USDT": "8RJA4WhY2Ei48c4xANSgPoqw7DU7mRgvg6eqJS3tvLEN",
"SRM/USDT": "CRLpSnSf7JkoJi9tUnz55R2FoTCrDDkWxQMU6uSVBQgc"
},
"srm_vault_pk": "9rwmye1qcsD2txMxG7ZKdyJwyTyHXPwkB7y4K1vX9LXZ",
"symbols": {
"BTC": "C6kYXcaRUMqeBF5fhg165RWU7AnpT9z92fvKNoMqjmz6",
"ETH": "8p968u9m7jZzKSsqxFDqki69MjqdFkwPM9FN4AN8hvHR",
"SOL": "So11111111111111111111111111111111111111112",
"SRM": "9FbAMDvXqNjPqZSYt4EWTguJuDrGkfvwr3gSFpiSbX9S",
"USDT": "7KBVenLz5WNH4PA5MdGkJNpDDyNKnBQTwnz1UqJv9GUm"
},
"vault_pks": [
"9pTjFBB3xheuqR9iDG63x2TLZjeb6f3yCBZE6EjYtqV3",
"7HA5Ne1g2t8cRvzEYdoMwGJch1AneMQLiJRJccm1tw9y",
"CGj8exjKg88byyjRCEuYGB5CXvAqB1YzHEHrDiUFLwYK",
"ApX38vWvRybQHKoj6AsQHQDa7gQPChYkNHgqAj2kDxDo",
"CbcaxuYfe53NTX5eRUaRzxGyRyMLTt7JT6p2p6VZVnh7"
]
},
"BTC_ETH_USDT": {
"mango_group_pk": "7pVYhpKUHw88neQHxgExSH6cerMZ1Axx1ALQP9sxtvQV",
"mint_pks": [

View File

@ -7,7 +7,14 @@ import pandas as pd
import notebookimporter # noqa: F401
pd.options.display.float_format = '{:,.8f}'.format
decimal.getcontext().prec = 18
# Increased precision from 18 to 36 because for a decimal like:
# val = Decimal("17436036573.2030800")
#
# The following rounding operations would both throw decimal.InvalidOperation:
# val.quantize(Decimal('.000000001'))
# round(val, 9)
decimal.getcontext().prec = 36
_log_levels = {
logging.CRITICAL: "🛑",