Big renaming of Context properties to make them more consistent and appropriate.

This commit is contained in:
Geoff Taylor 2021-08-26 10:31:02 +01:00
parent b89a072cdd
commit 261848f325
54 changed files with 273 additions and 280 deletions

View File

@ -2,7 +2,6 @@
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# ⚠ Warning\n",
"\n",
@ -11,11 +10,11 @@
"[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gl/OpinionatedGeek%2Fmango-explorer/HEAD?filepath=Pandas.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)"
]
],
"metadata": {}
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 🥭 Mango + Pandas 🐼🐼\n",
"\n",
@ -24,31 +23,28 @@
"The `DataFrame` is then queried for the total assets and liabilities, the Top 10 margin accounts with the most assets and the most liabilities, and then the Top 10 margin accounts closest to liquidation.\n",
"\n",
"The data remains in the `DataFrame` called `df` so you can easily add your own queries and analyses."
]
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"import logging\n",
"import mango\n",
"import pandas as pd\n",
"import time\n"
]
],
"outputs": [],
"metadata": {
"jupyter": {
"source_hidden": true
}
}
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"start_time = time.time()\n",
"context = mango.Context.default()\n",
@ -62,7 +58,7 @@
"print(f\"Done. Time taken: {time.time() - start_time}\")\n",
"\n",
"print(\"Loading margin accounts...\")\n",
"margin_accounts = mango.MarginAccount.load_all_for_group_with_open_orders(context, context.program_id, group)\n",
"margin_accounts = mango.MarginAccount.load_all_for_group_with_open_orders(context, context.mango_program_address, group)\n",
"print(f\"Done. {len(margin_accounts)} accounts. Time taken: {time.time() - start_time}\")\n",
"\n",
"print(\"Loading pandas dataframe...\")\n",
@ -113,20 +109,22 @@
" }\n",
" all_formats.update(sheet_formats)\n",
" return df.style.format(all_formats)\n"
]
],
"outputs": [],
"metadata": {
"tags": []
}
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 🔥 Total Assets + Liabilities"
]
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(f\"\"\"\n",
"Total Assets: ${df['Assets'].sum():>15,.2f}\n",
@ -135,112 +133,114 @@
"Liquidatable: {len(df[(df[\"Collateral Ratio\"] != 0) & (df[\"Collateral Ratio\"] <= 1.1)]):>15,}\n",
"🥭 Ripe Mangoes: {len(df[(df[\"Collateral Ratio\"] > 1.1) & (df[\"Collateral Ratio\"] < 1.2)]):>15,}\n",
"\"\"\")"
]
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 🔝 Top 10 Greatest Assets\n",
"\n",
"The top 10 margin accounts with most assets"
]
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"render_styled(df.sort_values(\"Assets\", ascending=False).head(10))"
]
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 🔝 Top 10 Greatest Liabilities\n",
"\n",
"The top 10 margin accounts with most liabilities"
]
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"render_styled(df.sort_values(\"Liabilities\", ascending=False).head(10))"
]
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 🔝 Top 10 Least Collateralised\n",
"\n",
"The 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."
]
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"nonzero = df[df[\"Collateral Ratio\"] != 0]\n",
"render_styled(nonzero.sort_values(\"Collateral Ratio\", ascending=True).head(10))"
]
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 💧 Liquidatable\n",
"\n",
"An account is 'liquidatable' when its available collateral falls below the group's maintenance collataeral threshold.\n",
"\n",
"This code shows all liquidatable margin accounts, sorted by the available collateral (_not_ the collateral ratio)."
]
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"simplified = nonzero.drop([\"Settled Assets\", \"Unsettled Assets\"], axis=1)\n",
"liquidatable = simplified[simplified[\"Collateral Ratio\"] < group.maint_coll_ratio].copy()\n",
"\n",
"print(f\"There are {len(liquidatable)} liquidatable accounts.\")\n",
"render_styled(liquidatable.sort_values(\"Available Collateral\", ascending=False).head(len(liquidatable)))"
]
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 🥭 Ripe Mangoes\n",
"\n",
"'Ripe mangoes' are margin accounts that are below the group's initial margin requirements but have not yet fallen below the liquidation threshold.\n",
"\n",
"This code shows all ripe mangoes, sorted by the available collateral (_not_ the collateral ratio)."
]
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"ripe = simplified[simplified[\"Collateral Ratio\"] < group.init_coll_ratio]\n",
"only_ripe = ripe[ripe[\"Collateral Ratio\"] >= group.maint_coll_ratio].copy()\n",
"\n",
"print(f\"There are {len(only_ripe)} 🥭 ripe mangoes.\")\n",
"render_styled(only_ripe.sort_values(\"Available Collateral\", ascending=False).head(len(only_ripe)))"
]
],
"outputs": [],
"metadata": {}
}
],
"metadata": {

View File

@ -2,7 +2,6 @@
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# ⚠ Warning\n",
"\n",
@ -11,11 +10,11 @@
"[![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)"
]
],
"metadata": {}
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 🥭 Show All Margin Accounts\n",
"\n",
@ -24,26 +23,21 @@
"It fetches the data from Solana, parses it, and then prints it.\n",
"\n",
"Note: this can take a long time to run."
]
],
"metadata": {}
},
{
"cell_type": "markdown",
"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."
]
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"outputPrepend"
]
},
"outputs": [],
"source": [
"if __name__ == \"__main__\":\n",
" import mango\n",
@ -57,7 +51,7 @@
" print(f\"Done loading group. Time taken: {time.time() - start_time}\")\n",
"\n",
" print(\"Loading margin accounts...\")\n",
" margin_accounts = mango.MarginAccount.load_all_for_group_with_open_orders(context, context.program_id, group)\n",
" margin_accounts = mango.MarginAccount.load_all_for_group_with_open_orders(context, context.mango_program_address, group)\n",
" print(f\"Done loading {len(margin_accounts)} account(s). Total time taken: {time.time() - start_time}\")\n",
"\n",
" print(*margin_accounts, sep=\"\\n\")\n",
@ -67,7 +61,13 @@
" # import cProfile\n",
" # import pstats\n",
" # cProfile.run(\"show_all_accounts()\", sort=pstats.SortKey.TIME)\n"
]
],
"outputs": [],
"metadata": {
"tags": [
"outputPrepend"
]
}
}
],
"metadata": {

View File

@ -25,7 +25,7 @@ logging.warning(mango.WARNING_DISCLAIMER_TEXT)
context = mango.ContextBuilder.from_command_line_parameters(args)
wallet = mango.Wallet.from_command_line_parameters_or_raise(args)
group = mango.Group.load(context, context.group_id)
group = mango.Group.load(context, context.group_address)
account = mango.Account.load_for_owner_by_index(context, wallet.address, group, args.account_index)
market_symbol = args.market.upper()

View File

@ -33,7 +33,7 @@ logging.warning(mango.WARNING_DISCLAIMER_TEXT)
context = mango.ContextBuilder.from_command_line_parameters(args)
wallet = mango.Wallet.from_command_line_parameters_or_raise(args)
group = mango.Group.load(context, context.group_id)
group = mango.Group.load(context, context.group_address)
account = mango.Account.load_for_owner_by_index(context, wallet.address, group, args.account_index)
market_symbol = args.market.upper()

View File

@ -29,7 +29,7 @@ logging.warning(mango.WARNING_DISCLAIMER_TEXT)
context = mango.ContextBuilder.from_command_line_parameters(args)
logging.info(f"Context: {context}")
wallet = mango.Wallet.from_command_line_parameters_or_raise(args)
group = mango.Group.load(context, context.group_id)
group = mango.Group.load(context, context.group_address)
account = mango.Account.load_for_owner_by_index(context, wallet.address, group, args.account_index)
logging.info(f"Wallet address: {wallet.address}")

View File

@ -27,7 +27,7 @@ logging.warning(mango.WARNING_DISCLAIMER_TEXT)
context = mango.ContextBuilder.from_command_line_parameters(args)
wallet = mango.Wallet.from_command_line_parameters_or_raise(args)
group = mango.Group.load(context, context.group_id)
group = mango.Group.load(context, context.group_address)
accounts = mango.Account.load_all_for_owner(context, wallet.address, group)
if len(accounts) == 0:
raise Exception(f"Could not find any Mango accounts for '{wallet.address}'.")

View File

@ -42,7 +42,7 @@ logging.warning(mango.WARNING_DISCLAIMER_TEXT)
context = mango.ContextBuilder.from_command_line_parameters(args)
wallet = mango.Wallet.from_command_line_parameters_or_raise(args)
group = mango.Group.load(context, context.group_id)
group = mango.Group.load(context, context.group_address)
account = mango.Account.load_for_owner_by_index(context, wallet.address, group, args.account_index)
disposer = mango.DisposePropagator()

View File

@ -21,7 +21,7 @@ logging.warning(mango.WARNING_DISCLAIMER_TEXT)
context = mango.ContextBuilder.from_command_line_parameters(args)
wallet = mango.Wallet.from_command_line_parameters_or_raise(args)
group = mango.Group.load(context, context.group_id)
group = mango.Group.load(context, context.group_address)
signers: mango.CombinableInstructions = mango.CombinableInstructions.from_wallet(wallet)
init = mango.build_create_account_instructions(context, wallet, group)

View File

@ -74,7 +74,7 @@ health_check = mango.HealthCheck()
disposer.add_disposable(health_check)
wallet = mango.Wallet.from_command_line_parameters_or_raise(args)
group = mango.Group.load(context, context.group_id)
group = mango.Group.load(context, context.group_address)
account = mango.Account.load_for_owner_by_index(context, wallet.address, group, args.account_index)
market_symbol = args.market.upper()

View File

@ -35,7 +35,7 @@ if account_info is None:
else:
account_sols = account_info.lamports / mango.SOL_DECIMAL_DIVISOR
if account_sols < args.minimum_sol_balance:
report = f"Account \"{args.name} [{args.address}]\" on {context.client.cluster} has only {account_sols} SOL, which is below the minimum required balance of {args.minimum_sol_balance} SOL."
report = f"Account \"{args.name} [{args.address}]\" on {context.client.cluster_name} has only {account_sols} SOL, which is below the minimum required balance of {args.minimum_sol_balance} SOL."
for notify in args.notify:
notify.send(report)
print(f"Notification sent: {report}")

View File

@ -32,7 +32,7 @@ logging.warning(mango.WARNING_DISCLAIMER_TEXT)
context = mango.ContextBuilder.from_command_line_parameters(args)
wallet = mango.Wallet.from_command_line_parameters_or_raise(args)
group = mango.Group.load(context, context.group_id)
group = mango.Group.load(context, context.group_address)
account = mango.Account.load_for_owner_by_index(context, wallet.address, group, args.account_index)
market_symbol = args.market.upper()

View File

@ -20,7 +20,7 @@ def report_accrued(basket_token: mango.AccountBasketBaseToken):
def load_perp_market(context: mango.Context, group: mango.Group, group_basket_market: mango.GroupBasketMarket):
perp_market_details = mango.PerpMarketDetails.load(context, group_basket_market.perp_market_info.address, group)
perp_market = mango.PerpMarket(context.program_id, group_basket_market.perp_market_info.address,
perp_market = mango.PerpMarket(context.mango_program_address, group_basket_market.perp_market_info.address,
group_basket_market.base_token_info.token,
group_basket_market.quote_token_info.token, perp_market_details)
@ -70,7 +70,7 @@ if (not args.all) and (args.market is None):
context = mango.ContextBuilder.from_command_line_parameters(args)
wallet = mango.Wallet.from_command_line_parameters_or_raise(args)
group = mango.Group.load(context, context.group_id)
group = mango.Group.load(context, context.group_address)
mngo = group.find_token_info_by_symbol("MNGO")
account = mango.Account.load_for_owner_by_index(context, wallet.address, group, args.account_index)

View File

@ -27,7 +27,7 @@ logging.warning(mango.WARNING_DISCLAIMER_TEXT)
context = mango.ContextBuilder.from_command_line_parameters(args)
wallet = mango.Wallet.from_command_line_parameters_or_raise(args)
group = mango.Group.load(context, context.group_id)
group = mango.Group.load(context, context.group_address)
account = mango.Account.load_for_owner_by_index(context, wallet.address, group, args.account_index)
logging.info(f"Context: {context}")

View File

@ -29,6 +29,6 @@ if address is None:
wallet = mango.Wallet.from_command_line_parameters_or_raise(args)
address = wallet.address
group = mango.Group.load(context, context.group_id)
group = mango.Group.load(context, context.group_address)
mango_accounts = mango.Account.load_all_for_owner(context, address, group)
print(mango_accounts)

View File

@ -26,7 +26,7 @@ logging.warning(mango.WARNING_DISCLAIMER_TEXT)
context = mango.ContextBuilder.from_command_line_parameters(args)
wallet = mango.Wallet.from_command_line_parameters_or_raise(args)
group = mango.Group.load(context, context.group_id)
group = mango.Group.load(context, context.group_address)
account = mango.Account.load_for_owner_by_index(context, wallet.address, group, args.account_index)
market_symbol = args.market.upper()

View File

@ -25,7 +25,7 @@ logging.warning(mango.WARNING_DISCLAIMER_TEXT)
context = mango.ContextBuilder.from_command_line_parameters(args)
wallet = mango.Wallet.from_command_line_parameters_or_raise(args)
group = mango.Group.load(context, context.group_id)
group = mango.Group.load(context, context.group_address)
account = mango.Account.load_for_owner_by_index(context, wallet.address, group, args.account_index)
market_symbol = args.market.upper()

View File

@ -25,7 +25,7 @@ logging.warning(mango.WARNING_DISCLAIMER_TEXT)
context = mango.ContextBuilder.from_command_line_parameters(args)
wallet = mango.Wallet.from_command_line_parameters_or_raise(args)
group = mango.Group.load(context, context.group_id)
group = mango.Group.load(context, context.group_address)
account = mango.Account.load_for_owner_by_index(context, wallet.address, group, args.account_index)
market_symbol = args.market.upper()

View File

@ -32,7 +32,7 @@ if not isinstance(market, mango.SerumMarket):
raise Exception(f"Market {market_symbol} is not a Serum market: {market}")
all_open_orders_for_market = mango.OpenOrders.load_for_market_and_owner(
context, market.address, wallet.address, context.dex_program_id, market.base.decimals, market.quote.decimals)
context, market.address, wallet.address, context.serum_program_address, market.base.decimals, market.quote.decimals)
print(f"Found {len(all_open_orders_for_market)} OpenOrders account(s) for market {market.symbol}.")
for open_orders in all_open_orders_for_market:
print(open_orders)

View File

@ -43,7 +43,7 @@ logging.warning(mango.WARNING_DISCLAIMER_TEXT)
try:
context = mango.ContextBuilder.from_command_line_parameters(args)
wallet = mango.Wallet.from_command_line_parameters_or_raise(args)
group = mango.Group.load(context, context.group_id)
group = mango.Group.load(context, context.group_address)
account = mango.Account.load_for_owner_by_index(context, wallet.address, group, args.account_index)
market_symbol = args.market.upper()

View File

@ -55,7 +55,7 @@ def sols_from_lamports(lamports: Decimal) -> Decimal:
def notifier(name: str) -> typing.Callable[[mango.AccountInfo], None]:
def notify(account_info: mango.AccountInfo) -> None:
account_sols = sols_from_lamports(account_info.lamports)
report = f"Account \"{name} [{account_info.address}]\" on {context.client.cluster} has only {account_sols} SOL, which is below the minimum required balance of {args.minimum_sol_balance} SOL."
report = f"Account \"{name} [{account_info.address}]\" on {context.client.cluster_name} has only {account_sols} SOL, which is below the minimum required balance of {args.minimum_sol_balance} SOL."
send_balance_notification(report)
print(f"Notification sent: {report}")
return notify

View File

@ -29,7 +29,7 @@ logging.warning(mango.WARNING_DISCLAIMER_TEXT)
context = mango.ContextBuilder.from_command_line_parameters(args)
wallet = mango.Wallet.from_command_line_parameters_or_raise(args)
group = mango.Group.load(context, context.group_id)
group = mango.Group.load(context, context.group_address)
accounts = mango.Account.load_all_for_owner(context, wallet.address, group)
if len(accounts) == 0:
raise Exception(f"Could not find any margin accounts for '{wallet.address}'.")

View File

@ -28,7 +28,7 @@ while :
do
cancel-my-orders --name "WSMM ${MARKET} (cancel)" --market $MARKET --log-level ERROR
CURRENT_PRICE=$(fetch-price --provider serum --symbol $ORACLE_MARKET --log-level ERROR --cluster mainnet | cut -d"'" -f 2 | sed 's/,//')
CURRENT_PRICE=$(fetch-price --provider serum --symbol $ORACLE_MARKET --log-level ERROR --cluster-name mainnet | cut -d"'" -f 2 | sed 's/,//')
place-order --name "WSMM ${MARKET} (buy)" --market $MARKET --order-type LIMIT \
--log-level ERROR --side BUY --quantity $FIXED_POSITION_SIZE --price $(echo "$CURRENT_PRICE - $FIXED_SPREAD" | bc)
place-order --name "WSMM ${MARKET} (sell)" --market $MARKET --order-type LIMIT \

View File

@ -206,7 +206,7 @@ class Account(AddressableAccount):
)
]
results = context.client.get_program_accounts(context.program_id, memcmp_opts=filters)
results = context.client.get_program_accounts(context.mango_program_address, memcmp_opts=filters)
accounts = []
for account_data in results:
address = PublicKey(account_data["pubkey"])

