Initial import.
This commit is contained in:
commit
2cc5135517
|
@ -0,0 +1,2 @@
|
|||
.git
|
||||
wallet.json
|
|
@ -0,0 +1,4 @@
|
|||
CURRENT_DIRECTORY="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
export PATH=$PATH:$CURRENT_DIRECTORY/bin
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# Mac OS X files
|
||||
.DS_Store
|
||||
|
||||
# Don't store any data in git
|
||||
data/
|
||||
|
||||
# Ignore tmp/ directories
|
||||
tmp/
|
||||
|
||||
# Ignore any Python autogenerated nonsense.
|
||||
.ipynb_checkpoints
|
||||
__pycache__
|
||||
.mypy_cache
|
||||
|
||||
# Don't check in anything that might have credentials.
|
||||
configuration.json
|
||||
wallet.json
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,177 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "periodic-education",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 🥭 Constants\n",
|
||||
"\n",
|
||||
"Some hard-coded values, all kept in one place."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "recreational-fellowship",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import decimal\n",
|
||||
"import json\n",
|
||||
"\n",
|
||||
"from solana.publickey import PublicKey\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "marine-skiing",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## SYSTEM_PROGRAM_ADDRESS\n",
|
||||
"\n",
|
||||
"The Solana system program address is always 11111111111111111111111111111111."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "matched-variance",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"SYSTEM_PROGRAM_ADDRESS = PublicKey(\"11111111111111111111111111111111\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "champion-advance",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## SOL_DECIMAL_DIVISOR decimal\n",
|
||||
"\n",
|
||||
"The divisor to use to turn an integer value of SOLs from an account's `balance` into a value with the correct number of decimal places."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "eastern-broadway",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"SOL_DECIMAL_DIVISOR = decimal.Decimal(1000000000)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "completed-saturn",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## NUM_TOKENS\n",
|
||||
"\n",
|
||||
"This is currently hard-coded to 3."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "rocky-adjustment",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"NUM_TOKENS = 3\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "suburban-negative",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## NUM_MARKETS\n",
|
||||
"\n",
|
||||
"There is one fewer market than tokens."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "bound-handling",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"NUM_MARKETS = NUM_TOKENS - 1\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "tough-difference",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## MangoConstants\n",
|
||||
"\n",
|
||||
"Load all Mango Market's constants from its own `ids.json` file (retrieved from [GitHub](https://raw.githubusercontent.com/blockworks-foundation/mango-client-ts/main/src/ids.json)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "blank-swaziland",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"with open(\"ids.json\") as json_file:\n",
|
||||
" MangoConstants = json.load(json_file)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "precious-climate",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Running\n",
|
||||
"\n",
|
||||
"Just try to access some things and print them out to make sure we have loaded properly."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "forward-google",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if __name__ == \"__main__\":\n",
|
||||
" print(\"System program address:\", SYSTEM_PROGRAM_ADDRESS)\n",
|
||||
" print(\"SOL decimal divisor:\", SOL_DECIMAL_DIVISOR)\n",
|
||||
" print(\"Number of tokens:\", NUM_TOKENS)\n",
|
||||
" print(\"Number of markets:\", NUM_MARKETS)\n",
|
||||
" mango_group = MangoConstants[\"mainnet-beta\"]\n",
|
||||
" print(f\"Mango program ID: {mango_group['mango_program_id']}\")\n",
|
||||
" for oracle in mango_group[\"oracles\"]:\n",
|
||||
" print(f\"Oracle [{oracle}]: {mango_group['oracles'][oracle]}\")\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"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ceramic-logic",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 🥭 Context\n",
|
||||
"\n",
|
||||
"A `Context` object to manage Solana connection configuration and Mango groups."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "roman-arrival",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import typing\n",
|
||||
"\n",
|
||||
"from decimal import Decimal\n",
|
||||
"from solana.publickey import PublicKey\n",
|
||||
"from solana.rpc.api import Client\n",
|
||||
"from solana.rpc.types import MemcmpOpts, TokenAccountOpts\n",
|
||||
"from solana.rpc.commitment import Single\n",
|
||||
"\n",
|
||||
"from Constants import MangoConstants, SOL_DECIMAL_DIVISOR\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "educational-investment",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class Context:\n",
|
||||
" def __init__(self, cluster: str, cluster_url: str, program_id: PublicKey, dex_program_id: PublicKey,\n",
|
||||
" group_name: str, group_id: PublicKey):\n",
|
||||
" self.cluster: str = cluster\n",
|
||||
" self.cluster_url: str = cluster_url\n",
|
||||
" self.client: Client = Client(cluster_url)\n",
|
||||
" self.program_id: PublicKey = program_id\n",
|
||||
" self.dex_program_id: PublicKey = dex_program_id\n",
|
||||
" self.group_name: str = group_name\n",
|
||||
" self.group_id: PublicKey = group_id\n",
|
||||
" self.commitment = Single\n",
|
||||
" self.encoding:str = \"base64\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
" def fetch_sol_balance(self, account_public_key: PublicKey):\n",
|
||||
" result = self.client.get_balance(account_public_key, commitment=self.commitment)\n",
|
||||
" value = Decimal(result[\"result\"][\"value\"])\n",
|
||||
" return value / SOL_DECIMAL_DIVISOR\n",
|
||||
"\n",
|
||||
"\n",
|
||||
" def fetch_token_balance(self, account_public_key: PublicKey, token_public_key: PublicKey):\n",
|
||||
" opts = TokenAccountOpts(mint = token_public_key)\n",
|
||||
"\n",
|
||||
" token_account = self.client.get_token_accounts_by_owner(account_public_key, opts, commitment=self.commitment)\n",
|
||||
" result = self.client.get_token_account_balance(token_account[\"result\"][\"value\"][0][\"pubkey\"], commitment=self.commitment)\n",
|
||||
" value = Decimal(result[\"result\"][\"value\"][\"amount\"])\n",
|
||||
" decimal_places = result[\"result\"][\"value\"][\"decimals\"]\n",
|
||||
" divisor = Decimal(10 ** decimal_places)\n",
|
||||
" return value / divisor\n",
|
||||
"\n",
|
||||
"\n",
|
||||
" # This should be easier to call and should probably be on the Client object, but we don't\n",
|
||||
" # control that.\n",
|
||||
" def fetch_program_accounts_for_owner(self, program_id: PublicKey, owner: PublicKey):\n",
|
||||
" memcmp_opts = [\n",
|
||||
" MemcmpOpts(offset=40, bytes=str(owner)),\n",
|
||||
" ]\n",
|
||||
"\n",
|
||||
" return self.client.get_program_accounts(program_id, memcmp_opts=memcmp_opts, commitment=self.commitment, encoding=self.encoding)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
" @staticmethod\n",
|
||||
" def _lookup_name_by_address(address: PublicKey, collection: typing.Dict[str, str]) -> typing.Optional[str]:\n",
|
||||
" address_string = str(address)\n",
|
||||
" for stored_name, stored_address in collection.items():\n",
|
||||
" if address_string == stored_address:\n",
|
||||
" return stored_name\n",
|
||||
" return None\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",
|
||||
"\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) -> str:\n",
|
||||
" return Context._lookup_name_by_address(token_address, MangoConstants[self.cluster][\"symbols\"]) or \"« Unknown Token »\"\n",
|
||||
"\n",
|
||||
" def __str__(self) -> str:\n",
|
||||
" return f\"\"\"« Context:\n",
|
||||
" Cluster: {self.cluster}\n",
|
||||
" Cluster URL: {self.cluster_url}\n",
|
||||
" Program ID: {self.program_id}\n",
|
||||
" DEX Program ID: {self.dex_program_id}\n",
|
||||
" Group Name: {self.group_name}\n",
|
||||
" Group ID: {self.group_id}\n",
|
||||
"\"\"\"\n",
|
||||
"\n",
|
||||
" def __repr__(self) -> str:\n",
|
||||
" return f\"{self}\"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "pointed-minimum",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# default_context object\n",
|
||||
"\n",
|
||||
"A default `Context` object that connects to mainnet, to save having to create one all over the place."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "advisory-workstation",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"default_cluster = os.environ.get(\"CLUSTER\") or 'mainnet-beta'\n",
|
||||
"default_cluster_url = os.environ.get(\"CLUSTER_URL\") or MangoConstants[\"cluster_urls\"][default_cluster]\n",
|
||||
"\n",
|
||||
"default_program_id = PublicKey(MangoConstants[default_cluster][\"mango_program_id\"])\n",
|
||||
"default_dex_program_id = PublicKey(MangoConstants[default_cluster][\"dex_program_id\"])\n",
|
||||
"\n",
|
||||
"default_group_name = os.environ.get(\"GROUP_NAME\") or 'BTC_ETH_USDT'\n",
|
||||
"default_group_id = PublicKey(MangoConstants[default_cluster][\"mango_groups\"][default_group_name][\"mango_group_pk\"])\n",
|
||||
"\n",
|
||||
"default_context = Context(default_cluster, default_cluster_url, default_program_id,\n",
|
||||
" default_dex_program_id, default_group_name, default_group_id)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "disciplinary-shield",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Running\n",
|
||||
"\n",
|
||||
"If running interactively, just print out the default Context object."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "welcome-hearing",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if __name__ == \"__main__\":\n",
|
||||
" print(default_context)\n",
|
||||
"\n",
|
||||
" print(\"Lookup ETH token name result:\", default_context.lookup_token_name(PublicKey(\"2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk\")))\n",
|
||||
" print(\"Lookup BTC/USDC market name result:\", default_context.lookup_market_name(PublicKey(\"CVfYa8RGXnuDBeGmniCcdkBwoLqVxh92xB1JqgRQx3F\")))\n",
|
||||
" # Fill out your account address between the quotes below\n",
|
||||
" MY_ACCOUNT_ADDRESS = \"\"\n",
|
||||
" # Don't edit anything beyond here.\n",
|
||||
"\n",
|
||||
" if MY_ACCOUNT_ADDRESS != \"\":\n",
|
||||
" account_key = PublicKey(MY_ACCOUNT_ADDRESS)\n",
|
||||
" print(\"SOL balance:\", default_context.fetch_sol_balance(account_key))\n",
|
||||
" print(\"SRM balance:\", default_context.fetch_token_balance(account_key, PublicKey(\"SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt\")))\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"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "helpful-command",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 🥭 Decoder\n",
|
||||
"\n",
|
||||
"Some useful functions for decoding base64 and base58 data."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "likely-gender",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import base64\n",
|
||||
"import base58\n",
|
||||
"import typing\n",
|
||||
"\n",
|
||||
"from solana.publickey import PublicKey\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ruled-percentage",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## decode_binary() function\n",
|
||||
"\n",
|
||||
"A Solana binary data structure may come back as an array with the base64 or base58 encoded data, and a text moniker saying which encoding was used.\n",
|
||||
"\n",
|
||||
"For example:\n",
|
||||
"```\n",
|
||||
"['AwAAAAAAAACCaOmpoURMK6XHelGTaFawcuQ/78/15LAemWI8jrt3SRKLy2R9i60eclDjuDS8+p/ZhvTUd9G7uQVOYCsR6+BhmqGCiO6EPYP2PQkf/VRTvw7JjXvIjPFJy06QR1Cq1WfTonHl0OjCkyEf60SD07+MFJu5pVWNFGGEO/8AiAYfduaKdnFTaZEHPcK5Eq72WWHeHg2yIbBF09kyeOhlCJwOoG8O5SgpPV8QOA64ZNV4aKroFfADg6kEy/wWCdp3fv2B8WJgAAAAANVfH3HGtjwAAQAAAAAAAADr8cwFi9UOAAEAAAAAAAAAgfFiYAAAAABo3Dbz0L0oAAEAAAAAAAAAr8K+TvCjCwABAAAAAAAAAIHxYmAAAAAA49t5tVNZhwABAAAAAAAAAAmPtcB1zC8AAQAAAAAAAABIBGiCcyaEZdNhrTyeqUY692vOzzPdHaxAxguht3JQGlkzjtd05dX9LENHkl2z1XvUbTNKZlweypNRetmH0lmQ9VYQAHqylxZVK65gEg85g27YuSyvOBZAjJyRmYU9KdCO1D+4ehdPu9dQB1yI1uh75wShdAaFn2o4qrMYwq3SQQEAAAAAAAAAAiH1PPJKAuh6oGiE35aGhUQhFi/bxgKOudpFv8HEHNCFDy1uAqR6+CTQmradxC1wyyjL+iSft+5XudJWwSdi72NJGmyK96x7Obj/AgAAAAB8RjOEdJow6r9LMhIAAAAAGkNK4CXHh5M2st7PnwAAAE33lx1h8hPFD04AAAAAAAA8YRV3Oa309B2wGwAAAAAAOIlOLmkr6+r605n+AQAAAACgmZmZmZkZAQAAAAAAAAAAMDMzMzMzMwEAAAAAAAAA25D1XcAtRzSuuyx3U+X7aE9vM1EJySU9KprgL0LMJ/vat9+SEEUZuga7O5tTUrcMDYWDg+LYaAWhSQiN2fYk7aCGAQAAAAAAgIQeAAAAAAAA8gUqAQAAAAYGBgICAAAA', 'base64']\n",
|
||||
"```\n",
|
||||
"Alternatively, it may just be a base58-encoded string.\n",
|
||||
"\n",
|
||||
"`decode_binary()` decodes the data properly based on which encoding was used."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "available-latvia",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def decode_binary(encoded: typing.List) -> bytes:\n",
|
||||
" if isinstance(encoded, str):\n",
|
||||
" return base58.b58decode(encoded)\n",
|
||||
" elif encoded[1] == \"base64\":\n",
|
||||
" return base64.b64decode(encoded[0])\n",
|
||||
" else:\n",
|
||||
" return base58.b58decode(encoded[0])\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "integrated-appendix",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## encode_binary() function\n",
|
||||
"\n",
|
||||
"Inverse of `decode_binary()`, this takes a binary list and encodes it (using base 64), then returns the encoded string and the string \"base64\" in an array.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "seven-buying",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def encode_binary(decoded: bytes) -> typing.List:\n",
|
||||
" return [base64.b64encode(decoded), \"base64\"]\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "automotive-eating",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## encode_key() function\n",
|
||||
"\n",
|
||||
"Encodes a `PublicKey` in the proper way for RPC calls."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "minute-titanium",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def encode_key(key: PublicKey) -> str:\n",
|
||||
" return str(key)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "above-graduate",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Running\n",
|
||||
"\n",
|
||||
"A simple harness to run the above code.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "deadly-norwegian",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if __name__ == \"__main__\":\n",
|
||||
" data = decode_binary(['AwAAAAAAAACCaOmpoURMK6XHelGTaFawcuQ/78/15LAemWI8jrt3SRKLy2R9i60eclDjuDS8+p/ZhvTUd9G7uQVOYCsR6+BhmqGCiO6EPYP2PQkf/VRTvw7JjXvIjPFJy06QR1Cq1WfTonHl0OjCkyEf60SD07+MFJu5pVWNFGGEO/8AiAYfduaKdnFTaZEHPcK5Eq72WWHeHg2yIbBF09kyeOhlCJwOoG8O5SgpPV8QOA64ZNV4aKroFfADg6kEy/wWCdp3fv2B8WJgAAAAANVfH3HGtjwAAQAAAAAAAADr8cwFi9UOAAEAAAAAAAAAgfFiYAAAAABo3Dbz0L0oAAEAAAAAAAAAr8K+TvCjCwABAAAAAAAAAIHxYmAAAAAA49t5tVNZhwABAAAAAAAAAAmPtcB1zC8AAQAAAAAAAABIBGiCcyaEZdNhrTyeqUY692vOzzPdHaxAxguht3JQGlkzjtd05dX9LENHkl2z1XvUbTNKZlweypNRetmH0lmQ9VYQAHqylxZVK65gEg85g27YuSyvOBZAjJyRmYU9KdCO1D+4ehdPu9dQB1yI1uh75wShdAaFn2o4qrMYwq3SQQEAAAAAAAAAAiH1PPJKAuh6oGiE35aGhUQhFi/bxgKOudpFv8HEHNCFDy1uAqR6+CTQmradxC1wyyjL+iSft+5XudJWwSdi72NJGmyK96x7Obj/AgAAAAB8RjOEdJow6r9LMhIAAAAAGkNK4CXHh5M2st7PnwAAAE33lx1h8hPFD04AAAAAAAA8YRV3Oa309B2wGwAAAAAAOIlOLmkr6+r605n+AQAAAACgmZmZmZkZAQAAAAAAAAAAMDMzMzMzMwEAAAAAAAAA25D1XcAtRzSuuyx3U+X7aE9vM1EJySU9KprgL0LMJ/vat9+SEEUZuga7O5tTUrcMDYWDg+LYaAWhSQiN2fYk7aCGAQAAAAAAgIQeAAAAAAAA8gUqAQAAAAYGBgICAAAA', 'base64'])\n",
|
||||
" print(f\"Data length (should be 744): {len(data)}\")\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"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
FROM jupyter/scipy-notebook:latest
|
||||
|
||||
COPY requirements.txt /tmp/
|
||||
RUN pip install --requirement /tmp/requirements.txt && \
|
||||
fix-permissions $CONDA_DIR && \
|
||||
fix-permissions /home/$NB_USER
|
||||
|
||||
# Create our profile directory.
|
||||
RUN ipython profile create
|
||||
|
||||
# Copy across our magic/startup scripts.
|
||||
COPY meta/startup /home/jovyan/.ipython/profile_default/startup
|
||||
|
||||
ENV PATH="/home/jovyan/work/bin:${PATH}"
|
||||
ADD . /home/jovyan/work
|
||||
|
||||
WORKDIR /home/jovyan/work
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Geoff Taylor
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
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.
|
|
@ -0,0 +1,687 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "tight-particular",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 🥭 Layouts\n",
|
||||
"\n",
|
||||
"Structure layouts to load the sometimes-opaque data blobs from Solana accounts.\n",
|
||||
"\n",
|
||||
"The idea is to have one data-encapsulating class (in [Classes](Classes.ipynb)) for each type, as well as one or more LAYOUT structures. So for example `Group` loads `GROUP` but in future will also be able to load `GROUP_V2`.\n",
|
||||
"\n",
|
||||
"The general approach is:\n",
|
||||
"* Define one (or more) layouts to read in the data blob\n",
|
||||
"* Use the data from the data blob to construct a more useful strongly-typed object\n",
|
||||
"\n",
|
||||
"So for example `GROUP` is defined below but it's a low-level dependency. In general, code should depend and work with the `Group` class, not the `GROUP` structure.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "featured-exploration",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import construct\n",
|
||||
"import datetime\n",
|
||||
"\n",
|
||||
"from decimal import Decimal\n",
|
||||
"from solana.publickey import PublicKey\n",
|
||||
"\n",
|
||||
"from Constants import NUM_MARKETS, NUM_TOKENS\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "painted-citizenship",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Adapters\n",
|
||||
"\n",
|
||||
"These are adapters for the construct package to simplify our struct declarations."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "fuzzy-cambridge",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## DecimalAdapter class\n",
|
||||
"\n",
|
||||
"A simple construct `Adapter` that lets us use `Decimal`s directly in our structs."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "pretty-twenty",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class DecimalAdapter(construct.Adapter):\n",
|
||||
" def __init__(self, size: int = 8):\n",
|
||||
" construct.Adapter.__init__(self, construct.BytesInteger(size, swapped=True))\n",
|
||||
"\n",
|
||||
" def _decode(self, obj, context, path) -> Decimal:\n",
|
||||
" return Decimal(obj)\n",
|
||||
"\n",
|
||||
" def _encode(self, obj, context, path) -> bytes:\n",
|
||||
" return bytes(obj)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "alternate-disposition",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Float64Adapter class\n",
|
||||
"\n",
|
||||
"Some numbers are packaged as 16-bytes to represent a `float`. The way to get the `float` is to take the 16-byte int value and divide it by 2 to the power 64. In Javascript this would be:\n",
|
||||
"```\n",
|
||||
"return intValue / Math.pow(2, 64);\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"This is a simple construct `Adapter` that lets us use these float values directly in our structs."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "orange-router",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class Float64Adapter(construct.Adapter):\n",
|
||||
" _divisor = Decimal(2 ** 64)\n",
|
||||
"\n",
|
||||
" def __init__(self, size: int = 16):\n",
|
||||
" construct.Adapter.__init__(self, construct.BytesInteger(size, swapped=True))\n",
|
||||
"\n",
|
||||
" def _decode(self, obj, context, path) -> Decimal:\n",
|
||||
" return Decimal(obj) / Float64Adapter._divisor\n",
|
||||
"\n",
|
||||
" def _encode(self, obj, context, path) -> bytes:\n",
|
||||
" return bytes(obj)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "liberal-trout",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## PublicKeyAdapter\n",
|
||||
"\n",
|
||||
"A simple construct `Adapter` that lets us use `PublicKey`s directly in our structs."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "tested-shame",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class PublicKeyAdapter(construct.Adapter):\n",
|
||||
" def __init__(self):\n",
|
||||
" construct.Adapter.__init__(self, construct.Bytes(32))\n",
|
||||
"\n",
|
||||
" def _decode(self, obj, context, path) -> PublicKey:\n",
|
||||
" return PublicKey(obj)\n",
|
||||
"\n",
|
||||
" def _encode(self, obj, context, path) -> bytes:\n",
|
||||
" return bytes(obj)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "superb-consumer",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## DatetimeAdapter\n",
|
||||
"\n",
|
||||
"A simple construct `Adapter` that lets us load `datetime`s directly in our structs."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "express-duration",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class DatetimeAdapter(construct.Adapter):\n",
|
||||
" def __init__(self):\n",
|
||||
" construct.Adapter.__init__(self, construct.BytesInteger(8, swapped=True))\n",
|
||||
"\n",
|
||||
" def _decode(self, obj, context, path) -> datetime.datetime:\n",
|
||||
" return datetime.datetime.fromtimestamp(obj)\n",
|
||||
"\n",
|
||||
" def _encode(self, obj, context, path) -> bytes:\n",
|
||||
" return bytes(obj)\n",
|
||||
"\n",
|
||||
" "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "several-child",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# SERUM_ACCOUNT_FLAGS\n",
|
||||
"\n",
|
||||
"The SERUM_ prefix is because there's also `MANGO_ACCOUNT_FLAGS`.\n",
|
||||
"\n",
|
||||
"Here's the [Serum Rust structure](https://github.com/project-serum/serum-dex/blob/master/dex/src/state.rs):\n",
|
||||
"```\n",
|
||||
"#[derive(Copy, Clone, BitFlags, Debug, Eq, PartialEq)]\n",
|
||||
"#[repr(u64)]\n",
|
||||
"pub enum AccountFlag {\n",
|
||||
" Initialized = 1u64 << 0,\n",
|
||||
" Market = 1u64 << 1,\n",
|
||||
" OpenOrders = 1u64 << 2,\n",
|
||||
" RequestQueue = 1u64 << 3,\n",
|
||||
" EventQueue = 1u64 << 4,\n",
|
||||
" Bids = 1u64 << 5,\n",
|
||||
" Asks = 1u64 << 6,\n",
|
||||
" Disabled = 1u64 << 7,\n",
|
||||
"}\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "indie-surgeon",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"SERUM_ACCOUNT_FLAGS = construct.BitsSwapped(\n",
|
||||
" construct.BitStruct(\n",
|
||||
" \"initialized\" / construct.Flag,\n",
|
||||
" \"market\" / construct.Flag,\n",
|
||||
" \"open_orders\" / construct.Flag,\n",
|
||||
" \"request_queue\" / construct.Flag,\n",
|
||||
" \"event_queue\" / construct.Flag,\n",
|
||||
" \"bids\" / construct.Flag,\n",
|
||||
" \"asks\" / construct.Flag,\n",
|
||||
" \"disabled\" / construct.Flag,\n",
|
||||
" construct.Padding(7 * 8)\n",
|
||||
" )\n",
|
||||
")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "acquired-republic",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# MANGO_ACCOUNT_FLAGS\n",
|
||||
"\n",
|
||||
"The MANGO_ prefix is because there's also `SERUM_ACCOUNT_FLAGS`.\n",
|
||||
"\n",
|
||||
"The MANGO_ACCOUNT_FLAGS should be exactly 8 bytes.\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, BitFlags, Debug, Eq, PartialEq)]\n",
|
||||
"#[repr(u64)]\n",
|
||||
"pub enum AccountFlag {\n",
|
||||
" Initialized = 1u64 << 0,\n",
|
||||
" MangoGroup = 1u64 << 1,\n",
|
||||
" MarginAccount = 1u64 << 2,\n",
|
||||
" MangoSrmAccount = 1u64 << 3\n",
|
||||
"}\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "curious-thursday",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"MANGO_ACCOUNT_FLAGS = construct.BitsSwapped(\n",
|
||||
" construct.BitStruct(\n",
|
||||
" \"initialized\" / construct.Flag,\n",
|
||||
" \"group\" / construct.Flag,\n",
|
||||
" \"margin_account\" / construct.Flag,\n",
|
||||
" \"srm_account\" / construct.Flag,\n",
|
||||
" construct.Padding(4 + (7 * 8))\n",
|
||||
" )\n",
|
||||
")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "saved-conference",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# INDEX\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 MangoIndex {\n",
|
||||
" pub last_update: u64,\n",
|
||||
" pub borrow: U64F64,\n",
|
||||
" pub deposit: U64F64\n",
|
||||
"}\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "acting-annotation",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"INDEX = construct.Struct(\n",
|
||||
" \"last_update\" / DatetimeAdapter(),\n",
|
||||
" \"borrow\" / Float64Adapter(),\n",
|
||||
" \"deposit\" / Float64Adapter()\n",
|
||||
");\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "crazy-market",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# AGGREGATOR_CONFIG\n",
|
||||
"\n",
|
||||
"Here's the [Flux Rust structure](https://github.com/blockworks-foundation/solana-flux-aggregator/blob/master/program/src/state.rs):\n",
|
||||
"```\n",
|
||||
"#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Default, PartialEq)]\n",
|
||||
"pub struct AggregatorConfig {\n",
|
||||
" /// description\n",
|
||||
" pub description: [u8; 32],\n",
|
||||
"\n",
|
||||
" /// decimals for this feed\n",
|
||||
" pub decimals: u8,\n",
|
||||
"\n",
|
||||
" /// oracle cannot start a new round until after `restart_relay` rounds\n",
|
||||
" pub restart_delay: u8,\n",
|
||||
"\n",
|
||||
" /// max number of submissions in a round\n",
|
||||
" pub max_submissions: u8,\n",
|
||||
"\n",
|
||||
" /// min number of submissions in a round to resolve an answer\n",
|
||||
" pub min_submissions: u8,\n",
|
||||
"\n",
|
||||
" /// amount of tokens oracles are reward per submission\n",
|
||||
" pub reward_amount: u64,\n",
|
||||
"\n",
|
||||
" /// SPL token account from which to withdraw rewards\n",
|
||||
" pub reward_token_account: PublicKey,\n",
|
||||
"}\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "atomic-senator",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"AGGREGATOR_CONFIG = construct.Struct(\n",
|
||||
" \"description\" / construct.PaddedString(32, \"utf8\"),\n",
|
||||
" \"decimals\" / DecimalAdapter(1),\n",
|
||||
" \"restart_delay\" / DecimalAdapter(1),\n",
|
||||
" \"max_submissions\" / DecimalAdapter(1),\n",
|
||||
" \"min_submissions\" / DecimalAdapter(1),\n",
|
||||
" \"reward_amount\" / DecimalAdapter(),\n",
|
||||
" \"reward_token_account\" / PublicKeyAdapter()\n",
|
||||
");\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "utility-accountability",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# ROUND\n",
|
||||
"\n",
|
||||
"Here's the [Flux Rust structure](https://github.com/blockworks-foundation/solana-flux-aggregator/blob/master/program/src/state.rs):\n",
|
||||
"```\n",
|
||||
"#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Default, PartialEq)]\n",
|
||||
"pub struct Round {\n",
|
||||
" pub id: u64,\n",
|
||||
" pub created_at: u64,\n",
|
||||
" pub updated_at: u64,\n",
|
||||
"}\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "unlike-passage",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ROUND = construct.Struct(\n",
|
||||
" \"id\" / DecimalAdapter(),\n",
|
||||
" \"created_at\" / DecimalAdapter(),\n",
|
||||
" \"updated_at\" / DecimalAdapter()\n",
|
||||
");\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "essential-steal",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# ANSWER\n",
|
||||
"\n",
|
||||
"Here's the [Flux Rust structure](https://github.com/blockworks-foundation/solana-flux-aggregator/blob/master/program/src/state.rs):\n",
|
||||
"```\n",
|
||||
"#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Default, PartialEq)]\n",
|
||||
"pub struct Answer {\n",
|
||||
" pub round_id: u64,\n",
|
||||
" pub median: u64,\n",
|
||||
" pub created_at: u64,\n",
|
||||
" pub updated_at: u64,\n",
|
||||
"}\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "similar-result",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ANSWER = construct.Struct(\n",
|
||||
" \"round_id\" / DecimalAdapter(),\n",
|
||||
" \"median\" / DecimalAdapter(),\n",
|
||||
" \"created_at\" / DatetimeAdapter(),\n",
|
||||
" \"updated_at\" / DatetimeAdapter()\n",
|
||||
");\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "macro-invention",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# AGGREGATOR\n",
|
||||
"\n",
|
||||
"Here's the [Flux Rust structure](https://github.com/blockworks-foundation/solana-flux-aggregator/blob/master/program/src/state.rs):\n",
|
||||
"```\n",
|
||||
"#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Default, PartialEq)]\n",
|
||||
"pub struct Aggregator {\n",
|
||||
" pub config: AggregatorConfig,\n",
|
||||
" /// is initialized\n",
|
||||
" pub is_initialized: bool,\n",
|
||||
" /// authority\n",
|
||||
" pub owner: PublicKey,\n",
|
||||
" /// current round accepting oracle submissions\n",
|
||||
" pub round: Round,\n",
|
||||
" pub round_submissions: PublicKey, // has_one: Submissions\n",
|
||||
" /// the latest answer resolved\n",
|
||||
" pub answer: Answer,\n",
|
||||
" pub answer_submissions: PublicKey, // has_one: Submissions\n",
|
||||
"}\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "delayed-malpractice",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"AGGREGATOR = construct.Struct(\n",
|
||||
" \"config\" / AGGREGATOR_CONFIG,\n",
|
||||
" \"initialized\" / DecimalAdapter(1),\n",
|
||||
" \"owner\" / PublicKeyAdapter(),\n",
|
||||
" \"round\" / ROUND,\n",
|
||||
" \"round_submissions\" / PublicKeyAdapter(),\n",
|
||||
" \"answer\" / ANSWER,\n",
|
||||
" \"answer_submissions\" / PublicKeyAdapter()\n",
|
||||
");\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "protecting-finland",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# GROUP\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",
|
||||
" 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",
|
||||
"impl_loadable!(MangoGr\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "alpha-telescope",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"GROUP_PADDING = 8 - (NUM_TOKENS + NUM_MARKETS) % 8\n",
|
||||
"\n",
|
||||
"GROUP = 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",
|
||||
" \"signer_nonce\" / DecimalAdapter(),\n",
|
||||
" \"signer_key\" / PublicKeyAdapter(),\n",
|
||||
" \"dex_program_id\" / PublicKeyAdapter(),\n",
|
||||
" \"total_deposits\" / construct.Array(NUM_TOKENS, Float64Adapter()),\n",
|
||||
" \"total_borrows\" / construct.Array(NUM_TOKENS, Float64Adapter()),\n",
|
||||
" \"maint_coll_ratio\" / Float64Adapter(),\n",
|
||||
" \"init_coll_ratio\" / Float64Adapter(),\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"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "expired-japan",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# TOKEN_ACCOUNT"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "cheap-birthday",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"TOKEN_ACCOUNT = construct.Struct(\n",
|
||||
" \"mint\" / PublicKeyAdapter(),\n",
|
||||
" \"owner\" / PublicKeyAdapter(),\n",
|
||||
" \"amount\" / DecimalAdapter(),\n",
|
||||
" \"padding\" / construct.Padding(93)\n",
|
||||
");\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "induced-latitude",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# OPEN_ORDERS\n",
|
||||
"\n",
|
||||
"Trying to use the `OPEN_ORDERS_LAYOUT` and `OpenOrdersAccount` from `pyserum` just proved too probelmatic. (`OpenOrdersAccount` doesn't expose `referrer_rebate_accrued`, for instance.)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "animal-definition",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"OPEN_ORDERS = construct.Struct(\n",
|
||||
" construct.Padding(5),\n",
|
||||
" \"account_flags\" / SERUM_ACCOUNT_FLAGS,\n",
|
||||
" \"market\" / PublicKeyAdapter(),\n",
|
||||
" \"owner\" / PublicKeyAdapter(),\n",
|
||||
" \"base_token_free\" / DecimalAdapter(),\n",
|
||||
" \"base_token_total\" / DecimalAdapter(),\n",
|
||||
" \"quote_token_free\" / DecimalAdapter(),\n",
|
||||
" \"quote_token_total\" / DecimalAdapter(),\n",
|
||||
" \"free_slot_bits\" / DecimalAdapter(16),\n",
|
||||
" \"is_bid_bits\" / DecimalAdapter(16),\n",
|
||||
" \"orders\" / construct.Array(128, DecimalAdapter(16)),\n",
|
||||
" \"client_ids\" / construct.Array(128, DecimalAdapter()),\n",
|
||||
" \"referrer_rebate_accrued\" / DecimalAdapter(),\n",
|
||||
" \"padding\" / construct.Padding(7)\n",
|
||||
");\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "mature-banana",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# MARGIN_ACCOUNT\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 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 padding: [u8; 7] // padding to make compatible with previous MarginAccount size\n",
|
||||
" // TODO add has_borrows field for easy memcmp fetching\n",
|
||||
"}\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "lucky-wealth",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"MARGIN_ACCOUNT = construct.Struct(\n",
|
||||
" \"account_flags\" / MANGO_ACCOUNT_FLAGS,\n",
|
||||
" \"mango_group\" / PublicKeyAdapter(),\n",
|
||||
" \"owner\" / PublicKeyAdapter(),\n",
|
||||
" \"deposits\" / construct.Array(NUM_TOKENS, Float64Adapter()),\n",
|
||||
" \"borrows\" / construct.Array(NUM_TOKENS, Float64Adapter()),\n",
|
||||
" \"open_orders\" / construct.Array(NUM_MARKETS, PublicKeyAdapter()),\n",
|
||||
" \"padding\" / construct.Padding(8)\n",
|
||||
");\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "clinical-subject",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Running"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "soviet-information",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if __name__ == \"__main__\":\n",
|
||||
" import base64\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",
|
||||
"\n",
|
||||
" group = GROUP.parse(decoded)\n",
|
||||
" print(\"\\n\\nThis is hard-coded, not live information!\")\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-showcode": false
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "boring-principle",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 🥭 Mango + Pandas 🐼🐼"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "expressed-creek",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pandas as pd\n",
|
||||
"import time\n",
|
||||
"\n",
|
||||
"from Context import default_context\n",
|
||||
"from Classes import Group, MarginAccount, OpenOrders\n",
|
||||
"\n",
|
||||
"start_time = time.time()\n",
|
||||
"\n",
|
||||
"print(\"Loading group...\")\n",
|
||||
"group = Group.load(default_context)\n",
|
||||
"print(f\"Done. Time taken: {time.time() - start_time}\")\n",
|
||||
"\n",
|
||||
"print(\"Loading prices...\")\n",
|
||||
"prices = group.get_prices()\n",
|
||||
"print(f\"Done. Time taken: {time.time() - start_time}\")\n",
|
||||
"\n",
|
||||
"print(\"Loading margin accounts...\")\n",
|
||||
"margin_accounts = MarginAccount.load_all_for_group(default_context, default_context.program_id, group)\n",
|
||||
"print(f\"Done. Time taken: {time.time() - start_time}\")\n",
|
||||
"\n",
|
||||
"print(\"Loading open orders accounts...\")\n",
|
||||
"open_orders = OpenOrders.load_raw_open_orders_accounts(default_context, group)\n",
|
||||
"print(f\"Done. Time taken: {time.time() - start_time}\")\n",
|
||||
"\n",
|
||||
"print(\"Installing open orders accounts...\")\n",
|
||||
"open_orders_by_address = {key: value for key, value in [(str(address), open_orders_account) for address, open_orders_account in open_orders]}\n",
|
||||
"for margin_account in margin_accounts:\n",
|
||||
" margin_account.install_open_orders_accounts(group, open_orders_by_address)\n",
|
||||
"print(f\"Done. Time taken: {time.time() - start_time}\")\n",
|
||||
"\n",
|
||||
"print(\"Loading pandas dataframe...\")\n",
|
||||
"data = []\n",
|
||||
"df_index = []\n",
|
||||
"for index, margin_account in enumerate(margin_accounts):\n",
|
||||
" balance_sheet = margin_account.get_balance_sheet(group, prices)\n",
|
||||
" df_index += [str(margin_account.address)]\n",
|
||||
" data += [{\"Owner\": margin_account.owner, \"Liabilities\": balance_sheet.liabilities, \"Assets\": balance_sheet.assets, \"Settled Assets\": balance_sheet.settled_assets, \"Unsettled Assets\": balance_sheet.unsettled_assets, \"Collateral Ratio\": balance_sheet.collateral_ratio}]\n",
|
||||
"df = pd.DataFrame(data, index=df_index)\n",
|
||||
"\n",
|
||||
"print(f\"Done. Time taken: {time.time() - start_time}\")\n",
|
||||
"\n",
|
||||
"def render_styled(df: pd.DataFrame):\n",
|
||||
" return df.style.format({\n",
|
||||
" \"Liabilities\": \"${:,.2f}\",\n",
|
||||
" \"Assets\": \"${:,.2f}\",\n",
|
||||
" \"Settled Assets\": \"${:,.2f}\",\n",
|
||||
" \"Unsettled Assets\": \"${:,.2f}\",\n",
|
||||
" \"Collateral Ratio\": \"{:,.2%}\"\n",
|
||||
"})\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "rural-number",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Total assets and liabilities"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "optical-protocol",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print(f\"\"\"\n",
|
||||
"Total Assets: ${df['Assets'].sum():>15,.2f}\n",
|
||||
"Total Liabilities: ${df['Liabilities'].sum():>15,.2f}\n",
|
||||
"\"\"\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "hollow-smile",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Top 10 margin accounts with most assets"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "adopted-corrections",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"render_styled(df.sort_values(\"Assets\", ascending=False).head(10))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "executive-malawi",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Top 10 margin accounts with most liabilities"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "comparable-davis",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"render_styled(df.sort_values(\"Liabilities\", ascending=False).head(10))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "welsh-blake",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Top 10 least collateralised margin accounts\n",
|
||||
"\n",
|
||||
"Collect all margin accounts that have a non-zero Collateral Ratio (so have some liabilities). Then sort them from least-collateralised to most-collateralised."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "gothic-example",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"render_styled(df[df[\"Collateral Ratio\"] != 0].sort_values(\"Collateral Ratio\", ascending=True).head(10))"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
# Mango Explorer
|
||||
|
||||
**I am not yet confident in the figures and calculations. Please don't rely on this code yet!**
|
||||
|
||||
This is the start of a project to explore and provide useful code for [Mango Markets](https://mango.markets/).
|
||||
|
||||
There are some notebook pages to explore Mango account structures for your own accounts.
|
||||
|
||||
The aim is also to have, at some point, a fully-functioning liquidator.
|
||||
|
||||
The goal is not to be easy to use (although that would be nice!). The goal is to show you how the system works.
|
||||
|
||||
|
||||
## Show your Mango margin accounts
|
||||
|
||||
To try this out, go to the [Show My Accounts](ShowMyAccounts.ipynb) page and enter your public key. (Note: I know you're running untrusted code, but it can't do much if you only give it your public key.)
|
||||
|
||||
|
||||
## Show all Mango margin accounts
|
||||
|
||||
To try this out, go to the [Show All Accounts](ShowAllAccounts.ipynb) page and run the code.
|
||||
|
||||
|
||||
## Load all margin accounts into a Pandas `DataFrame`
|
||||
|
||||
To try this out, go to the [Pandas](Pandas.ipynb) page and run the code.
|
||||
|
||||
[Pandas](https://pandas.pydata.org/) is a data analysis and manipulation tool and it's useful for sorting and slicing large data sets.
|
||||
|
||||
The [Pandas](Pandas.ipynb) page can currently show you:
|
||||
* The total assets and liabilities currently in [Mango Markets](https://mango.markets/) margin accounts.
|
||||
* The top ten margin accounts with the most assets.
|
||||
* The top ten margin accounts with the most liabilities.
|
||||
* The top ten margin accounts with the lowest collateralisation.
|
||||
|
||||
|
||||
## Structure of this project
|
||||
|
||||
The code is (nearly) all Python in Jupyter Notebooks.
|
||||
|
||||
Some notebooks are more code files than useful notebooks themselves (although being able to easily run the code is still a boon):
|
||||
* The [Layouts](Layouts.ipynb) notebook contains the low-level Python data structures for interpreting raw Solana program data.
|
||||
* The [Classes](Classes.ipynb) notebook contains higher-level classes for loading and working with that data.
|
||||
|
||||
Other notebooks are more user-oriented:
|
||||
* [Show My Accounts](ShowMyAccounts.ipynb) to show data pertaining to a single Mango Markets margin account.
|
||||
* [Show All Accounts](ShowAllAccounts.ipynb) to show data for all Mango Markets margin accounts.
|
||||
* [Pandas](Pandas.ipynb) to load data into a [Pandas](https://pandas.pydata.org/) `DataFrame` to allow for further manipulation and analysis.
|
||||
|
||||
|
||||
## TODO
|
||||
|
||||
Still to come:
|
||||
* More work on margin accounts and valuation
|
||||
* Finding and showing liquidatable margin accounts
|
||||
* A command-line tool to run a full liquidator
|
||||
* Performance of code that loads all margin accounts is much too slow and needs to be improved.
|
||||
* The notebooks use [hard-coded data from the Mango Client](https://raw.githubusercontent.com/blockworks-foundation/mango-client-ts/main/src/ids.json) - it would be good if it could do without that.
|
||||
|
||||
Don't hold your breath waiting for these!
|
||||
|
||||
|
||||
# Support
|
||||
|
||||
You can contact me [@OpinionatedGeek on Twitter](https://twitter.com/OpinionatedGeek).
|
|
@ -0,0 +1,105 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "sonic-somewhere",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 🥭 Show All Accounts\n",
|
||||
"\n",
|
||||
"This notebook tries to display information about all Mango margin accounts.\n",
|
||||
"\n",
|
||||
"It fetches the data from Solana, parses it, and then prints it.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "addressed-society",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## How To Use This Page\n",
|
||||
"\n",
|
||||
"Theo code should be runnable as-is. Just click the >> button in the toolbar above, and you should see output appear below the code."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "naked-notion",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from pyserum.market import Market\n",
|
||||
"\n",
|
||||
"from Context import default_context\n",
|
||||
"from Classes import AccountInfo, Group, MarginAccount, OpenOrders, TokenAccount\n",
|
||||
"\n",
|
||||
"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",
|
||||
" prices = group.get_prices()\n",
|
||||
" for price in prices:\n",
|
||||
" print(f\"Price: {price:,.8f}\")\n",
|
||||
"\n",
|
||||
" vaults = AccountInfo.load_multiple(default_context, [token.vault for token in group.tokens])\n",
|
||||
" print(\"Vaults:\", vaults)\n",
|
||||
"\n",
|
||||
" for index, vault in enumerate(vaults):\n",
|
||||
" token = TokenAccount.parse(vault.data)\n",
|
||||
" decimals = group.tokens[index].decimals\n",
|
||||
" amount = token.amount / (10 ** decimals)\n",
|
||||
" print(f\"Vault token amount[{index}]: {amount:,.8f}\")\n",
|
||||
"\n",
|
||||
" import time\n",
|
||||
" start_time = time.time()\n",
|
||||
" print(\"Loading margin accounts...\")\n",
|
||||
" margin_accounts = MarginAccount.load_all_for_group(default_context, default_context.program_id, group)\n",
|
||||
" print(f\"Done. Time taken: {time.time() - start_time}\")\n",
|
||||
"\n",
|
||||
" print(\"Loading open orders accounts...\")\n",
|
||||
" # Calling margin_account.load_open_orders() in a loop for each margin account would take a long time.\n",
|
||||
" # We can do better. Load all the relevant OpenOrders accounts, and then just call each margin account\n",
|
||||
" # to map its own OpenOrders from the loaded OpenOrders dictionary.\n",
|
||||
" open_orders = OpenOrders.load_raw_open_orders_accounts(default_context, group)\n",
|
||||
" open_orders_by_address = {key: value for key, value in [(str(address), open_orders_account) for address, open_orders_account in open_orders]}\n",
|
||||
" for margin_account in margin_accounts:\n",
|
||||
" margin_account.install_open_orders_accounts(group, open_orders_by_address)\n",
|
||||
" print(f\"Done. Time taken: {time.time() - start_time}\")\n",
|
||||
"\n",
|
||||
" # print(margin_accounts)\n",
|
||||
"\n",
|
||||
"# show_all_accounts()\n",
|
||||
"import cProfile\n",
|
||||
"import pstats\n",
|
||||
"cProfile.run(\"show_all_accounts()\", sort=pstats.SortKey.TIME)\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"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "acute-worker",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 🥭 Show My Accounts\n",
|
||||
"\n",
|
||||
"This notebook tries to display information about all Mango margin accounts that belong to a specified account.\n",
|
||||
"\n",
|
||||
"It fetches the data from Solana, parses it, and then prints it.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "blessed-concentration",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## How To Use This Page\n",
|
||||
"\n",
|
||||
"Enter the public key of the account you want to check in the value for `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": "lonely-graham",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ACCOUNT_TO_LOOK_UP = \"\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "prescription-basics",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from solana.publickey import PublicKey\n",
|
||||
"from Context import default_context\n",
|
||||
"from Classes import AccountInfo, Group, MarginAccount\n",
|
||||
"\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",
|
||||
"print(\"My account:\", root_account)\n",
|
||||
"\n",
|
||||
"group = Group.load(default_context)\n",
|
||||
"# print(\"Group:\", group)\n",
|
||||
"\n",
|
||||
"prices = group.get_prices()\n",
|
||||
"\n",
|
||||
"my_margin_accounts = MarginAccount.load_all_for_owner(default_context, default_context.program_id, group, root_account_key)\n",
|
||||
"for my_margin_account in my_margin_accounts:\n",
|
||||
" print(\"My margin account:\", my_margin_account)\n",
|
||||
" print(my_margin_account.get_balance_sheet(group, prices))\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"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
CURRENT_DIRECTORY="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
cd "${CURRENT_DIRECTORY}/.."
|
||||
|
||||
docker build . -t mango-markets
|
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
CURRENT_DIRECTORY="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
docker run -it --rm \
|
||||
-e GRANT_SUDO=yes --user root \
|
||||
-p 8888:8888 \
|
||||
--name mango-markets \
|
||||
-v ${CURRENT_DIRECTORY}/..:/home/jovyan/work \
|
||||
mango-markets:latest \
|
||||
start.sh jupyter lab --NotebookApp.iopub_data_rate_limit=9e9
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from pathlib import Path
|
||||
from os.path import isfile, join
|
||||
|
||||
# Get the full path to this script.
|
||||
script_path = Path(os.path.realpath(__file__))
|
||||
|
||||
# The parent of the script is the bin directory, the parent of that is the
|
||||
# notebook directory. It's this notebook directory we want.
|
||||
notebook_directory = script_path.parent.parent
|
||||
print(f"Linting notebook files in: {notebook_directory}")
|
||||
|
||||
# But we want to lint every notebook just to make sure the code in it is OK.
|
||||
all_notebook_files = [f for f in os.listdir(notebook_directory) if isfile(
|
||||
notebook_directory / f) and f.endswith(".ipynb")]
|
||||
|
||||
all_notebook_files.sort()
|
||||
|
||||
try:
|
||||
# Now lint each notebook in turn.
|
||||
for notebook_name in all_notebook_files:
|
||||
print(f"Linting {notebook_name}...", flush=True)
|
||||
command = f'nblint --linter pyflakes {notebook_name} | grep -v -E "Code Cell [0-9]+ that starts with" | grep -v "may be undefined" | grep -v "projectsetup" | grep -v "unable to detect undefined names" | sed "/^[[:space:]]*$/d"'
|
||||
os.system(command)
|
||||
except Exception as ex:
|
||||
print(f"Caught exception: {ex}")
|
||||
|
||||
tmpfile = notebook_directory / "tmp.py"
|
||||
if os.path.exists(tmpfile):
|
||||
os.unlink(tmpfile)
|
||||
|
||||
print("All linting complete.")
|
|
@ -0,0 +1,82 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import importlib
|
||||
import nbformat
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from pathlib import Path
|
||||
from os.path import isfile, join
|
||||
from mypy import api
|
||||
from nbconvert.exporters import PythonExporter
|
||||
|
||||
# Get the full path to this script.
|
||||
script_path = Path(os.path.realpath(__file__))
|
||||
|
||||
# The parent of the script is the bin directory, the parent of that is the
|
||||
# notebook directory. It's this notebook directory we want.
|
||||
notebook_directory = script_path.parent.parent
|
||||
print(f"Running notebook files in: {notebook_directory}")
|
||||
|
||||
# Add the notebook directory to our import path.
|
||||
sys.path.append(str(notebook_directory))
|
||||
sys.path.append(str(notebook_directory / "meta" / "startup"))
|
||||
|
||||
# Change into the notebook directory as well so relative locations work properly.
|
||||
os.chdir(notebook_directory)
|
||||
|
||||
# Tell the importer how to import .ipynb files.
|
||||
import projectsetup
|
||||
|
||||
parser = argparse.ArgumentParser(description='Run MyPy for static checks.')
|
||||
parser.add_argument('--filename',
|
||||
metavar='FILENAME',
|
||||
help="(optional) name of file to be checked")
|
||||
|
||||
args = parser.parse_args()
|
||||
all_notebook_files = []
|
||||
if args.filename:
|
||||
all_notebook_files = [args.filename]
|
||||
else:
|
||||
# We want to load every notebook just to make sure the code in it is OK.
|
||||
all_notebook_files = [f for f in os.listdir(notebook_directory) if isfile(notebook_directory / f) and f.endswith(".ipynb")]
|
||||
all_notebook_files.sort()
|
||||
|
||||
line_pattern = re.compile('(<[a-z]+>:)\s*(\d+)\s*:\s*(.*?)\s*:\s*(.*?)$')
|
||||
def print_pattern_result(message: str):
|
||||
pattern_result = line_pattern.findall(message)
|
||||
if pattern_result:
|
||||
_, line_number, category, text = pattern_result[0]
|
||||
print(f"[{category}] line {line_number}: {text}")
|
||||
elif message:
|
||||
print(message)
|
||||
|
||||
# Now import each notebook in turn. If there are code problems, this should show them.
|
||||
# This should also run tests, if the module has any.
|
||||
for module_name in all_notebook_files:
|
||||
print(f"Checking: {module_name}")
|
||||
with open(module_name, 'r') as notebook_file:
|
||||
body = notebook_file.read()
|
||||
|
||||
if module_name.endswith(".ipynb"):
|
||||
notebook = nbformat.reads(body, as_version=4)
|
||||
python_exporter = PythonExporter()
|
||||
(body, resources) = python_exporter.from_notebook_node(notebook)
|
||||
|
||||
warnings, errors, exit_code = api.run(['--ignore-missing-imports', '-c', body])
|
||||
if exit_code != 0:
|
||||
print(f"MyPy exited with code {exit_code} on {module_name}")
|
||||
if warnings:
|
||||
print("MyPy Issues:")
|
||||
for warning in warnings.split("\n"):
|
||||
print_pattern_result(warning)
|
||||
if errors:
|
||||
print("MyPy Errors:")
|
||||
for error in errors.split("\n"):
|
||||
print_pattern_result(error)
|
||||
|
||||
break
|
||||
|
||||
print("All files checked using MyPy.")
|
|
@ -0,0 +1,270 @@
|
|||
{
|
||||
"cluster_urls": {
|
||||
"devnet": "https://devnet.solana.com",
|
||||
"localnet": "http://127.0.0.1:8899",
|
||||
"mainnet-beta": "https://solana-api.projectserum.com",
|
||||
"testnet": "https://testnet.solana.com"
|
||||
},
|
||||
"devnet": {
|
||||
"dex_program_id": "DESVgJVGajEgKGXhb6XmqDHGz3VjdgP7rEVESBgxmroY",
|
||||
"faucets": {
|
||||
"BTC": "97z3NzcDxqRMyE7F73PuHEmAbA72S7eDopjhe7GTymTk",
|
||||
"ETH": "CvRouhBrimBuSyLd8zHxduNJDtV4LWtoowoF62FCwK7V",
|
||||
"MSRM": "9ysywkpvyvxaaezq2Dapj1p1gHPP3U3D3ccTTecVfYHe",
|
||||
"SRM": "9NzrM7CZ1jq46mX2JGcqyUxBupQEn614A5sZrvg3TrCU",
|
||||
"USDC": "",
|
||||
"USDT": "AS1EfwXvpejkkLrEPdz9J84By9kPvVBzYaD6Ks8ya1A6",
|
||||
"WUSDT": "DV8YAUHc4CiadQoFCHriTjNXbtwCw1Rt834EBYeCyvGt"
|
||||
},
|
||||
"fee_symbol": "SRM",
|
||||
"mango_groups": {
|
||||
"BTC_ETH_USDC": {
|
||||
"mango_group_pk": "C9ZtsC1wmqMzbyCUTeBppZSKH82FsKrGnaWjv5BtWvvo",
|
||||
"mint_pks": [
|
||||
"C6kYXcaRUMqeBF5fhg165RWU7AnpT9z92fvKNoMqjmz6",
|
||||
"8p968u9m7jZzKSsqxFDqki69MjqdFkwPM9FN4AN8hvHR",
|
||||
"Fq939Y5hycK62ZGwBjftLY2VyxqAQ8f1MxRqBMdAaBS7"
|
||||
],
|
||||
"oracle_pks": [
|
||||
"3iQqi9nBREjVvKtVWd44Jcbvs39CDAe6zSd613QzxuPE",
|
||||
"5qxMJFJXB42j3kKo3FbTughREjziottXHcgLnjCNwjEs"
|
||||
],
|
||||
"spot_market_pks": [
|
||||
"FKysSZkCCh41G1SCxpE7Cb7eaLofYBhEneLzHFz6JvjH",
|
||||
"BYz5dJegg11x94jS2R7ZTCgaJwimvupmkjeYDm9Y3UwP"
|
||||
],
|
||||
"spot_market_symbols": {
|
||||
"BTC/USDC": "FKysSZkCCh41G1SCxpE7Cb7eaLofYBhEneLzHFz6JvjH",
|
||||
"ETH/USDC": "BYz5dJegg11x94jS2R7ZTCgaJwimvupmkjeYDm9Y3UwP"
|
||||
},
|
||||
"srm_vault_pk": "7YBghCMgSnvs3cg9taiwwKySiwDvby4USL3bmg8JQXF2",
|
||||
"symbols": {
|
||||
"BTC": "C6kYXcaRUMqeBF5fhg165RWU7AnpT9z92fvKNoMqjmz6",
|
||||
"ETH": "8p968u9m7jZzKSsqxFDqki69MjqdFkwPM9FN4AN8hvHR",
|
||||
"USDC": "Fq939Y5hycK62ZGwBjftLY2VyxqAQ8f1MxRqBMdAaBS7"
|
||||
},
|
||||
"vault_pks": [
|
||||
"Avpn6H3Tu2kJqCouYVpJgqU17iKRR9e2eEjVydQpPuyM",
|
||||
"GRPxtA2TdS8PaGK4E9utnFo7QGf4jLt7RRqbkQXR5fsU",
|
||||
"6gmXb2hRKeSRD1vvGwVtAVJSH8fBH7SitseyMnC3cat3"
|
||||
]
|
||||
},
|
||||
"BTC_ETH_USDT": {
|
||||
"mango_group_pk": "H7T5VdS3x68VTFak9h34Q6qQzECeF9PcPL82qyfiUrcH",
|
||||
"mint_pks": [
|
||||
"C6kYXcaRUMqeBF5fhg165RWU7AnpT9z92fvKNoMqjmz6",
|
||||
"8p968u9m7jZzKSsqxFDqki69MjqdFkwPM9FN4AN8hvHR",
|
||||
"7KBVenLz5WNH4PA5MdGkJNpDDyNKnBQTwnz1UqJv9GUm"
|
||||
],
|
||||
"oracle_pks": [
|
||||
"EHasrbBk5mFnTYPjmdNzDoa7cEBH3yN8D4DLJn7Q41hY",
|
||||
"3ynBi9nQyKoEJwC47cmXviLSgHaaXQBKRxukatYYLN1Y"
|
||||
],
|
||||
"spot_market_pks": [
|
||||
"6Cpt7EYmzUcHLBQzZuYNqnyKQKieofZXw6bpCWtmwZM1",
|
||||
"4UQq7c8FdwGkb2TghHVgJShHMJwS4YzjvA3yiF6zArJD"
|
||||
],
|
||||
"spot_market_symbols": {
|
||||
"BTC/USDT": "6Cpt7EYmzUcHLBQzZuYNqnyKQKieofZXw6bpCWtmwZM1",
|
||||
"ETH/USDT": "4UQq7c8FdwGkb2TghHVgJShHMJwS4YzjvA3yiF6zArJD"
|
||||
},
|
||||
"srm_vault_pk": "7Q85DzBw92oyXxQ5e5K2XnNHw9doRQaZKqvhxCef9uN8",
|
||||
"symbols": {
|
||||
"BTC": "C6kYXcaRUMqeBF5fhg165RWU7AnpT9z92fvKNoMqjmz6",
|
||||
"ETH": "8p968u9m7jZzKSsqxFDqki69MjqdFkwPM9FN4AN8hvHR",
|
||||
"USDT": "7KBVenLz5WNH4PA5MdGkJNpDDyNKnBQTwnz1UqJv9GUm"
|
||||
},
|
||||
"vault_pks": [
|
||||
"4ntkC6t3h75T5SWTKcgHHy9NQeengcydyn4NQtrb5aSZ",
|
||||
"6kdcJ3fTNmFRozBn3Yi6S9WgjzUPo6ZQg6LXQzomTjmj",
|
||||
"7LTGV36ixZBp3gPDowvFQx3HKdVgcLX3TDzGVeWsTiy8"
|
||||
]
|
||||
},
|
||||
"BTC_ETH_WUSDT": {
|
||||
"mango_group_pk": "6WZGjqRi9XKgkqZdsYwit4ASVDQ1iiHqkiDvVgY1n2uW",
|
||||
"mint_pks": [
|
||||
"C6kYXcaRUMqeBF5fhg165RWU7AnpT9z92fvKNoMqjmz6",
|
||||
"8p968u9m7jZzKSsqxFDqki69MjqdFkwPM9FN4AN8hvHR",
|
||||
"7tSPGVhneTBWZjLGJGZb9V2UntC7T98cwtSLtgcXjeSs"
|
||||
],
|
||||
"oracle_pks": [
|
||||
"EHasrbBk5mFnTYPjmdNzDoa7cEBH3yN8D4DLJn7Q41hY",
|
||||
"3ynBi9nQyKoEJwC47cmXviLSgHaaXQBKRxukatYYLN1Y"
|
||||
],
|
||||
"spot_market_pks": [
|
||||
"ELXP9wTE4apvK9sxAqtCtMidbAvJJDrNVg4wL6jqQEBA",
|
||||
"97mbLfi4S56y5Vg2LCF4Z7ru8jD1QjHa5SH3eyFYrMdg"
|
||||
],
|
||||
"spot_market_symbols": {
|
||||
"BTC/WUSDT": "ELXP9wTE4apvK9sxAqtCtMidbAvJJDrNVg4wL6jqQEBA",
|
||||
"ETH/WUSDT": "97mbLfi4S56y5Vg2LCF4Z7ru8jD1QjHa5SH3eyFYrMdg"
|
||||
},
|
||||
"srm_vault_pk": "3TgeEz19ycH7dH6FvR5rCjxD1sJzAq8Esj8BC98CaeEN",
|
||||
"symbols": {
|
||||
"BTC": "C6kYXcaRUMqeBF5fhg165RWU7AnpT9z92fvKNoMqjmz6",
|
||||
"ETH": "8p968u9m7jZzKSsqxFDqki69MjqdFkwPM9FN4AN8hvHR",
|
||||
"WUSDT": "7tSPGVhneTBWZjLGJGZb9V2UntC7T98cwtSLtgcXjeSs"
|
||||
},
|
||||
"vault_pks": [
|
||||
"Gf4Z19ygbzmBVSXcyvwZQPdrGs8k5TLPPSikU3hZzq8k",
|
||||
"5AUNwSv8hdwJVhUuaBtsFadtJQfiudSZcY6U9fNoRd2A",
|
||||
"41r2LvZZDECzm3sdxuP9AcyXZ9FPXqoTjbmnPfBG7ofn"
|
||||
]
|
||||
}
|
||||
},
|
||||
"mango_program_id": "32YaLZeyUHhdFaGSemTncUEcZEEGjKart8NX1XG2k3fs",
|
||||
"oracles": {
|
||||
"BTC/USDC": "EHasrbBk5mFnTYPjmdNzDoa7cEBH3yN8D4DLJn7Q41hY",
|
||||
"BTC/USDT": "EHasrbBk5mFnTYPjmdNzDoa7cEBH3yN8D4DLJn7Q41hY",
|
||||
"BTC/WUSDT": "EHasrbBk5mFnTYPjmdNzDoa7cEBH3yN8D4DLJn7Q41hY",
|
||||
"ETH/USDC": "3ynBi9nQyKoEJwC47cmXviLSgHaaXQBKRxukatYYLN1Y",
|
||||
"ETH/USDT": "3ynBi9nQyKoEJwC47cmXviLSgHaaXQBKRxukatYYLN1Y",
|
||||
"ETH/WUSDT": "3ynBi9nQyKoEJwC47cmXviLSgHaaXQBKRxukatYYLN1Y"
|
||||
},
|
||||
"spot_markets": {
|
||||
"BTC/USDC": "FKysSZkCCh41G1SCxpE7Cb7eaLofYBhEneLzHFz6JvjH",
|
||||
"BTC/USDT": "6Cpt7EYmzUcHLBQzZuYNqnyKQKieofZXw6bpCWtmwZM1",
|
||||
"BTC/WUSDT": "ELXP9wTE4apvK9sxAqtCtMidbAvJJDrNVg4wL6jqQEBA",
|
||||
"ETH/USDC": "BYz5dJegg11x94jS2R7ZTCgaJwimvupmkjeYDm9Y3UwP",
|
||||
"ETH/USDT": "4UQq7c8FdwGkb2TghHVgJShHMJwS4YzjvA3yiF6zArJD",
|
||||
"ETH/WUSDT": "97mbLfi4S56y5Vg2LCF4Z7ru8jD1QjHa5SH3eyFYrMdg"
|
||||
},
|
||||
"symbols": {
|
||||
"BTC": "C6kYXcaRUMqeBF5fhg165RWU7AnpT9z92fvKNoMqjmz6",
|
||||
"ETH": "8p968u9m7jZzKSsqxFDqki69MjqdFkwPM9FN4AN8hvHR",
|
||||
"MSRM": "934bNdNw9QfE8dXD4mKQiKajYURfSkPhxfYZzpvmygca",
|
||||
"SRM": "9FbAMDvXqNjPqZSYt4EWTguJuDrGkfvwr3gSFpiSbX9S",
|
||||
"USDC": "Fq939Y5hycK62ZGwBjftLY2VyxqAQ8f1MxRqBMdAaBS7",
|
||||
"USDT": "7KBVenLz5WNH4PA5MdGkJNpDDyNKnBQTwnz1UqJv9GUm",
|
||||
"WUSDT": "7tSPGVhneTBWZjLGJGZb9V2UntC7T98cwtSLtgcXjeSs"
|
||||
}
|
||||
},
|
||||
"localnet": {
|
||||
"dex_program_id": "",
|
||||
"mango_groups": {
|
||||
"BTC_ETH_USDC": {}
|
||||
},
|
||||
"mango_program_id": "",
|
||||
"spot_markets": {
|
||||
"BTC_USDC": "",
|
||||
"ETH_USDC": ""
|
||||
},
|
||||
"symbols": {
|
||||
"BTC": "",
|
||||
"ETH": "",
|
||||
"USDC": ""
|
||||
}
|
||||
},
|
||||
"mainnet-beta": {
|
||||
"dex_program_id": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin",
|
||||
"dex_program_id_v2": "EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o",
|
||||
"fee_token": "SRM",
|
||||
"mango_groups": {
|
||||
"BTC_ETH_USDT": {
|
||||
"mango_group_pk": "7pVYhpKUHw88neQHxgExSH6cerMZ1Axx1ALQP9sxtvQV",
|
||||
"mint_pks": [
|
||||
"9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E",
|
||||
"2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk",
|
||||
"Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"
|
||||
],
|
||||
"oracle_pks": [
|
||||
"HWh11EWkVHHZoRV6D6WzfRSna4yFv8ZvwcqDk74oDnSs",
|
||||
"AcYcDG74nxeFHxuqeD5RRWTMWKi77QVx7t9bEy8Y4eyN"
|
||||
],
|
||||
"spot_market_pks": [
|
||||
"C1EuT9VokAKLiW7i2ASnZUvxDoKuKkCpDDeNxAptuNe4",
|
||||
"7dLVkUfBVfCGkFhSXDCq1ukM9usathSgS716t643iFGF"
|
||||
],
|
||||
"spot_market_symbols": {
|
||||
"BTC/USDT": "C1EuT9VokAKLiW7i2ASnZUvxDoKuKkCpDDeNxAptuNe4",
|
||||
"ETH/USDT": "7dLVkUfBVfCGkFhSXDCq1ukM9usathSgS716t643iFGF"
|
||||
},
|
||||
"srm_vault_pk": "65D8BWH5Bx4jstkZAkc5a3CdYvZjUAehJde3TTUTruQY",
|
||||
"symbols": {
|
||||
"BTC": "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E",
|
||||
"ETH": "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk",
|
||||
"USDT": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"
|
||||
},
|
||||
"vault_pks": [
|
||||
"9AWv6CMSUwppXV1VW8ueMvevtPsyDEPFjeJ7UYHhyxMk",
|
||||
"3AGLriXSkujXN3TT2HrmfdLhMR9ApoYSMdPUiuXW95Kn",
|
||||
"9UL2DZCskV2m7zFsA7h7igzEw4HMtTjRdQr2X6sTCn1i"
|
||||
]
|
||||
},
|
||||
"BTC_ETH_WUSDT": {
|
||||
"mango_group_pk": "6NsLVpG2pdxn2rEFkmHHYjkY5QP5qG4RN1fRcSqDVxPC",
|
||||
"mint_pks": [
|
||||
"9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E",
|
||||
"2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk",
|
||||
"BQcdHdAQW1hczDbBi9hiegXAR7A98Q9jx3X3iBBBDiq4"
|
||||
],
|
||||
"oracle_pks": [
|
||||
"HWh11EWkVHHZoRV6D6WzfRSna4yFv8ZvwcqDk74oDnSs",
|
||||
"AcYcDG74nxeFHxuqeD5RRWTMWKi77QVx7t9bEy8Y4eyN"
|
||||
],
|
||||
"spot_market_pks": [
|
||||
"5r8FfnbNYcQbS1m4CYmoHYGjBtu6bxfo6UJHNRfzPiYH",
|
||||
"71CtEComq2XdhGNbXBuYPmosAjMCPSedcgbNi5jDaGbR"
|
||||
],
|
||||
"spot_market_symbols": {
|
||||
"BTC/WUSDT": "5r8FfnbNYcQbS1m4CYmoHYGjBtu6bxfo6UJHNRfzPiYH",
|
||||
"ETH/WUSDT": "71CtEComq2XdhGNbXBuYPmosAjMCPSedcgbNi5jDaGbR"
|
||||
},
|
||||
"srm_vault_pk": "Fn6VHzE2PkJBeJAtCd6T5PP4ni79SqAdnFozFewgh8i6",
|
||||
"symbols": {
|
||||
"BTC": "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E",
|
||||
"ETH": "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk",
|
||||
"WUSDT": "BQcdHdAQW1hczDbBi9hiegXAR7A98Q9jx3X3iBBBDiq4"
|
||||
},
|
||||
"vault_pks": [
|
||||
"FF8h6eCSqoGQyuYmh3BzxdaTfomCBPrCctRq9Yo6nCcd",
|
||||
"GWwECYXmTUumcUsjbwdJDc9ws4KDWYBJ1GGmckZr2hTK",
|
||||
"BoGTDjtbEtK8HPCu2VPNJfA7bTLuVDPETDoHvztm6Mqe"
|
||||
]
|
||||
}
|
||||
},
|
||||
"mango_program_id": "JD3bq9hGdy38PuWQ4h2YJpELmHVGPPfFSuFkpzAd9zfu",
|
||||
"oracles": {
|
||||
"BTC/USDT": "HWh11EWkVHHZoRV6D6WzfRSna4yFv8ZvwcqDk74oDnSs",
|
||||
"BTC/WUSDT": "HWh11EWkVHHZoRV6D6WzfRSna4yFv8ZvwcqDk74oDnSs",
|
||||
"ETH/USDT": "AcYcDG74nxeFHxuqeD5RRWTMWKi77QVx7t9bEy8Y4eyN",
|
||||
"ETH/WUSDT": "AcYcDG74nxeFHxuqeD5RRWTMWKi77QVx7t9bEy8Y4eyN"
|
||||
},
|
||||
"spot_markets": {
|
||||
"BTC/USDC": "CVfYa8RGXnuDBeGmniCcdkBwoLqVxh92xB1JqgRQx3F",
|
||||
"BTC/USDT": "C1EuT9VokAKLiW7i2ASnZUvxDoKuKkCpDDeNxAptuNe4",
|
||||
"BTC/WUSDT": "5r8FfnbNYcQbS1m4CYmoHYGjBtu6bxfo6UJHNRfzPiYH",
|
||||
"ETH/USDC": "H5uzEytiByuXt964KampmuNCurNDwkVVypkym75J2DQW",
|
||||
"ETH/USDT": "7dLVkUfBVfCGkFhSXDCq1ukM9usathSgS716t643iFGF",
|
||||
"ETH/WUSDT": "71CtEComq2XdhGNbXBuYPmosAjMCPSedcgbNi5jDaGbR",
|
||||
"SOL/USDT": "HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1",
|
||||
"SOL/WUSDT": "7xLk17EQQ5KLDLDe44wCmupJKJjTGd8hs3eSVVhCx932",
|
||||
"SRM/USDC": "CDdR97S8y96v3To93aKvi3nCnjUrbuVSuumw8FLvbVeg"
|
||||
},
|
||||
"symbols": {
|
||||
"BTC": "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E",
|
||||
"ETH": "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk",
|
||||
"MSRM": "MSRMcoVyrFxnSgo5uXwone5SKcGhT1KEJMFEkMEWf9L",
|
||||
"SRM": "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt",
|
||||
"USDC": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
||||
"USDT": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",
|
||||
"WUSDT": "BQcdHdAQW1hczDbBi9hiegXAR7A98Q9jx3X3iBBBDiq4"
|
||||
}
|
||||
},
|
||||
"testnet": {
|
||||
"dex_program_id": "",
|
||||
"mango_groups": {
|
||||
"BTC_ETH_USDC": {}
|
||||
},
|
||||
"mango_program_id": "",
|
||||
"spot_markets": {
|
||||
"BTC_USDC": "",
|
||||
"ETH_USDC": ""
|
||||
},
|
||||
"symbols": {
|
||||
"BTC": "BahhjqQVqw3LELEVfyhHyEp7Db1TbFUNVrMiSsmTaBR2",
|
||||
"ETH": "Dw91oaC5UGxzSDGaFontF1JS3XoViQV3EaTAgdtRPprM",
|
||||
"USDC": "9q4p8UFxphSipGL3TGku8byTijgk4koTMwhBMV4QKvjw"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
"""
|
||||
From: https://github.com/knowsuchagency/jupyter-mypy/blob/master/main.py
|
||||
|
||||
Add mypy type-checking cell magic to jupyter/ipython.
|
||||
|
||||
"""
|
||||
|
||||
from IPython.core.magic import register_cell_magic
|
||||
|
||||
|
||||
@register_cell_magic
|
||||
def mypy(line, cell):
|
||||
"""
|
||||
Run the following cell though mypy.
|
||||
|
||||
Any parameters that would normally be passed to the mypy cli
|
||||
can be passed on the first line, with the exception of the
|
||||
-c flag we use to pass the code from the cell we want to execute
|
||||
|
||||
i.e.
|
||||
|
||||
%%mypy --something
|
||||
...
|
||||
...
|
||||
...
|
||||
|
||||
If mypy returns a 0 exit code nothing will be printed.
|
||||
"""
|
||||
|
||||
from IPython import get_ipython
|
||||
from IPython.display import display, HTML
|
||||
from mypy import api
|
||||
|
||||
result = api.run(line.split() + ['--ignore-missing-imports', '-c', cell])
|
||||
|
||||
# Result is a tuple with three parts:
|
||||
# 0 - mypy warnings/problems/issues.
|
||||
# 1 - mypy's stderr, for mypy problems.
|
||||
# 2 - mypy's exit code.
|
||||
if result[2] != 0:
|
||||
html = ""
|
||||
if result[0]:
|
||||
html += f"<div style='color: darkorange'>Warnings:<ul>"
|
||||
for message in result[0].split("<string>"):
|
||||
if message:
|
||||
html += f"<li>{message}</li>"
|
||||
html += f"</ul></div>"
|
||||
|
||||
if result[1]:
|
||||
html += f"<div style='color: red'>Errors:<ul>"
|
||||
for message in result[1].split("<string>"):
|
||||
if message:
|
||||
html += f"<li>{message}</li>"
|
||||
html += f"</ul></div>"
|
||||
|
||||
display(HTML(html))
|
||||
|
||||
shell = get_ipython()
|
||||
shell.run_cell(cell)
|
||||
|
||||
# Delete these to avoid name conflicts.
|
||||
del mypy
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
# This is all taken from:
|
||||
# https://jupyter-notebook.readthedocs.io/en/stable/examples/Notebook/Importing%20Notebooks.html
|
||||
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import types
|
||||
|
||||
from importlib.abc import MetaPathFinder
|
||||
from importlib.util import spec_from_loader
|
||||
from nbformat import read
|
||||
from IPython import get_ipython
|
||||
from IPython.core.interactiveshell import InteractiveShell
|
||||
from IPython.display import display, HTML
|
||||
|
||||
ALWAYS_IMPORT = """
|
||||
import datetime
|
||||
import decimal
|
||||
import logging
|
||||
import logging.handlers
|
||||
import numbers
|
||||
import pandas as pd
|
||||
import traceback
|
||||
import typing
|
||||
|
||||
from IPython.display import display, HTML
|
||||
"""
|
||||
|
||||
|
||||
def find_notebook(fullname, path=None):
|
||||
"""Find a notebook, given its fully qualified name and an optional path
|
||||
|
||||
This turns "foo.bar" into "foo/bar.ipynb"
|
||||
and tries turning "Foo_Bar" into "Foo Bar" if Foo_Bar
|
||||
does not exist.
|
||||
"""
|
||||
name = fullname.rsplit('.', 1)[-1]
|
||||
if not path:
|
||||
path = ['']
|
||||
for d in path:
|
||||
nb_path = os.path.join(d, name + ".ipynb")
|
||||
if os.path.isfile(nb_path):
|
||||
return nb_path
|
||||
# let import Notebook_Name find "Notebook Name.ipynb"
|
||||
nb_path = nb_path.replace("_", " ")
|
||||
if os.path.isfile(nb_path):
|
||||
return nb_path
|
||||
|
||||
|
||||
class NotebookLoader(object):
|
||||
"""Module Loader for Jupyter Notebooks"""
|
||||
|
||||
def __init__(self, path=None):
|
||||
self.shell = InteractiveShell.instance()
|
||||
self.path = path
|
||||
|
||||
def load_module(self, fullname):
|
||||
"""import a notebook as a module"""
|
||||
path = find_notebook(fullname, self.path)
|
||||
|
||||
# load the notebook object
|
||||
with io.open(path, 'r', encoding='utf-8') as f:
|
||||
nb = read(f, 4)
|
||||
|
||||
# create the module and add it to sys.modules
|
||||
# if name in sys.modules:
|
||||
# return sys.modules[name]
|
||||
mod = types.ModuleType(fullname)
|
||||
mod.__file__ = path
|
||||
mod.__loader__ = self
|
||||
mod.__dict__['get_ipython'] = get_ipython
|
||||
sys.modules[fullname] = mod
|
||||
|
||||
# extra work to ensure that magics that would affect the user_ns
|
||||
# actually affect the notebook module's ns
|
||||
save_user_ns = self.shell.user_ns
|
||||
self.shell.user_ns = mod.__dict__
|
||||
|
||||
try:
|
||||
exec(ALWAYS_IMPORT, mod.__dict__)
|
||||
for cell in nb.cells:
|
||||
if cell.cell_type == 'code':
|
||||
# transform the input to executable Python
|
||||
code = self.shell.input_transformer_manager.transform_cell(
|
||||
cell.source)
|
||||
# run the code in themodule
|
||||
exec(code, mod.__dict__)
|
||||
finally:
|
||||
self.shell.user_ns = save_user_ns
|
||||
return mod
|
||||
|
||||
|
||||
class NotebookFinder(MetaPathFinder):
|
||||
"""Module finder that locates Jupyter Notebooks"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def find_spec(self, fullname, path, target=None):
|
||||
found = False
|
||||
try:
|
||||
nb_path = find_notebook(fullname, path)
|
||||
if nb_path:
|
||||
found = True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not found:
|
||||
return
|
||||
|
||||
notebookloader = NotebookLoader(path)
|
||||
return spec_from_loader(fullname, notebookloader)
|
||||
|
||||
|
||||
sys.meta_path.append(NotebookFinder())
|
|
@ -0,0 +1,19 @@
|
|||
import datetime
|
||||
import decimal
|
||||
import logging
|
||||
import logging.handlers
|
||||
import numbers
|
||||
import pandas as pd
|
||||
import traceback
|
||||
import typing
|
||||
|
||||
from IPython.display import display, HTML
|
||||
|
||||
# Perform some magic around importing notebooks.
|
||||
import notebookimporter
|
||||
|
||||
pd.options.display.float_format = '{:,.8f}'.format
|
||||
decimal.getcontext().prec = 18
|
||||
|
||||
# Make logging a little more verbose than the default.
|
||||
logging.basicConfig(level=logging.INFO)
|
|
@ -0,0 +1,7 @@
|
|||
ipython
|
||||
mypy
|
||||
nblint
|
||||
pandas
|
||||
pyserum
|
||||
solana
|
||||
web3
|
Loading…
Reference in New Issue