View File

@ -134,10 +134,10 @@ UnspecifiedEncoding = "unspecified"
# some common operations better from our point of view.
#
class CompatibleClient:
def __init__(self, name: str, cluster: str, cluster_url: str, commitment: Commitment, skip_preflight: bool, instruction_reporter: InstructionReporter):
def __init__(self, name: str, cluster_name: str, cluster_url: str, commitment: Commitment, skip_preflight: bool, instruction_reporter: InstructionReporter):
self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
self.name: str = name
self.cluster: str = cluster
self.cluster_name: str = cluster_name
self.cluster_url: str = cluster_url
self.commitment: Commitment = commitment
self.skip_preflight: bool = skip_preflight
@ -334,7 +334,7 @@ class CompatibleClient:
return self._build_options(commitment, encoding_to_use, data_slice)
def __str__(self) -> str:
return f"« 𝙲𝚘𝚖𝚙𝚊𝚝𝚒𝚋𝚕𝚎𝙲𝚕𝚒𝚎𝚗𝚝 [{self.cluster}]: {self.cluster_url} »"
return f"« 𝙲𝚘𝚖𝚙𝚊𝚝𝚒𝚋𝚕𝚎𝙲𝚕𝚒𝚎𝚗𝚝 [{self.cluster_name}]: {self.cluster_url} »"
def __repr__(self) -> str:
return f"{self}"
@ -351,12 +351,12 @@ class BetterClient:
8), Decimal(16), Decimal(20), Decimal(30)]
@property
def cluster(self) -> str:
return self.compatible_client.cluster
def cluster_name(self) -> str:
return self.compatible_client.cluster_name
@cluster.setter
def cluster(self, value: str) -> None:
self.compatible_client.cluster = value
@cluster_name.setter
def cluster_name(self, value: str) -> None:
self.compatible_client.cluster_name = value
@property
def cluster_url(self) -> str:
@ -399,8 +399,8 @@ class BetterClient:
self.compatible_client.instruction_reporter = value
@staticmethod
def from_configuration(name: str, cluster: str, cluster_url: str, commitment: Commitment, skip_preflight: bool, instruction_reporter: InstructionReporter) -> "BetterClient":
compatible = CompatibleClient(name, cluster, cluster_url, commitment, skip_preflight, instruction_reporter)
def from_configuration(name: str, cluster_name: str, cluster_url: str, commitment: Commitment, skip_preflight: bool, instruction_reporter: InstructionReporter) -> "BetterClient":
compatible = CompatibleClient(name, cluster_name, cluster_url, commitment, skip_preflight, instruction_reporter)
return BetterClient(compatible)
def is_node_healthy(self) -> bool:
@ -480,7 +480,7 @@ class BetterClient:
return all_confirmed
def __str__(self) -> str:
return f"« 𝙱𝚎𝚝𝚝𝚎𝚛𝙲𝚕𝚒𝚎𝚗𝚝 [{self.cluster}]: {self.cluster_url} »"
return f"« 𝙱𝚎𝚝𝚝𝚎𝚛𝙲𝚕𝚒𝚎𝚗𝚝 [{self.cluster_name}]: {self.cluster_url} »"
def __repr__(self) -> str:
return f"{self}"

View File

@ -40,17 +40,19 @@ _pool_scheduler = ThreadPoolScheduler(multiprocessing.cpu_count())
class Context:
def __init__(self, name: str, cluster: str, cluster_url: str, skip_preflight: bool, program_id: PublicKey, dex_program_id: PublicKey,
group_name: str, group_id: PublicKey, token_lookup: TokenLookup, market_lookup: MarketLookup):
def __init__(self, name: str, cluster_name: str, cluster_url: str, skip_preflight: bool, mango_program_address: PublicKey,
serum_program_address: PublicKey, group_name: str, group_address: PublicKey,
token_lookup: TokenLookup, market_lookup: MarketLookup):
self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
self.name: str = name
instruction_reporter: InstructionReporter = CompoundInstructionReporter.from_ids(program_id, dex_program_id)
instruction_reporter: InstructionReporter = CompoundInstructionReporter.from_addresses(
mango_program_address, serum_program_address)
self.client: BetterClient = BetterClient.from_configuration(
name, cluster, cluster_url, Commitment("processed"), skip_preflight, instruction_reporter)
self.program_id: PublicKey = program_id
self.dex_program_id: PublicKey = dex_program_id
name, cluster_name, cluster_url, Commitment("processed"), skip_preflight, instruction_reporter)
self.mango_program_address: PublicKey = mango_program_address
self.serum_program_address: PublicKey = serum_program_address
self.group_name: str = group_name
self.group_id: PublicKey = group_id
self.group_address: PublicKey = group_address
self.token_lookup: TokenLookup = token_lookup
self.market_lookup: MarketLookup = market_lookup
@ -74,19 +76,19 @@ class Context:
def lookup_group_name(self, group_address: PublicKey) -> str:
group_address_str = str(group_address)
for group in MangoConstants["groups"]:
if group["cluster"] == self.client.cluster and group["publicKey"] == group_address_str:
if group["cluster"] == self.client.cluster_name and group["publicKey"] == group_address_str:
return group["name"]
return "« Unknown Group »"
def __str__(self) -> str:
return f"""« 𝙲𝚘𝚗𝚝𝚎𝚡𝚝 '{self.name}':
Cluster: {self.client.cluster}
Cluster Name: {self.client.cluster_name}
Cluster URL: {self.client.cluster_url}
Program ID: {self.program_id}
DEX Program ID: {self.dex_program_id}
Group Name: {self.group_name}
Group ID: {self.group_id}
Group Address: {self.group_address}
Mango Program Address: {self.mango_program_address}
Serum Program Address: {self.serum_program_address}
»"""
def __repr__(self) -> str:

View File

@ -43,7 +43,7 @@ from .tokenlookup import TokenLookup, CompoundTokenLookup
# * CLUSTER_URL
# * GROUP_NAME
# * GROUP_ADDRESS
# * PROGRAM_ADDRESS
# * MANGO_PROGRAM_ADDRESS
# * SERUM_PROGRAM_ADDRESS
@ -61,13 +61,13 @@ class ContextBuilder:
def add_command_line_parameters(parser: argparse.ArgumentParser, logging_default=logging.INFO) -> None:
parser.add_argument("--name", type=str, default="Mango Explorer",
help="Name of the program (used in reports and alerts)")
parser.add_argument("--cluster", type=str, default=None, help="Solana RPC cluster name")
parser.add_argument("--cluster-name", type=str, default=None, help="Solana RPC cluster name")
parser.add_argument("--cluster-url", type=str, default=None, help="Solana RPC cluster URL")
parser.add_argument("--skip-preflight", default=False, action="store_true", help="Skip pre-flight checks")
parser.add_argument("--program-id", type=PublicKey, default=None, help="Mango program ID/address")
parser.add_argument("--dex-program-id", type=PublicKey, default=None, help="DEX program ID/address")
parser.add_argument("--group-name", type=str, default=None, help="Mango group name")
parser.add_argument("--group-id", type=PublicKey, default=None, help="Mango group ID/address")
parser.add_argument("--group-address", type=PublicKey, default=None, help="Mango group address")
parser.add_argument("--mango-program-address", type=PublicKey, default=None, help="Mango program address")
parser.add_argument("--serum-program-address", type=PublicKey, default=None, help="Serum program address")
parser.add_argument("--skip-preflight", default=False, action="store_true", help="Skip pre-flight checks")
parser.add_argument("--token-data-file", type=str, default=SplTokenLookup.DefaultDataFilepath,
help="data file that contains token symbols, names, mints and decimals (format is same as https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/solana.tokenlist.json)")
@ -85,25 +85,17 @@ class ContextBuilder:
#
@staticmethod
def from_command_line_parameters(args: argparse.Namespace) -> "Context":
# Here we should have values for all our parameters (because they'll either be specified
# on the command-line or will be the default_* value) but we may be in the situation where
# a group name is specified but not a group ID, and in that case we want to look up the
# group ID.
#
# In that situation, the group_name will not be default_group_name but the group_id will
# still be default_group_id. In that situation we want to override what we were passed
# as the group_id.
name: typing.Optional[str] = args.name
group_name: typing.Optional[str] = args.group_name
cluster: typing.Optional[str] = args.cluster
cluster_name: typing.Optional[str] = args.cluster_name
cluster_url: typing.Optional[str] = args.cluster_url
group_name: typing.Optional[str] = args.group_name
group_address: typing.Optional[PublicKey] = args.group_address
mango_program_address: typing.Optional[PublicKey] = args.mango_program_address
serum_program_address: typing.Optional[PublicKey] = args.serum_program_address
skip_preflight: bool = bool(args.skip_preflight)
group_id: typing.Optional[PublicKey] = args.group_id
program_id: typing.Optional[PublicKey] = args.program_id
dex_program_id: typing.Optional[PublicKey] = args.dex_program_id
token_filename: str = args.token_data_file
return ContextBuilder._build(name, cluster, cluster_url, skip_preflight, group_name, group_id, program_id, dex_program_id, token_filename)
return ContextBuilder._build(name, cluster_name, cluster_url, skip_preflight, group_name, group_address, mango_program_address, serum_program_address, token_filename)
@staticmethod
def default():
@ -111,21 +103,21 @@ class ContextBuilder:
@staticmethod
def from_group_name(context: Context, group_name: str) -> Context:
return ContextBuilder._build(context.name, context.client.cluster, context.client.cluster_url,
return ContextBuilder._build(context.name, context.client.cluster_name, context.client.cluster_url,
context.client.skip_preflight, group_name, None,
None, None, SplTokenLookup.DefaultDataFilepath)
@staticmethod
def forced_to_devnet(context: Context) -> Context:
cluster: str = "devnet"
cluster_url: str = MangoConstants["cluster_urls"][cluster]
return ContextBuilder._build(context.name, cluster, cluster_url, context.client.skip_preflight, context.group_name, context.group_id, context.program_id, context.dex_program_id, SplTokenLookup.DefaultDataFilepath)
cluster_name: str = "devnet"
cluster_url: str = MangoConstants["cluster_urls"][cluster_name]
return ContextBuilder._build(context.name, cluster_name, cluster_url, context.client.skip_preflight, context.group_name, context.group_address, context.mango_program_address, context.serum_program_address, SplTokenLookup.DefaultDataFilepath)
@staticmethod
def forced_to_mainnet_beta(context: Context) -> Context:
cluster: str = "mainnet"
cluster_url: str = MangoConstants["cluster_urls"][cluster]
return ContextBuilder._build(context.name, cluster, cluster_url, context.client.skip_preflight, context.group_name, context.group_id, context.program_id, context.dex_program_id, SplTokenLookup.DefaultDataFilepath)
cluster_name: str = "mainnet"
cluster_url: str = MangoConstants["cluster_urls"][cluster_name]
return ContextBuilder._build(context.name, cluster_name, cluster_url, context.client.skip_preflight, context.group_name, context.group_address, context.mango_program_address, context.serum_program_address, SplTokenLookup.DefaultDataFilepath)
# This function is the converse of `add_command_line_parameters()` - it takes
# an argument of parsed command-line parameters and expects to see the ones it added
@ -134,7 +126,7 @@ class ContextBuilder:
# It then uses those parameters to create a properly-configured `Context` object.
#
@staticmethod
def _build(name: typing.Optional[str], cluster: typing.Optional[str], cluster_url: typing.Optional[str],
def _build(name: typing.Optional[str], cluster_name: typing.Optional[str], cluster_url: typing.Optional[str],
skip_preflight: bool, group_name: typing.Optional[str], group_address: typing.Optional[PublicKey],
program_address: typing.Optional[PublicKey], serum_program_address: typing.Optional[PublicKey],
token_filename: str) -> "Context":
@ -144,7 +136,7 @@ class ContextBuilder:
return None
default_group_data = MangoConstants["groups"][0]
actual_name: str = name or os.environ.get("NAME") or "Mango Explorer"
actual_cluster: str = cluster or os.environ.get("CLUSTER") or default_group_data["cluster"]
actual_cluster: str = cluster_name or os.environ.get("CLUSTER") or default_group_data["cluster"]
actual_cluster_url: str = cluster_url or os.environ.get(
"CLUSTER_URL") or MangoConstants["cluster_urls"][actual_cluster]
actual_skip_preflight: bool = skip_preflight
@ -156,12 +148,12 @@ class ContextBuilder:
found_group_data = group
if found_group_data is None:
raise Exception(f"Could not find group named '{actual_group_name}' in cluster '{actual_cluster}.")
raise Exception(f"Could not find group named '{actual_group_name}' in cluster_name '{actual_cluster}.")
actual_group_address: PublicKey = group_address or public_key_or_none(os.environ.get(
"GROUP_ADDRESS")) or PublicKey(found_group_data["publicKey"])
actual_program_address: PublicKey = program_address or public_key_or_none(os.environ.get(
"PROGRAM_ADDRESS")) or PublicKey(found_group_data["mangoProgramId"])
"MANGO_PROGRAM_ADDRESS")) or PublicKey(found_group_data["mangoProgramId"])
actual_serum_program_address: PublicKey = serum_program_address or public_key_or_none(os.environ.get(
"SERUM_PROGRAM_ADDRESS")) or PublicKey(found_group_data["serumProgramId"])

View File

@ -82,7 +82,7 @@ class Group(AddressableAccount):
basket_indices: typing.Sequence[bool],
basket: typing.Sequence[GroupBasketMarket],
signer_nonce: Decimal, signer_key: PublicKey,
admin: PublicKey, dex_program_id: PublicKey, cache: PublicKey, valid_interval: Decimal,
admin: PublicKey, serum_program_address: PublicKey, cache: PublicKey, valid_interval: Decimal,
dao_vault: PublicKey, srm_vault: PublicKey, msrm_vault: PublicKey):
super().__init__(account_info)
self.version: Version = version
@ -95,7 +95,7 @@ class Group(AddressableAccount):
self.signer_nonce: Decimal = signer_nonce
self.signer_key: PublicKey = signer_key
self.admin: PublicKey = admin
self.dex_program_id: PublicKey = dex_program_id
self.serum_program_address: PublicKey = serum_program_address
self.cache: PublicKey = cache
self.valid_interval: Decimal = valid_interval
self.dao_vault: PublicKey = dao_vault
@ -151,14 +151,14 @@ class Group(AddressableAccount):
signer_nonce: Decimal = layout.signer_nonce
signer_key: PublicKey = layout.signer_key
admin: PublicKey = layout.admin
dex_program_id: PublicKey = layout.dex_program_id
serum_program_address: PublicKey = layout.serum_program_address
cache: PublicKey = layout.cache
valid_interval: Decimal = layout.valid_interval
dao_vault: PublicKey = layout.dao_vault
srm_vault: PublicKey = layout.srm_vault
msrm_vault: PublicKey = layout.msrm_vault
return Group(account_info, version, name, meta_data, quote_token_info, in_basket, basket, signer_nonce, signer_key, admin, dex_program_id, cache, valid_interval, dao_vault, srm_vault, msrm_vault)
return Group(account_info, version, name, meta_data, quote_token_info, in_basket, basket, signer_nonce, signer_key, admin, serum_program_address, cache, valid_interval, dao_vault, srm_vault, msrm_vault)
@staticmethod
def parse(context: Context, account_info: AccountInfo) -> "Group":
@ -186,7 +186,7 @@ class Group(AddressableAccount):
@staticmethod
def load(context: Context, address: typing.Optional[PublicKey] = None) -> "Group":
group_address: PublicKey = address or context.group_id
group_address: PublicKey = address or context.group_address
account_info = AccountInfo.load(context, group_address)
if account_info is None:
raise Exception(f"Group account not found at address '{group_address}'")
@ -239,7 +239,7 @@ class Group(AddressableAccount):
Name: {self.name}
Signer [Nonce: {self.signer_nonce}]: {self.signer_key}
Admin: {self.admin}
DEX Program ID: {self.dex_program_id}
DEX Program ID: {self.serum_program_address}
Cache: {self.cache}
DAO Vault: {self.dao_vault}
SRM Vault: {self.srm_vault}

View File

@ -45,20 +45,20 @@ class IdsJsonMarketType(enum.Enum):
class IdsJsonMarketLookup(MarketLookup):
def __init__(self, cluster: str) -> None:
def __init__(self, cluster_name: str) -> None:
super().__init__()
self.cluster: str = cluster
self.cluster_name: str = cluster_name
@staticmethod
def _from_dict(market_type: IdsJsonMarketType, program_id: PublicKey, group_address: PublicKey, data: typing.Dict, tokens: typing.Sequence[Token], quote_symbol: str) -> Market:
def _from_dict(market_type: IdsJsonMarketType, mango_program_address: PublicKey, group_address: PublicKey, data: typing.Dict, tokens: typing.Sequence[Token], quote_symbol: str) -> Market:
base_symbol = data["baseSymbol"]
base = Token.find_by_symbol(tokens, base_symbol)
quote = Token.find_by_symbol(tokens, quote_symbol)
address = PublicKey(data["publicKey"])
if market_type == IdsJsonMarketType.PERP:
return PerpMarketStub(program_id, address, base, quote, group_address)
return PerpMarketStub(mango_program_address, address, base, quote, group_address)
else:
return SpotMarketStub(program_id, address, base, quote, group_address)
return SpotMarketStub(mango_program_address, address, base, quote, group_address)
@staticmethod
def _load_tokens(data: typing.Dict) -> typing.Sequence[Token]:
@ -80,51 +80,51 @@ class IdsJsonMarketLookup(MarketLookup):
check_spots = False # Skip spot markets because we're explicitly told it's a perp
for group in MangoConstants["groups"]:
if group["cluster"] == self.cluster:
if group["cluster"] == self.cluster_name:
group_address: PublicKey = PublicKey(group["publicKey"])
program_id: PublicKey = PublicKey(group["mangoProgramId"])
mango_program_address: PublicKey = PublicKey(group["mangoProgramId"])
if check_perps:
for market_data in group["perpMarkets"]:
if market_data["name"].upper() == symbol.upper():
tokens = IdsJsonMarketLookup._load_tokens(group["tokens"])
return IdsJsonMarketLookup._from_dict(IdsJsonMarketType.PERP, program_id, group_address, market_data, tokens, group["quoteSymbol"])
return IdsJsonMarketLookup._from_dict(IdsJsonMarketType.PERP, mango_program_address, group_address, market_data, tokens, group["quoteSymbol"])
if check_spots:
for market_data in group["spotMarkets"]:
if market_data["name"].upper() == symbol.upper():
tokens = IdsJsonMarketLookup._load_tokens(group["tokens"])
return IdsJsonMarketLookup._from_dict(IdsJsonMarketType.SPOT, program_id, group_address, market_data, tokens, group["quoteSymbol"])
return IdsJsonMarketLookup._from_dict(IdsJsonMarketType.SPOT, mango_program_address, group_address, market_data, tokens, group["quoteSymbol"])
return None
def find_by_address(self, address: PublicKey) -> typing.Optional[Market]:
for group in MangoConstants["groups"]:
if group["cluster"] == self.cluster:
if group["cluster"] == self.cluster_name:
group_address: PublicKey = PublicKey(group["publicKey"])
program_id: PublicKey = PublicKey(group["mangoProgramId"])
mango_program_address: PublicKey = PublicKey(group["mangoProgramId"])
for market_data in group["perpMarkets"]:
if market_data["key"] == str(address):
tokens = IdsJsonMarketLookup._load_tokens(group["tokens"])
return IdsJsonMarketLookup._from_dict(IdsJsonMarketType.PERP, program_id, group_address, market_data, tokens, group["quoteSymbol"])
return IdsJsonMarketLookup._from_dict(IdsJsonMarketType.PERP, mango_program_address, group_address, market_data, tokens, group["quoteSymbol"])
for market_data in group["spotMarkets"]:
if market_data["key"] == str(address):
tokens = IdsJsonMarketLookup._load_tokens(group["tokens"])
return IdsJsonMarketLookup._from_dict(IdsJsonMarketType.SPOT, program_id, group_address, market_data, tokens, group["quoteSymbol"])
return IdsJsonMarketLookup._from_dict(IdsJsonMarketType.SPOT, mango_program_address, group_address, market_data, tokens, group["quoteSymbol"])
return None
def all_markets(self) -> typing.Sequence[Market]:
markets = []
for group in MangoConstants["groups"]:
if group["cluster"] == self.cluster:
if group["cluster"] == self.cluster_name:
group_address: PublicKey = PublicKey(group["publicKey"])
program_id: PublicKey = PublicKey(group["mangoProgramId"])
mango_program_address: PublicKey = PublicKey(group["mangoProgramId"])
for market_data in group["perpMarkets"]:
tokens = IdsJsonMarketLookup._load_tokens(group["tokens"])
market = IdsJsonMarketLookup._from_dict(
IdsJsonMarketType.PERP, program_id, group_address, market_data, tokens, group["quoteSymbol"])
IdsJsonMarketType.PERP, mango_program_address, group_address, market_data, tokens, group["quoteSymbol"])
markets = [market]
for market_data in group["spotMarkets"]:
tokens = IdsJsonMarketLookup._load_tokens(group["tokens"])
market = IdsJsonMarketLookup._from_dict(
IdsJsonMarketType.SPOT, program_id, group_address, market_data, tokens, group["quoteSymbol"])
IdsJsonMarketType.SPOT, mango_program_address, group_address, market_data, tokens, group["quoteSymbol"])
markets = [market]
return markets

View File

@ -29,14 +29,14 @@ from .tokenlookup import TokenLookup
#
class IdsJsonTokenLookup(TokenLookup):
def __init__(self, cluster: str, group_name: str) -> None:
def __init__(self, cluster_name: str, group_name: str) -> None:
super().__init__()
self.cluster: str = cluster
self.cluster_name: str = cluster_name
self.group_name: str = group_name
def find_by_symbol(self, symbol: str) -> typing.Optional[Token]:
for group in MangoConstants["groups"]:
if group["cluster"] == self.cluster and group["name"] == self.group_name:
if group["cluster"] == self.cluster_name and group["name"] == self.group_name:
for token in group["tokens"]:
if token["symbol"] == symbol:
return Token(token["symbol"], token["symbol"], PublicKey(token["mintKey"]), Decimal(token["decimals"]))
@ -45,7 +45,7 @@ class IdsJsonTokenLookup(TokenLookup):
def find_by_mint(self, mint: PublicKey) -> typing.Optional[Token]:
mint_str = str(mint)
for group in MangoConstants["groups"]:
if group["cluster"] == self.cluster and group["name"] == self.group_name:
if group["cluster"] == self.cluster_name and group["name"] == self.group_name:
for token in group["tokens"]:
if token["mintKey"] == mint_str:
return Token(token["symbol"], token["symbol"], PublicKey(token["mintKey"]), Decimal(token["decimals"]))

View File

@ -48,11 +48,11 @@ class InstructionReporter:
# The `SerumInstructionParser` class knows a bit more about Serum instructions.
#
class SerumInstructionReporter(InstructionReporter):
def __init__(self, dex_program_id: PublicKey):
self.dex_program_id: PublicKey = dex_program_id
def __init__(self, serum_program_address: PublicKey):
self.serum_program_address: PublicKey = serum_program_address
def matches(self, instruction: TransactionInstruction) -> bool:
return instruction.program_id == self.dex_program_id
return instruction.program_id == self.serum_program_address
def report(self, instruction: TransactionInstruction) -> str:
initial = layouts.SERUM_INSTRUCTION_VARIANT_FINDER.parse(instruction.data)
@ -65,11 +65,11 @@ class SerumInstructionReporter(InstructionReporter):
# The `MangoInstructionReporter` class knows a bit more about Mango instructions.
#
class MangoInstructionReporter(InstructionReporter):
def __init__(self, program_id: PublicKey):
self.program_id: PublicKey = program_id
def __init__(self, mango_program_address: PublicKey):
self.mango_program_address: PublicKey = mango_program_address
def matches(self, instruction: TransactionInstruction) -> bool:
return instruction.program_id == self.program_id
return instruction.program_id == self.mango_program_address
def report(self, instruction: TransactionInstruction) -> str:
initial = layouts.MANGO_INSTRUCTION_VARIANT_FINDER.parse(instruction.data)
@ -107,8 +107,8 @@ class CompoundInstructionReporter(InstructionReporter):
f"Could not find instruction reporter for instruction {instruction}.")
@staticmethod
def from_ids(program_id: PublicKey, dex_program_id: PublicKey) -> InstructionReporter:
def from_addresses(mango_program_address: PublicKey, serum_program_address: PublicKey) -> InstructionReporter:
base: InstructionReporter = InstructionReporter()
serum: InstructionReporter = SerumInstructionReporter(dex_program_id)
mango: InstructionReporter = MangoInstructionReporter(program_id)
serum: InstructionReporter = SerumInstructionReporter(serum_program_address)
mango: InstructionReporter = MangoInstructionReporter(mango_program_address)
return CompoundInstructionReporter([mango, serum, base])

View File

@ -66,11 +66,11 @@ from .wallet import Wallet
# necesary.
#
def build_create_solana_account_instructions(context: Context, wallet: Wallet, program_id: PublicKey, size: int, lamports: int = 0) -> CombinableInstructions:
def build_create_solana_account_instructions(context: Context, wallet: Wallet, mango_program_address: PublicKey, size: int, lamports: int = 0) -> CombinableInstructions:
minimum_balance = context.client.get_minimum_balance_for_rent_exemption(size)
account = SolanaAccount()
create_instruction = create_account(
CreateAccountParams(wallet.address, account.public_key(), lamports + minimum_balance, size, program_id))
CreateAccountParams(wallet.address, account.public_key(), lamports + minimum_balance, size, mango_program_address))
return CombinableInstructions(signers=[account], instructions=[create_instruction])
@ -177,7 +177,7 @@ def build_serum_consume_events_instructions(context: Context, market_address: Pu
AccountMeta(pubkey=pubkey, is_signer=False, is_writable=True)
for pubkey in [*open_orders_addresses, market_address, event_queue_address]
],
program_id=context.dex_program_id,
program_id=context.serum_program_address,
data=PYSERUM_INSTRUCTIONS_LAYOUT.build(
dict(instruction_type=PySerumInstructionType.CONSUME_EVENTS, args=dict(limit=limit))
),
@ -260,7 +260,7 @@ def build_spot_settle_instructions(context: Context, wallet: Wallet, account: Ac
AccountMeta(is_signer=False, is_writable=True, pubkey=group.cache),
AccountMeta(is_signer=True, is_writable=False, pubkey=wallet.address),
AccountMeta(is_signer=False, is_writable=True, pubkey=account.address),
AccountMeta(is_signer=False, is_writable=False, pubkey=context.dex_program_id),
AccountMeta(is_signer=False, is_writable=False, pubkey=context.serum_program_address),
AccountMeta(is_signer=False, is_writable=True, pubkey=market.state.public_key()),
AccountMeta(is_signer=False, is_writable=True, pubkey=open_orders_address),
AccountMeta(is_signer=False, is_writable=False, pubkey=group.signer_key),
@ -275,7 +275,7 @@ def build_spot_settle_instructions(context: Context, wallet: Wallet, account: Ac
AccountMeta(is_signer=False, is_writable=False, pubkey=vault_signer),
AccountMeta(is_signer=False, is_writable=False, pubkey=TOKEN_PROGRAM_ID)
],
program_id=context.program_id,
program_id=context.mango_program_address,
data=layouts.SETTLE_FUNDS.build(dict())
)
@ -352,7 +352,7 @@ def build_cancel_perp_order_instructions(context: Context, wallet: Wallet, accou
AccountMeta(is_signer=False, is_writable=True, pubkey=perp_market_details.bids),
AccountMeta(is_signer=False, is_writable=True, pubkey=perp_market_details.asks)
],
program_id=context.program_id,
program_id=context.mango_program_address,
data=data
)
]
@ -399,7 +399,7 @@ def build_place_perp_order_instructions(context: Context, wallet: Wallet, group:
*list([AccountMeta(is_signer=False, is_writable=False,
pubkey=oo_address or SYSTEM_PROGRAM_ADDRESS) for oo_address in account.spot_open_orders])
],
program_id=context.program_id,
program_id=context.mango_program_address,
data=layouts.PLACE_PERP_ORDER.build(
{
"price": native_price,
@ -435,7 +435,7 @@ def build_mango_consume_events_instructions(context: Context, group: Group, perp
*list([AccountMeta(is_signer=False, is_writable=True,
pubkey=account_address) for account_address in account_addresses])
],
program_id=context.program_id,
program_id=context.mango_program_address,
data=layouts.CONSUME_EVENTS.build(
{
"limit": limit,
@ -447,7 +447,7 @@ def build_mango_consume_events_instructions(context: Context, group: Group, perp
def build_create_account_instructions(context: Context, wallet: Wallet, group: Group) -> CombinableInstructions:
create_account_instructions = build_create_solana_account_instructions(
context, wallet, context.program_id, layouts.MANGO_ACCOUNT.sizeof())
context, wallet, context.mango_program_address, layouts.MANGO_ACCOUNT.sizeof())
mango_account_address = create_account_instructions.signers[0].public_key()
# /// 0. `[]` mango_group_ai - Group that this mango account is for
@ -460,7 +460,7 @@ def build_create_account_instructions(context: Context, wallet: Wallet, group: G
AccountMeta(is_signer=False, is_writable=True, pubkey=mango_account_address),
AccountMeta(is_signer=True, is_writable=False, pubkey=wallet.address)
],
program_id=context.program_id,
program_id=context.mango_program_address,
data=layouts.INIT_MANGO_ACCOUNT.build({})
)
return create_account_instructions + CombinableInstructions(signers=[], instructions=[init])
@ -496,7 +496,7 @@ def build_deposit_instructions(context: Context, wallet: Wallet, group: Group, a
AccountMeta(is_signer=False, is_writable=False, pubkey=TOKEN_PROGRAM_ID),
AccountMeta(is_signer=False, is_writable=True, pubkey=token_account.address)
],
program_id=context.program_id,
program_id=context.mango_program_address,
data=layouts.DEPOSIT.build({
"quantity": value
})
@ -544,7 +544,7 @@ def build_withdraw_instructions(context: Context, wallet: Wallet, group: Group,
*list([AccountMeta(is_signer=False, is_writable=False,
pubkey=oo_address or SYSTEM_PROGRAM_ADDRESS) for oo_address in account.spot_open_orders])
],
program_id=context.program_id,
program_id=context.mango_program_address,
data=layouts.WITHDRAW.build({
"quantity": value,
"allow_borrow": allow_borrow
@ -557,7 +557,7 @@ def build_withdraw_instructions(context: Context, wallet: Wallet, group: Group,
def build_spot_openorders_instructions(context: Context, wallet: Wallet, group: Group, account: Account, market: PySerumMarket) -> CombinableInstructions:
instructions: CombinableInstructions = CombinableInstructions.empty()
create_open_orders = build_create_solana_account_instructions(
context, wallet, context.dex_program_id, layouts.OPEN_ORDERS.sizeof())
context, wallet, context.serum_program_address, layouts.OPEN_ORDERS.sizeof())
instructions += create_open_orders
open_orders_address = create_open_orders.signers[0].public_key()
@ -567,13 +567,13 @@ def build_spot_openorders_instructions(context: Context, wallet: Wallet, group:
AccountMeta(is_signer=False, is_writable=False, pubkey=group.address),
AccountMeta(is_signer=False, is_writable=True, pubkey=account.address),
AccountMeta(is_signer=True, is_writable=False, pubkey=wallet.address),
AccountMeta(is_signer=False, is_writable=False, pubkey=context.dex_program_id),
AccountMeta(is_signer=False, is_writable=False, pubkey=context.serum_program_address),
AccountMeta(is_signer=False, is_writable=True, pubkey=open_orders_address),
AccountMeta(is_signer=False, is_writable=False, pubkey=market.state.public_key()),
AccountMeta(is_signer=False, is_writable=False, pubkey=group.signer_key),
AccountMeta(is_signer=False, is_writable=False, pubkey=SYSVAR_RENT_PUBKEY)
],
program_id=context.program_id,
program_id=context.mango_program_address,
data=layouts.INIT_SPOT_OPEN_ORDERS.build(dict())
)
instructions += CombinableInstructions(signers=[], instructions=[initialise_open_orders_instruction])
@ -666,7 +666,7 @@ def build_spot_place_order_instructions(context: Context, wallet: Wallet, group:
AccountMeta(is_signer=False, is_writable=True, pubkey=account.address),
AccountMeta(is_signer=True, is_writable=False, pubkey=wallet.address),
AccountMeta(is_signer=False, is_writable=False, pubkey=group.cache),
AccountMeta(is_signer=False, is_writable=False, pubkey=context.dex_program_id),
AccountMeta(is_signer=False, is_writable=False, pubkey=context.serum_program_address),
AccountMeta(is_signer=False, is_writable=True, pubkey=market.state.public_key()),
AccountMeta(is_signer=False, is_writable=True, pubkey=market.state.bids()),
AccountMeta(is_signer=False, is_writable=True, pubkey=market.state.asks()),
@ -690,7 +690,7 @@ def build_spot_place_order_instructions(context: Context, wallet: Wallet, group:
pubkey=oo_address or SYSTEM_PROGRAM_ADDRESS) for oo_address in account.spot_open_orders]),
*fee_discount_address_meta
],
program_id=context.program_id,
program_id=context.mango_program_address,
data=layouts.PLACE_SPOT_ORDER.build(
dict(
side=serum_side,
@ -736,7 +736,7 @@ def build_cancel_spot_order_instructions(context: Context, wallet: Wallet, group
AccountMeta(is_signer=False, is_writable=False, pubkey=group.address),
AccountMeta(is_signer=True, is_writable=False, pubkey=wallet.address),
AccountMeta(is_signer=False, is_writable=False, pubkey=account.address),
AccountMeta(is_signer=False, is_writable=False, pubkey=context.dex_program_id),
AccountMeta(is_signer=False, is_writable=False, pubkey=context.serum_program_address),
AccountMeta(is_signer=False, is_writable=True, pubkey=market.state.public_key()),
AccountMeta(is_signer=False, is_writable=True, pubkey=market.state.bids()),
AccountMeta(is_signer=False, is_writable=True, pubkey=market.state.asks()),
@ -744,7 +744,7 @@ def build_cancel_spot_order_instructions(context: Context, wallet: Wallet, group
AccountMeta(is_signer=False, is_writable=False, pubkey=group.signer_key),
AccountMeta(is_signer=False, is_writable=True, pubkey=market.state.event_queue())
],
program_id=context.program_id,
program_id=context.mango_program_address,
data=layouts.CANCEL_SPOT_ORDER.build(
{
"order_id": order.id,
@ -815,7 +815,7 @@ def build_redeem_accrued_mango_instructions(context: Context, wallet: Wallet, pe
AccountMeta(is_signer=False, is_writable=False, pubkey=group.signer_key),
AccountMeta(is_signer=False, is_writable=False, pubkey=TOKEN_PROGRAM_ID)
],
program_id=context.program_id,
program_id=context.mango_program_address,
data=layouts.REDEEM_MNGO.build(dict())
)
return CombinableInstructions(signers=[], instructions=[redeem_accrued_mango_instruction])

View File

@ -450,7 +450,7 @@ PERP_MARKET_INFO = construct.Struct(
# pub signer_nonce: u64,
# pub signer_key: Pubkey,
# pub admin: Pubkey, // Used to add new markets and adjust risk params
# pub dex_program_id: Pubkey, // Consider allowing more
# pub serum_program_address: Pubkey, // Consider allowing more
# pub mango_cache: Pubkey,
# pub valid_interval: u64,
#
@ -473,7 +473,7 @@ GROUP = construct.Struct(
"signer_nonce" / DecimalAdapter(),
"signer_key" / PublicKeyAdapter(),
"admin" / PublicKeyAdapter(),
"dex_program_id" / PublicKeyAdapter(),
"serum_program_address" / PublicKeyAdapter(),
"cache" / PublicKeyAdapter(),
"valid_interval" / DecimalAdapter(),
"dao_vault" / PublicKeyAdapter(),

View File

@ -39,11 +39,10 @@ class InventorySource(enum.Enum):
#
# This class describes a crypto market. It *must* have an address, a base token and a quote token.
#
class Market(metaclass=abc.ABCMeta):
def __init__(self, program_id: PublicKey, address: PublicKey, inventory_source: InventorySource, base: Token, quote: Token, lot_size_converter: LotSizeConverter):
def __init__(self, program_address: PublicKey, address: PublicKey, inventory_source: InventorySource, base: Token, quote: Token, lot_size_converter: LotSizeConverter):
self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
self.program_id: PublicKey = program_id
self.program_address: PublicKey = program_address
self.address: PublicKey = address
self.inventory_source: InventorySource = inventory_source
self.base: Token = base

View File

@ -76,7 +76,7 @@ def _polling_serum_model_state_builder_factory(context: mango.Context, wallet: m
raise Exception(
f"Could not find token account owned by {wallet.address} for quote token {market.quote}.")
all_open_orders = mango.OpenOrders.load_for_market_and_owner(
context, market.address, wallet.address, context.dex_program_id, market.base.decimals, market.quote.decimals)
context, market.address, wallet.address, context.serum_program_address, market.base.decimals, market.quote.decimals)
if len(all_open_orders) == 0:
raise Exception(
f"Could not find serum openorders account owned by {wallet.address} for market {market.symbol}.")

View File

@ -36,14 +36,14 @@ from .version import Version
class OpenOrders(AddressableAccount):
def __init__(self, account_info: AccountInfo, version: Version, program_id: PublicKey,
def __init__(self, account_info: AccountInfo, version: Version, program_address: PublicKey,
account_flags: AccountFlags, market: PublicKey, owner: PublicKey,
base_token_free: Decimal, base_token_total: Decimal, quote_token_free: Decimal,
quote_token_total: Decimal, placed_orders: typing.Sequence[PlacedOrder],
referrer_rebate_accrued: Decimal):
super().__init__(account_info)
self.version: Version = version
self.program_id: PublicKey = program_id
self.program_address: PublicKey = program_address
self.account_flags: AccountFlags = account_flags
self.market: PublicKey = market
self.owner: PublicKey = owner
@ -62,7 +62,7 @@ class OpenOrders(AddressableAccount):
def from_layout(layout: layouts.OPEN_ORDERS, account_info: AccountInfo,
base_decimals: Decimal, quote_decimals: Decimal) -> "OpenOrders":
account_flags = AccountFlags.from_layout(layout.account_flags)
program_id = account_info.owner
program_address = account_info.owner
base_divisor = 10 ** base_decimals
quote_divisor = 10 ** quote_decimals
@ -75,7 +75,7 @@ class OpenOrders(AddressableAccount):
if account_flags.initialized:
placed_orders = PlacedOrder.build_from_open_orders_data(
layout.free_slot_bits, layout.is_bid_bits, layout.orders, layout.client_ids)
return OpenOrders(account_info, Version.UNSPECIFIED, program_id, account_flags, layout.market,
return OpenOrders(account_info, Version.UNSPECIFIED, program_address, account_flags, layout.market,
layout.owner, base_token_free, base_token_total, quote_token_free,
quote_token_total, placed_orders, layout.referrer_rebate_accrued)
@ -98,7 +98,7 @@ class OpenOrders(AddressableAccount):
]
results = context.client.get_program_accounts(
group.dex_program_id, data_size=layouts.OPEN_ORDERS.sizeof(), memcmp_opts=filters)
group.serum_program_address, data_size=layouts.OPEN_ORDERS.sizeof(), memcmp_opts=filters)
account_infos = list(map(lambda pair: AccountInfo._from_response_values(pair[0], pair[1]), [
(result["account"], PublicKey(result["pubkey"])) for result in results]))
account_infos_by_address = {key: value for key, value in [
@ -113,7 +113,7 @@ class OpenOrders(AddressableAccount):
return OpenOrders.parse(open_orders_account, base_decimals, quote_decimals)
@staticmethod
def load_for_market_and_owner(context: Context, market: PublicKey, owner: PublicKey, program_id: PublicKey, base_decimals: Decimal, quote_decimals: Decimal):
def load_for_market_and_owner(context: Context, market: PublicKey, owner: PublicKey, program_address: PublicKey, base_decimals: Decimal, quote_decimals: Decimal):
filters = [
MemcmpOpts(
offset=layouts.ACCOUNT_FLAGS.sizeof() + 5,
@ -126,7 +126,7 @@ class OpenOrders(AddressableAccount):
]
results = context.client.get_program_accounts(
program_id, data_size=layouts.OPEN_ORDERS.sizeof(), memcmp_opts=filters)
program_address, data_size=layouts.OPEN_ORDERS.sizeof(), memcmp_opts=filters)
accounts = map(lambda result: AccountInfo._from_response_values(
result["account"], PublicKey(result["pubkey"])), results)
return list(map(lambda acc: OpenOrders.parse(acc, base_decimals, quote_decimals), accounts))
@ -136,7 +136,7 @@ class OpenOrders(AddressableAccount):
return f"""« OpenOrders [{self.address}]:
Flags: {self.account_flags}
Program ID: {self.program_id}
Program ID: {self.program_address}
Market: {self.market}
Owner: {self.owner}
Base Token: {self.base_token_free:,.8f} of {self.base_token_total:,.8f}

View File

@ -110,7 +110,7 @@ class PythOracle(Oracle):
class PythOracleProvider(OracleProvider):
def __init__(self, context: Context) -> None:
self.address: PublicKey = PYTH_MAINNET_MAPPING_ROOT if context.client.cluster == "mainnet" else PYTH_DEVNET_MAPPING_ROOT
self.address: PublicKey = PYTH_MAINNET_MAPPING_ROOT if context.client.cluster_name == "mainnet" else PYTH_DEVNET_MAPPING_ROOT
super().__init__(f"Pyth Oracle Factory [{self.address}]")
self.context: Context = context

View File

@ -74,7 +74,7 @@ class SerumOracle(Oracle):
context.client.skip_preflight,
context.client.instruction_reporter)
mainnet_serum_market_lookup: SerumMarketLookup = SerumMarketLookup.load(
context.dex_program_id, SplTokenLookup.DefaultDataFilepath)
context.serum_program_address, SplTokenLookup.DefaultDataFilepath)
adjusted_market = self.market
mainnet_adjusted_market: typing.Optional[Market] = mainnet_serum_market_lookup.find_by_symbol(
self.market.symbol)
@ -111,7 +111,7 @@ class SerumOracleProvider(OracleProvider):
def oracle_for_market(self, context: Context, market: Market) -> typing.Optional[Oracle]:
loaded_market: Market = ensure_market_loaded(context, market)
if isinstance(loaded_market, SpotMarket):
serum_market = SerumMarket(context.dex_program_id, loaded_market.address, loaded_market.base,
serum_market = SerumMarket(context.serum_program_address, loaded_market.address, loaded_market.base,
loaded_market.quote, loaded_market.underlying_serum_market)
return SerumOracle(serum_market)
elif isinstance(loaded_market, SerumMarket):

View File

@ -34,8 +34,8 @@ from .token import Token
# This class encapsulates our knowledge of a Mango perps market.
#
class PerpMarket(Market):
def __init__(self, program_id: PublicKey, address: PublicKey, base: Token, quote: Token, underlying_perp_market: PerpMarketDetails):
super().__init__(program_id, address, InventorySource.ACCOUNT, base, quote, RaisingLotSizeConverter())
def __init__(self, mango_program_address: PublicKey, address: PublicKey, base: Token, quote: Token, underlying_perp_market: PerpMarketDetails):
super().__init__(mango_program_address, address, InventorySource.ACCOUNT, base, quote, RaisingLotSizeConverter())
self.underlying_perp_market: PerpMarketDetails = underlying_perp_market
self.lot_size_converter: LotSizeConverter = LotSizeConverter(
base, underlying_perp_market.base_lot_size, quote, underlying_perp_market.quote_lot_size)
@ -81,7 +81,7 @@ class PerpMarket(Market):
def __str__(self) -> str:
underlying: str = f"{self.underlying_perp_market}".replace("\n", "\n ")
return f"""« 𝙿𝚎𝚛𝚙𝙼𝚊𝚛𝚔𝚎𝚝 {self.symbol} {self.address} [{self.program_id}]
return f"""« 𝙿𝚎𝚛𝚙𝙼𝚊𝚛𝚔𝚎𝚝 {self.symbol} {self.address} [{self.program_address}]
{underlying}
»"""
@ -91,18 +91,18 @@ class PerpMarket(Market):
# This class holds information to load a `PerpMarket` object but doesn't automatically load it.
#
class PerpMarketStub(Market):
def __init__(self, program_id: PublicKey, address: PublicKey, base: Token, quote: Token, group_address: PublicKey):
super().__init__(program_id, address, InventorySource.ACCOUNT, base, quote, RaisingLotSizeConverter())
def __init__(self, mango_program_address: PublicKey, address: PublicKey, base: Token, quote: Token, group_address: PublicKey):
super().__init__(mango_program_address, address, InventorySource.ACCOUNT, base, quote, RaisingLotSizeConverter())
self.group_address: PublicKey = group_address
def load(self, context: Context, group: typing.Optional[Group] = None) -> PerpMarket:
actual_group: Group = group or Group.load(context, self.group_address)
underlying_perp_market: PerpMarketDetails = PerpMarketDetails.load(context, self.address, actual_group)
return PerpMarket(self.program_id, self.address, self.base, self.quote, underlying_perp_market)
return PerpMarket(self.program_address, self.address, self.base, self.quote, underlying_perp_market)
@property
def symbol(self) -> str:
return f"{self.base.symbol}-PERP"
def __str__(self) -> str:
return f"« 𝙿𝚎𝚛𝚙𝙼𝚊𝚛𝚔𝚎𝚝𝚂𝚝𝚞𝚋 {self.symbol} {self.address} [{self.program_id}] »"
return f"« 𝙿𝚎𝚛𝚙𝙼𝚊𝚛𝚔𝚎𝚝𝚂𝚝𝚞𝚋 {self.symbol} {self.address} [{self.program_address}] »"

View File

@ -34,8 +34,8 @@ from .token import Token
# This class encapsulates our knowledge of a Serum spot market.
#
class SerumMarket(Market):
def __init__(self, program_id: PublicKey, address: PublicKey, base: Token, quote: Token, underlying_serum_market: PySerumMarket):
super().__init__(program_id, address, InventorySource.SPL_TOKENS, base, quote, RaisingLotSizeConverter())
def __init__(self, serum_program_address: PublicKey, address: PublicKey, base: Token, quote: Token, underlying_serum_market: PySerumMarket):
super().__init__(serum_program_address, address, InventorySource.SPL_TOKENS, base, quote, RaisingLotSizeConverter())
self.underlying_serum_market: PySerumMarket = underlying_serum_market
self.lot_size_converter: LotSizeConverter = LotSizeConverter(
base, underlying_serum_market.state.base_lot_size, quote, underlying_serum_market.state.quote_lot_size)
@ -54,7 +54,7 @@ class SerumMarket(Market):
return list(map(Order.from_serum_order, itertools.chain(bids_orderbook.orders(), asks_orderbook.orders())))
def __str__(self) -> str:
return f"""« 𝚂𝚎𝚛𝚞𝚖𝙼𝚊𝚛𝚔𝚎𝚝 {self.symbol} {self.address} [{self.program_id}]
return f"""« 𝚂𝚎𝚛𝚞𝚖𝙼𝚊𝚛𝚔𝚎𝚝 {self.symbol} {self.address} [{self.program_address}]
Event Queue: {self.underlying_serum_market.state.event_queue()}
Request Queue: {self.underlying_serum_market.state.request_queue()}
Bids: {self.underlying_serum_market.state.bids()}
@ -69,13 +69,13 @@ class SerumMarket(Market):
# This class holds information to load a `SerumMarket` object but doesn't automatically load it.
#
class SerumMarketStub(Market):
def __init__(self, program_id: PublicKey, address: PublicKey, base: Token, quote: Token):
super().__init__(program_id, address, InventorySource.SPL_TOKENS, base, quote, RaisingLotSizeConverter())
def __init__(self, serum_program_address: PublicKey, address: PublicKey, base: Token, quote: Token):
super().__init__(serum_program_address, address, InventorySource.SPL_TOKENS, base, quote, RaisingLotSizeConverter())
def load(self, context: Context) -> SerumMarket:
underlying_serum_market: PySerumMarket = PySerumMarket.load(
context.client.compatible_client, self.address, context.dex_program_id)
return SerumMarket(self.program_id, self.address, self.base, self.quote, underlying_serum_market)
context.client.compatible_client, self.address, context.serum_program_address)
return SerumMarket(self.program_address, self.address, self.base, self.quote, underlying_serum_market)
def __str__(self) -> str:
return f"« 𝚂𝚎𝚛𝚞𝚖𝙼𝚊𝚛𝚔𝚎𝚝𝚂𝚝𝚞𝚋 {self.symbol} {self.address} [{self.program_id}] »"
return f"« 𝚂𝚎𝚛𝚞𝚖𝙼𝚊𝚛𝚔𝚎𝚝𝚂𝚝𝚞𝚋 {self.symbol} {self.address} [{self.program_address}] »"

View File

@ -56,7 +56,7 @@ class SerumMarketInstructionBuilder(MarketInstructionBuilder):
@staticmethod
def load(context: Context, wallet: Wallet, serum_market: SerumMarket) -> "SerumMarketInstructionBuilder":
raw_market: PySerumMarket = PySerumMarket.load(
context.client.compatible_client, serum_market.address, context.dex_program_id)
context.client.compatible_client, serum_market.address, context.serum_program_address)
fee_discount_token_address: typing.Optional[PublicKey] = None
srm_token = context.token_lookup.find_by_symbol("SRM")
@ -68,7 +68,7 @@ class SerumMarketInstructionBuilder(MarketInstructionBuilder):
open_orders_address: typing.Optional[PublicKey] = None
all_open_orders = OpenOrders.load_for_market_and_owner(
context, serum_market.address, wallet.address, context.dex_program_id, serum_market.base.decimals, serum_market.quote.decimals)
context, serum_market.address, wallet.address, context.serum_program_address, serum_market.base.decimals, serum_market.quote.decimals)
if len(all_open_orders) > 0:
open_orders_address = all_open_orders[0].address

View File

@ -50,16 +50,16 @@ from .token import Token
# there is a name-value pair for the particular market we're interested in. Also, the
# current file only lists USDC and USDT markets, so that's all we can support this way.
class SerumMarketLookup(MarketLookup):
def __init__(self, program_id: PublicKey, token_data: typing.Dict) -> None:
def __init__(self, serum_program_address: PublicKey, token_data: typing.Dict) -> None:
super().__init__()
self.program_id: PublicKey = program_id
self.serum_program_address: PublicKey = serum_program_address
self.token_data: typing.Dict = token_data
@staticmethod
def load(program_id: PublicKey, token_data_filename: str) -> "SerumMarketLookup":
def load(serum_program_address: PublicKey, token_data_filename: str) -> "SerumMarketLookup":
with open(token_data_filename) as json_file:
token_data = json.load(json_file)
return SerumMarketLookup(program_id, token_data)
return SerumMarketLookup(serum_program_address, token_data)
@staticmethod
def _find_data_by_symbol(symbol: str, token_data: typing.Dict) -> typing.Optional[typing.Dict]:
@ -121,7 +121,7 @@ class SerumMarketLookup(MarketLookup):
f"Could not find market with quote token '{quote.symbol}'. Only markets based on USDC or USDT are supported.")
return None
return SerumMarketStub(self.program_id, market_address, base, quote)
return SerumMarketStub(self.serum_program_address, market_address, base, quote)
def find_by_address(self, address: PublicKey) -> typing.Optional[Market]:
address_string: str = str(address)
@ -138,7 +138,7 @@ class SerumMarketLookup(MarketLookup):
raise Exception("Could not load token data for USDC (which should always be present).")
quote = Token(quote_data["symbol"], quote_data["name"], PublicKey(
quote_data["address"]), Decimal(quote_data["decimals"]))
return SerumMarketStub(self.program_id, market_address, base, quote)
return SerumMarketStub(self.serum_program_address, market_address, base, quote)
if "serumV3Usdt" in token_data["extensions"]:
if token_data["extensions"]["serumV3Usdt"] == address_string:
market_address_string = token_data["extensions"]["serumV3Usdt"]
@ -150,7 +150,7 @@ class SerumMarketLookup(MarketLookup):
raise Exception("Could not load token data for USDT (which should always be present).")
quote = Token(quote_data["symbol"], quote_data["name"], PublicKey(
quote_data["address"]), Decimal(quote_data["decimals"]))
return SerumMarketStub(self.program_id, market_address, base, quote)
return SerumMarketStub(self.serum_program_address, market_address, base, quote)
return None
def all_markets(self) -> typing.Sequence[Market]:
@ -165,12 +165,12 @@ class SerumMarketLookup(MarketLookup):
market_address = PublicKey(market_address_string)
base = Token(token_data["symbol"], token_data["name"], PublicKey(
token_data["address"]), Decimal(token_data["decimals"]))
all_markets += [SerumMarketStub(self.program_id, market_address, base, usdc)]
all_markets += [SerumMarketStub(self.serum_program_address, market_address, base, usdc)]
if "serumV3Usdt" in token_data["extensions"]:
market_address_string = token_data["extensions"]["serumV3Usdt"]
market_address = PublicKey(market_address_string)
base = Token(token_data["symbol"], token_data["name"], PublicKey(
token_data["address"]), Decimal(token_data["decimals"]))
all_markets += [SerumMarketStub(self.program_id, market_address, base, usdt)]
all_markets += [SerumMarketStub(self.serum_program_address, market_address, base, usdt)]
return all_markets

View File

@ -35,8 +35,8 @@ from .token import Token
# This class encapsulates our knowledge of a Serum spot market.
#
class SpotMarket(Market):
def __init__(self, program_id: PublicKey, address: PublicKey, base: Token, quote: Token, group: Group, underlying_serum_market: PySerumMarket):
super().__init__(program_id, address, InventorySource.ACCOUNT, base, quote, RaisingLotSizeConverter())
def __init__(self, serum_program_address: PublicKey, address: PublicKey, base: Token, quote: Token, group: Group, underlying_serum_market: PySerumMarket):
super().__init__(serum_program_address, address, InventorySource.ACCOUNT, base, quote, RaisingLotSizeConverter())
self.group: Group = group
self.underlying_serum_market: PySerumMarket = underlying_serum_market
self.lot_size_converter: LotSizeConverter = LotSizeConverter(
@ -56,7 +56,7 @@ class SpotMarket(Market):
return list(map(Order.from_serum_order, itertools.chain(bids_orderbook.orders(), asks_orderbook.orders())))
def __str__(self) -> str:
return f"""« 𝚂𝚙𝚘𝚝𝙼𝚊𝚛𝚔𝚎𝚝 {self.symbol} {self.address} [{self.program_id}]
return f"""« 𝚂𝚙𝚘𝚝𝙼𝚊𝚛𝚔𝚎𝚝 {self.symbol} {self.address} [{self.program_address}]
Event Queue: {self.underlying_serum_market.state.event_queue()}
Request Queue: {self.underlying_serum_market.state.request_queue()}
Bids: {self.underlying_serum_market.state.bids()}
@ -73,15 +73,15 @@ class SpotMarket(Market):
class SpotMarketStub(Market):
def __init__(self, program_id: PublicKey, address: PublicKey, base: Token, quote: Token, group_address: PublicKey):
super().__init__(program_id, address, InventorySource.ACCOUNT, base, quote, RaisingLotSizeConverter())
def __init__(self, serum_program_address: PublicKey, address: PublicKey, base: Token, quote: Token, group_address: PublicKey):
super().__init__(serum_program_address, address, InventorySource.ACCOUNT, base, quote, RaisingLotSizeConverter())
self.group_address: PublicKey = group_address
def load(self, context: Context, group: typing.Optional[Group]) -> SpotMarket:
actual_group: Group = group or Group.load(context, self.group_address)
underlying_serum_market: PySerumMarket = PySerumMarket.load(
context.client.compatible_client, self.address, context.dex_program_id)
return SpotMarket(self.program_id, self.address, self.base, self.quote, actual_group, underlying_serum_market)
context.client.compatible_client, self.address, context.serum_program_address)
return SpotMarket(self.program_address, self.address, self.base, self.quote, actual_group, underlying_serum_market)
def __str__(self) -> str:
return f"« 𝚂𝚙𝚘𝚝𝙼𝚊𝚛𝚔𝚎𝚝𝚂𝚝𝚞𝚋 {self.symbol} {self.address} [{self.program_id}] »"
return f"« 𝚂𝚙𝚘𝚝𝙼𝚊𝚛𝚔𝚎𝚝𝚂𝚝𝚞𝚋 {self.symbol} {self.address} [{self.program_address}] »"

View File

@ -58,7 +58,7 @@ class SpotMarketInstructionBuilder(MarketInstructionBuilder):
@staticmethod
def load(context: Context, wallet: Wallet, group: Group, account: Account, spot_market: SpotMarket) -> "SpotMarketInstructionBuilder":
raw_market: PySerumMarket = PySerumMarket.load(
context.client.compatible_client, spot_market.address, context.dex_program_id)
context.client.compatible_client, spot_market.address, context.serum_program_address)
fee_discount_token_address: typing.Optional[PublicKey] = None
srm_token = context.token_lookup.find_by_symbol("SRM")

View File

@ -220,7 +220,7 @@ def fetch_all_recent_transaction_signatures(context: Context) -> typing.Sequence
before = None
signature_results: typing.List[str] = []
while not all_fetched:
signatures = context.client.get_confirmed_signatures_for_address2(context.group_id, before=before)
signatures = context.client.get_confirmed_signatures_for_address2(context.group_address, before=before)
signature_results += signatures
if (len(signatures) == 0):
all_fetched = True
@ -232,7 +232,7 @@ def fetch_all_recent_transaction_signatures(context: Context) -> typing.Sequence
def mango_instruction_from_response(context: Context, all_accounts: typing.Sequence[PublicKey], instruction_data: typing.Dict) -> typing.Optional["MangoInstruction"]:
program_account_index = instruction_data["programIdIndex"]
if all_accounts[program_account_index] != context.program_id:
if all_accounts[program_account_index] != context.mango_program_address:
# It's an instruction, it's just not a Mango one.
return None

View File

@ -92,7 +92,7 @@ def build_spot_open_orders_watcher(context: Context, manager: WebSocketSubscript
def build_serum_open_orders_watcher(context: Context, manager: WebSocketSubscriptionManager, health_check: HealthCheck, serum_market: SerumMarket, wallet: Wallet) -> Watcher[PlacedOrdersContainer]:
all_open_orders = OpenOrders.load_for_market_and_owner(
context, serum_market.address, wallet.address, context.dex_program_id, serum_market.base.decimals, serum_market.quote.decimals)
context, serum_market.address, wallet.address, context.serum_program_address, serum_market.base.decimals, serum_market.quote.decimals)
if len(all_open_orders) > 0:
initial_serum_open_orders: OpenOrders = all_open_orders[0]
open_orders_address = initial_serum_open_orders.address

View File

@ -10,7 +10,7 @@ do
cancel-my-orders --name "Random Taker ${MARKET} (cancel)" --market $MARKET --log-level ERROR
RANDOM_POSITION_SIZE=$(echo "scale=4; ($(echo "$RANDOM % 1000" | bc) / 1000) * $POSITION_SIZE_CEILING" | bc)
CURRENT_PRICE=$(fetch-price --provider serum --symbol $ORACLE_MARKET --log-level ERROR --cluster mainnet | cut -d"'" -f 2 | sed 's/,//')
CURRENT_PRICE=$(fetch-price --provider serum --symbol $ORACLE_MARKET --log-level ERROR --cluster-name mainnet | cut -d"'" -f 2 | sed 's/,//')
place-order --name "Random Taker ${MARKET} (place buy)" --market $MARKET --order-type IOC --log-level ERROR \
--side BUY --quantity $RANDOM_POSITION_SIZE --price $(echo "$CURRENT_PRICE + $IMMEDIATE_BUY_ADJUSTMENT" | bc)
@ -19,7 +19,7 @@ do
sleep ${PAUSE_FOR}
RANDOM_POSITION_SIZE=$(echo "scale=4; ($(echo "$RANDOM % 1000" | bc) / 1000) * $POSITION_SIZE_CEILING" | bc)
CURRENT_PRICE=$(fetch-price --provider serum --symbol $ORACLE_MARKET --log-level ERROR --cluster mainnet | cut -d"'" -f 2 | sed 's/,//')
CURRENT_PRICE=$(fetch-price --provider serum --symbol $ORACLE_MARKET --log-level ERROR --cluster-name mainnet | cut -d"'" -f 2 | sed 's/,//')
place-order --name "Random Taker ${MARKET} (place sell)" --market $MARKET --order-type IOC --log-level ERROR \
--side SELL --quantity $RANDOM_POSITION_SIZE --price $(echo "$CURRENT_PRICE - $IMMEDIATE_BUY_ADJUSTMENT" | bc)

View File

@ -10,7 +10,7 @@ while :
do
cancel-my-orders --name "WSMM ${MARKET} (cancel)" --market $MARKET --log-level ERROR
CURRENT_PRICE=$(fetch-price --provider serum --symbol $ORACLE_MARKET --log-level ERROR --cluster mainnet | cut -d"'" -f 2 | sed 's/,//')
CURRENT_PRICE=$(fetch-price --provider serum --symbol $ORACLE_MARKET --log-level ERROR --cluster-name mainnet | cut -d"'" -f 2 | sed 's/,//')
place-order --name "WSMM ${MARKET} (buy)" --market $MARKET --order-type LIMIT \
--log-level ERROR --side BUY --quantity $FIXED_POSITION_SIZE --price $(echo "$CURRENT_PRICE - $FIXED_SPREAD" | bc)
place-order --name "WSMM ${MARKET} (sell)" --market $MARKET --order-type LIMIT \

View File

@ -53,13 +53,13 @@ def fake_token_info() -> mango.TokenInfo:
def fake_context() -> mango.Context:
context = mango.Context(name="Mango Test",
cluster="test",
cluster_name="test",
cluster_url="http://localhost",
skip_preflight=False,
program_id=fake_seeded_public_key("program ID"),
dex_program_id=fake_seeded_public_key("DEX program ID"),
mango_program_address=fake_seeded_public_key("Mango program address"),
serum_program_address=fake_seeded_public_key("Serum program address"),
group_name="TEST_GROUP",
group_id=fake_seeded_public_key("group ID"),
group_address=fake_seeded_public_key("group ID"),
token_lookup=mango.NullTokenLookup(),
market_lookup=mango.NullMarketLookup())
context.client = MockClient()

View File

@ -37,7 +37,7 @@ def test_group_layout():
assert group.signer_nonce == 0
assert group.signer_key == PublicKey("9GXvznfEep9yEsvH4CQzqNy5GH81FNk1HDeAR8UjefSf")
assert group.admin == PublicKey("Cwg1f6m4m3DGwMEbmsbAfDtUToUf5jRdKrJSGD7GfZCB")
assert group.dex_program_id == PublicKey("DESVgJVGajEgKGXhb6XmqDHGz3VjdgP7rEVESBgxmroY")
assert group.serum_program_address == PublicKey("DESVgJVGajEgKGXhb6XmqDHGz3VjdgP7rEVESBgxmroY")
assert group.cache == PublicKey("PJhM2enPpZH7E9wgw7Sqt8S2p4mr3Bc7SycawQwfY7b")
assert group.valid_interval == 5
assert group.dao_vault == PublicKey("14gfuPWjUQnYXpsxs4WgsjafUrJctKkR9AMFH7fjvTgR")

View File

@ -31,13 +31,13 @@ def mock_group():
signer_nonce = Decimal(1)
signer_key = fake_seeded_public_key("signer key")
admin_key = fake_seeded_public_key("admin key")
dex_program_id = fake_seeded_public_key("DEX program ID")
serum_program_address = fake_seeded_public_key("DEX program ID")
cache_key = fake_seeded_public_key("cache key")
valid_interval = Decimal(7)
return mango.Group(account_info, mango.Version.V1, name, meta_data, token_infos,
spot_markets, perp_markets, oracles, signer_nonce, signer_key,
admin_key, dex_program_id, cache_key, valid_interval)
admin_key, serum_program_address, cache_key, valid_interval)
def mock_prices(prices: typing.Sequence[str]):
@ -53,11 +53,11 @@ def mock_prices(prices: typing.Sequence[str]):
def mock_open_orders(base_token_free: Decimal = Decimal(0), base_token_total: Decimal = Decimal(0), quote_token_free: Decimal = Decimal(0), quote_token_total: Decimal = Decimal(0), referrer_rebate_accrued: Decimal = Decimal(0)):
account_info = fake_account_info()
program_id = fake_seeded_public_key("program ID")
program_address = fake_seeded_public_key("program address")
market = fake_seeded_public_key("market")
owner = fake_seeded_public_key("owner")
flags = mango.AccountFlags(mango.Version.V1, True, False, True, False, False, False, False, False)
return mango.OpenOrders(account_info, mango.Version.V1, program_id, flags, market,
return mango.OpenOrders(account_info, mango.Version.V1, program_address, flags, market,
owner, base_token_free, base_token_total, quote_token_free,
quote_token_total, [], referrer_rebate_accrued)

View File

@ -4,10 +4,10 @@ from solana.publickey import PublicKey
def context_has_default_values(ctx: mango.Context):
assert ctx.program_id == PublicKey("mv3ekLzLbnVPNxjSKvqBpU3ZeZXPQdEC3bp5MDEBG68")
assert ctx.dex_program_id == PublicKey("9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin")
assert ctx.mango_program_address == PublicKey("mv3ekLzLbnVPNxjSKvqBpU3ZeZXPQdEC3bp5MDEBG68")
assert ctx.serum_program_address == PublicKey("9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin")
assert ctx.group_name == "mainnet.1"
assert ctx.group_id == PublicKey("98pjRuQjK3qA6gXts96PqZT4Ze5QmnCmt3QYjhbUSPue")
assert ctx.group_address == PublicKey("98pjRuQjK3qA6gXts96PqZT4Ze5QmnCmt3QYjhbUSPue")
def test_context_default_exists():
@ -22,21 +22,21 @@ def test_context_default_values():
# def test_new_from_cluster():
# context_has_default_values(mango.ContextBuilder.default())
# derived = mango.ContextBuilder.default().new_from_cluster("mainnet")
# assert derived.cluster == "mainnet"
# assert derived.cluster_name == "mainnet"
# assert derived.cluster_url == "https://solana-api.projectserum.com"
# assert derived.program_id == PublicKey("mv3ekLzLbnVPNxjSKvqBpU3ZeZXPQdEC3bp5MDEBG68")
# assert derived.dex_program_id == PublicKey("9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin")
# assert derived.mango_program_address == PublicKey("mv3ekLzLbnVPNxjSKvqBpU3ZeZXPQdEC3bp5MDEBG68")
# assert derived.serum_program_address == PublicKey("9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin")
# assert derived.group_name == "mainnet.0"
# assert derived.group_id == PublicKey("98pjRuQjK3qA6gXts96PqZT4Ze5QmnCmt3QYjhbUSPue")
# assert derived.group_address == PublicKey("98pjRuQjK3qA6gXts96PqZT4Ze5QmnCmt3QYjhbUSPue")
# context_has_default_values(mango.ContextBuilder.default())
def test_new_from_group_name():
context_has_default_values(mango.ContextBuilder.default())
derived = mango.ContextBuilder.from_group_name(mango.ContextBuilder.default(), "mainnet.0")
assert derived.program_id == PublicKey("mv3ekLzLbnVPNxjSKvqBpU3ZeZXPQdEC3bp5MDEBG68")
assert derived.dex_program_id == PublicKey("9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin")
assert derived.mango_program_address == PublicKey("mv3ekLzLbnVPNxjSKvqBpU3ZeZXPQdEC3bp5MDEBG68")
assert derived.serum_program_address == PublicKey("9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin")
# Should update both of these values on new group name.
assert derived.group_name == "mainnet.0"
assert derived.group_id == PublicKey("4yJ2Vx3kZnmHTNCrHzdoj5nCwriF2kVhfKNvqC6gU8tr")
assert derived.group_address == PublicKey("4yJ2Vx3kZnmHTNCrHzdoj5nCwriF2kVhfKNvqC6gU8tr")

View File

@ -15,7 +15,7 @@ def test_construction():
signer_nonce = Decimal(1)
signer_key = fake_seeded_public_key("signer key")
admin_key = fake_seeded_public_key("admin key")
dex_program_id = fake_seeded_public_key("DEX program ID")
serum_program_address = fake_seeded_public_key("Serum program ID")
cache_key = fake_seeded_public_key("cache key")
valid_interval = Decimal(7)
dao_vault = fake_seeded_public_key("insurance vault")
@ -24,7 +24,7 @@ def test_construction():
actual = mango.Group(account_info, mango.Version.V1, name, meta_data, shared_quote_token,
in_basket, basket_markets, signer_nonce, signer_key, admin_key,
dex_program_id, cache_key, valid_interval, dao_vault, srm_vault, msrm_vault)
serum_program_address, cache_key, valid_interval, dao_vault, srm_vault, msrm_vault)
assert actual is not None
assert actual.logger is not None
@ -36,7 +36,7 @@ def test_construction():
assert actual.signer_nonce == signer_nonce
assert actual.signer_key == signer_key
assert actual.admin == admin_key
assert actual.dex_program_id == dex_program_id
assert actual.serum_program_address == serum_program_address
assert actual.cache == cache_key
assert actual.valid_interval == valid_interval
assert actual.dao_vault == dao_vault
@ -55,7 +55,7 @@ def test_construction():
# context = fake_context()
# ids_json_token_lookup: mango.TokenLookup = mango.IdsJsonTokenLookup("devnet", mango.default_group_name)
# context.token_lookup = ids_json_token_lookup
# # context.cluster = "devnet" # Needed for devnet token lookups.
# # context.cluster_name = "devnet" # Needed for devnet token lookups.
# group = mango.Group.parse(context, group_account_info)
# assert group.address == group_public_key
@ -81,7 +81,7 @@ def test_construction():
# assert group.signer_nonce == 0
# assert group.signer_key == PublicKey("HzpyPmuz8C4P6ExinCDndujSAy68MeMNHNzYLpmMAGMS")
# assert group.admin == PublicKey("Cwg1f6m4m3DGwMEbmsbAfDtUToUf5jRdKrJSGD7GfZCB")
# assert group.dex_program_id == PublicKey("DESVgJVGajEgKGXhb6XmqDHGz3VjdgP7rEVESBgxmroY")
# assert group.serum_program_address == PublicKey("DESVgJVGajEgKGXhb6XmqDHGz3VjdgP7rEVESBgxmroY")
# assert group.cache == PublicKey("D2zSEAJTJADF46eCm1SrsSP3k4QDfv33nneTpbHow3DM")
# assert group.valid_interval == 5
# assert group.dao_vault is None

View File

@ -6,12 +6,12 @@ from decimal import Decimal
def test_constructor():
account_info = fake_account_info()
program_id = fake_public_key()
program_address = fake_public_key()
market = fake_public_key()
owner = fake_public_key()
flags = mango.AccountFlags(mango.Version.V1, True, False, True, False, False, False, False, False)
actual = mango.OpenOrders(account_info, mango.Version.V1, program_id, flags, market,
actual = mango.OpenOrders(account_info, mango.Version.V1, program_address, flags, market,
owner, Decimal(0), Decimal(0), Decimal(0), Decimal(0), [], Decimal(0))
assert actual is not None
assert actual.logger is not None

View File

@ -5,16 +5,16 @@ from decimal import Decimal
def test_spot_market_stub_constructor():
program_id = fake_seeded_public_key("program ID")
program_address = fake_seeded_public_key("program address")
address = fake_seeded_public_key("spot market address")
base = mango.Token("BASE", "Base Token", fake_seeded_public_key("base token"), Decimal(7))
quote = mango.Token("QUOTE", "Quote Token", fake_seeded_public_key("quote token"), Decimal(9))
group_address = fake_seeded_public_key("group address")
actual = mango.SpotMarketStub(program_id, address, base, quote, group_address)
actual = mango.SpotMarketStub(program_address, address, base, quote, group_address)
assert actual is not None
assert actual.logger is not None
assert actual.base == base
assert actual.quote == quote
assert actual.address == address
assert actual.program_id == program_id
assert actual.program_address == program_address
assert actual.group_address == group_address