mango-explorer/TransactionScout.ipynb

575 lines
26 KiB
Plaintext
Raw Normal View History

{
"cells": [
{
"cell_type": "markdown",
"id": "cooperative-israel",
"metadata": {},
"source": [
"# ⚠ Warning\n",
"\n",
"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n",
"\n",
"[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gl/OpinionatedGeek%2Fmango-explorer/HEAD?filepath=TransactionScout.ipynb) _🏃 To run this notebook press the ⏩ icon in the toolbar above._\n",
"\n",
"[🥭 Mango Markets](https://mango.markets/) support is available at: [Docs](https://docs.mango.markets/) | [Discord](https://discord.gg/67jySBhxrg) | [Twitter](https://twitter.com/mangomarkets) | [Github](https://github.com/blockworks-foundation) | [Email](mailto:hello@blockworks.foundation)"
]
},
{
"cell_type": "markdown",
"id": "alive-essay",
"metadata": {},
"source": [
"# 🥭 TransactionScount\n",
"\n",
"This notebook tries to show details of historical transactions.\n",
"\n",
"It fetches the data from Solana, parses it, and then prints it.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "hidden-tokyo",
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"import base58\n",
"import datetime\n",
"import logging\n",
"import typing\n",
"\n",
"logging.getLogger().setLevel(logging.ERROR)\n",
"\n",
"from decimal import Decimal\n",
"from solana.publickey import PublicKey\n",
"\n",
"from BaseModel import InstructionType, OwnedTokenValue, TokenLookup, TokenValue\n",
"from Context import Context, default_context\n",
"from Layouts import InstructionParsersByVariant, MANGO_INSTRUCTION_VARIANT_FINDER\n"
]
},
{
"cell_type": "markdown",
"id": "african-disney",
"metadata": {},
"source": [
"## Transaction Indices\n",
"\n",
"Transactions come with a large account list.\n",
"\n",
"Instructions, individually, take accounts.\n",
"\n",
"The accounts instructions take are listed in the the transaction's list of accounts.\n",
"\n",
"The instruction data therefore doesn't need to specify account public keys, only the index of those public keys in the main transaction's list.\n",
"\n",
"So, for example, if an instruction uses 3 accounts, the instruction data could say [3, 2, 14], meaning the first account it uses is index 3 in the whole transaction account list, the second is index 2 in the whole transaction account list, the third is index 14 in the whole transaction account list.\n",
"\n",
"This complicates figuring out which account is which for a given instruction, especially since some of the accounts (like the sender/signer account) can appear at different indices depending on which instruction is being used.\n",
"\n",
"We keep a few static dictionaries here to allow us to dereference important accounts per type.\n",
"\n",
"In addition, we dereference the accounts for each instruction when we instantiate each `TransactionInstruction`, so users of `TransactionInstruction` don't need to worry about these details.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "differential-october",
"metadata": {},
"outputs": [],
"source": [
"# The index of the sender/signer depends on the instruction.\n",
"_instruction_signer_indices: typing.Dict[InstructionType, int] = {\n",
" InstructionType.InitMangoGroup: 3,\n",
" InstructionType.InitMarginAccount: 2,\n",
" InstructionType.Deposit: 2,\n",
" InstructionType.Withdraw: 2,\n",
" InstructionType.Borrow: 2,\n",
" InstructionType.SettleBorrow: 2,\n",
" InstructionType.Liquidate: 1,\n",
" InstructionType.DepositSrm: 2,\n",
" InstructionType.WithdrawSrm: 2,\n",
" InstructionType.PlaceOrder: 1,\n",
" InstructionType.SettleFunds: 1,\n",
" InstructionType.CancelOrder: 1,\n",
" InstructionType.CancelOrderByClientId: 1,\n",
" InstructionType.ChangeBorrowLimit: 1,\n",
" InstructionType.PlaceAndSettle: 1,\n",
" InstructionType.ForceCancelOrders: 1,\n",
" InstructionType.PartialLiquidate: 1\n",
"}\n",
"\n",
"# The index of the token IN account depends on the instruction, and for some instructions doesn't exist.\n",
"_token_in_indices: typing.Dict[InstructionType, int] = {\n",
" InstructionType.InitMangoGroup: -1,\n",
" InstructionType.InitMarginAccount: -1,\n",
" InstructionType.Deposit: 3, # token_account_acc - TokenAccount owned by user which will be sending the funds\n",
" InstructionType.Withdraw: 4, # vault_acc - TokenAccount owned by MangoGroup which will be sending\n",
" InstructionType.Borrow: -1,\n",
" InstructionType.SettleBorrow: -1,\n",
" InstructionType.Liquidate: -1,\n",
" InstructionType.DepositSrm: 3, # srm_account_acc - TokenAccount owned by user which will be sending the funds\n",
" InstructionType.WithdrawSrm: 4, # vault_acc - SRM vault of MangoGroup\n",
" InstructionType.PlaceOrder: -1,\n",
" InstructionType.SettleFunds: -1,\n",
" InstructionType.CancelOrder: -1,\n",
" InstructionType.CancelOrderByClientId: -1,\n",
" InstructionType.ChangeBorrowLimit: -1,\n",
" InstructionType.PlaceAndSettle: -1,\n",
" InstructionType.ForceCancelOrders: -1,\n",
" InstructionType.PartialLiquidate: 2 # liqor_in_token_acc - liquidator's token account to deposit\n",
"}\n",
"\n",
"# The index of the token OUT account depends on the instruction, and for some instructions doesn't exist.\n",
"_token_out_indices: typing.Dict[InstructionType, int] = {\n",
" InstructionType.InitMangoGroup: -1,\n",
" InstructionType.InitMarginAccount: -1,\n",
" InstructionType.Deposit: 4, # vault_acc - TokenAccount owned by MangoGroup\n",
" InstructionType.Withdraw: 3, # token_account_acc - TokenAccount owned by user which will be receiving the funds\n",
" InstructionType.Borrow: -1,\n",
" InstructionType.SettleBorrow: -1,\n",
" InstructionType.Liquidate: -1,\n",
" InstructionType.DepositSrm: 4, # vault_acc - SRM vault of MangoGroup\n",
" InstructionType.WithdrawSrm: 3, # srm_account_acc - TokenAccount owned by user which will be receiving the funds\n",
" InstructionType.PlaceOrder: -1,\n",
" InstructionType.SettleFunds: -1,\n",
" InstructionType.CancelOrder: -1,\n",
" InstructionType.CancelOrderByClientId: -1,\n",
" InstructionType.ChangeBorrowLimit: -1,\n",
" InstructionType.PlaceAndSettle: -1,\n",
" InstructionType.ForceCancelOrders: -1,\n",
" InstructionType.PartialLiquidate: 3 # liqor_out_token_acc - liquidator's token account to withdraw into\n",
"}\n"
]
},
{
"cell_type": "markdown",
"id": "immediate-anchor",
"metadata": {},
"source": [
"## TransactionInstruction class\n",
"\n",
"This class packages up instruction data, which can come from disparate parts of the transaction. Keeping it all together here makes many things simpler."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "understanding-england",
"metadata": {},
"outputs": [],
"source": [
"class TransactionInstruction:\n",
" def __init__(self, instruction_type: InstructionType, instruction_data: typing.Any, accounts: typing.List[PublicKey]):\n",
" self.instruction_type = instruction_type\n",
" self.instruction_data = instruction_data\n",
" self.accounts = accounts\n",
"\n",
" @property\n",
" def group(self) -> PublicKey:\n",
" # Group PublicKey is always the zero index.\n",
" return self.accounts[0]\n",
"\n",
" @property\n",
" def sender(self) -> PublicKey:\n",
" account_index = _instruction_signer_indices[self.instruction_type]\n",
" return self.accounts[account_index]\n",
"\n",
" @property\n",
" def token_in_account(self) -> typing.Optional[PublicKey]:\n",
" account_index = _token_in_indices[self.instruction_type]\n",
" if account_index < 0:\n",
" return None\n",
" return self.accounts[account_index]\n",
"\n",
" @property\n",
" def token_out_account(self) -> typing.Optional[PublicKey]:\n",
" account_index = _token_out_indices[self.instruction_type]\n",
" if account_index < 0:\n",
" return None\n",
" return self.accounts[account_index]\n",
"\n",
" def describe_parameters(self) -> str:\n",
" instruction_type = self.instruction_type\n",
" additional_data = \"\"\n",
" if instruction_type == InstructionType.InitMangoGroup:\n",
" pass\n",
" elif instruction_type == InstructionType.InitMarginAccount:\n",
" pass\n",
" elif instruction_type == InstructionType.Deposit:\n",
" additional_data = f\"quantity: {self.instruction_data.quantity}\"\n",
" elif instruction_type == InstructionType.Withdraw:\n",
" additional_data = f\"quantity: {self.instruction_data.quantity}\"\n",
" elif instruction_type == InstructionType.Borrow:\n",
" additional_data = f\"quantity: {self.instruction_data.quantity}, token index: {self.instruction_data.token_index}\"\n",
" elif instruction_type == InstructionType.SettleBorrow:\n",
" additional_data = f\"quantity: {self.instruction_data.quantity}, token index: {self.instruction_data.token_index}\"\n",
" elif instruction_type == InstructionType.Liquidate:\n",
" additional_data = f\"deposit quantities: {self.instruction_data.deposit_quantities}\"\n",
" elif instruction_type == InstructionType.DepositSrm:\n",
" additional_data = f\"quantity: {self.instruction_data.quantity}\"\n",
" elif instruction_type == InstructionType.WithdrawSrm:\n",
" additional_data = f\"quantity: {self.instruction_data.quantity}\"\n",
" elif instruction_type == InstructionType.PlaceOrder:\n",
" pass\n",
" elif instruction_type == InstructionType.SettleFunds:\n",
" pass\n",
" elif instruction_type == InstructionType.CancelOrder:\n",
" pass\n",
" elif instruction_type == InstructionType.CancelOrderByClientId:\n",
" additional_data = f\"client ID: {self.instruction_data.client_id}\"\n",
" elif instruction_type == InstructionType.ChangeBorrowLimit:\n",
" additional_data = f\"borrow limit: {self.instruction_data.borrow_limit}, token index: {self.instruction_data.token_index}\"\n",
" elif instruction_type == InstructionType.PlaceAndSettle:\n",
" pass\n",
" elif instruction_type == InstructionType.ForceCancelOrders:\n",
" additional_data = f\"limit: {self.instruction_data.limit}\"\n",
" elif instruction_type == InstructionType.PartialLiquidate:\n",
" additional_data = f\"max deposit: {self.instruction_data.max_deposit}\"\n",
"\n",
" return additional_data\n",
"\n",
" @staticmethod\n",
" def from_response(context: Context, all_accounts: typing.List[PublicKey], instruction_data: typing.Dict) -> typing.Optional[\"TransactionInstruction\"]:\n",
" program_account_index = instruction_data[\"programIdIndex\"]\n",
" if all_accounts[program_account_index] != context.program_id:\n",
" # It's an instruction, it's just not a Mango one.\n",
" return None\n",
"\n",
" data = instruction_data[\"data\"]\n",
" instructions_account_indices = instruction_data[\"accounts\"]\n",
"\n",
" decoded = base58.b58decode(data)\n",
" initial = MANGO_INSTRUCTION_VARIANT_FINDER.parse(decoded)\n",
" parser = InstructionParsersByVariant[initial.variant]\n",
"\n",
" # A whole bunch of accounts are listed for a transaction. Some (or all) of them apply\n",
" # to this instruction. The instruction data gives the index of each account it uses,\n",
" # in the order in which it uses them. So, for example, if it uses 3 accounts, the\n",
" # instruction data could say [3, 2, 14], meaning the first account it uses is index 3\n",
" # in the whole transaction account list, the second is index 2 in the whole transaction\n",
" # account list, the third is index 14 in the whole transaction account list.\n",
" accounts: typing.List[PublicKey] = []\n",
" for index in instructions_account_indices:\n",
" accounts += [all_accounts[index]]\n",
"\n",
" parsed = parser.parse(decoded)\n",
" instruction_type = InstructionType(int(parsed.variant))\n",
"\n",
" return TransactionInstruction(instruction_type, parsed, accounts)\n",
"\n",
" def __str__(self) -> str:\n",
" parameters = self.describe_parameters() or \"None\"\n",
" return f\"« {self.instruction_type.name}: {parameters} »\"\n",
"\n",
" def __repr__(self) -> str:\n",
" return f\"{self}\"\n"
]
},
{
"cell_type": "markdown",
"id": "freelance-option",
"metadata": {},
"source": [
"# TransactionScout class"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "induced-wonder",
"metadata": {},
"outputs": [],
"source": [
"class TransactionScout:\n",
" def __init__(self, timestamp: datetime.datetime, signatures: typing.List[str],\n",
" group_name: str, accounts: typing.List[PublicKey],\n",
" instructions: typing.List[typing.Any], messages: typing.List[str],\n",
" pre_token_balances: typing.List[OwnedTokenValue],\n",
" post_token_balances: typing.List[OwnedTokenValue]):\n",
" self.timestamp: datetime.datetime = timestamp\n",
" self.signatures: typing.List[str] = signatures\n",
" self.group_name: str = group_name\n",
" self.accounts: typing.List[PublicKey] = accounts\n",
" self.instructions: typing.List[typing.Any] = instructions\n",
" self.messages: typing.List[str] = messages\n",
" self.pre_token_balances: typing.List[OwnedTokenValue] = pre_token_balances\n",
" self.post_token_balances: typing.List[OwnedTokenValue] = post_token_balances\n",
"\n",
" @property\n",
" def summary(self) -> str:\n",
" instructions = \", \".join([ins.instruction_type.name for ins in self.instructions])\n",
" changes = OwnedTokenValue.changes(self.pre_token_balances, self.post_token_balances)\n",
"\n",
" in_tokens = []\n",
" for ins in self.instructions:\n",
" if ins.token_in_account is not None:\n",
" in_tokens += [OwnedTokenValue.find_by_owner(changes, ins.token_in_account)]\n",
"\n",
" out_tokens = []\n",
" for ins in self.instructions:\n",
" if ins.token_out_account is not None:\n",
" out_tokens += [OwnedTokenValue.find_by_owner(changes, ins.token_out_account)]\n",
"\n",
" changed_tokens = in_tokens + out_tokens\n",
" changed_tokens_text = \", \".join([f\"{tok.token_value.value:,.8f} {tok.token_value.token.name}\" for tok in changed_tokens]) or \"None\"\n",
"\n",
" return f\"« TransactionScout {self.group_name} [{self.timestamp}] {instructions}: Token Changes: {changed_tokens_text}\\n {self.signatures} »\"\n",
"\n",
" @property\n",
" def sender(self) -> PublicKey:\n",
" return self.instructions[0].sender\n",
"\n",
" @property\n",
" def group(self) -> PublicKey:\n",
" return self.instructions[0].group\n",
"\n",
" def has_any_instruction_of_type(self, instruction_type: InstructionType) -> bool:\n",
" return any(map(lambda ins: ins.instruction_type == instruction_type, self.instructions))\n",
"\n",
" @staticmethod\n",
" def load_if_available(context: Context, signature: str) -> typing.Optional[\"TransactionScout\"]:\n",
" transaction_response = context.client.get_confirmed_transaction(signature)\n",
" transaction_details = context.unwrap_or_raise_exception(transaction_response)\n",
" if transaction_details is None:\n",
" return None\n",
" return TransactionScout.from_transaction_response(context, transaction_details)\n",
"\n",
" @staticmethod\n",
" def load(context: Context, signature: str) -> \"TransactionScout\":\n",
" tx = TransactionScout.load_if_available(context, signature)\n",
" if tx is None:\n",
" raise Exception(f\"Transaction '{signature}' not found.\")\n",
" return tx\n",
"\n",
" @staticmethod\n",
" def from_transaction_response(context: Context, response) -> \"TransactionScout\":\n",
" def balance_to_token_value(accounts: typing.List[PublicKey], balance: typing.Dict) -> OwnedTokenValue:\n",
" mint = PublicKey(balance[\"mint\"])\n",
" account = accounts[balance[\"accountIndex\"]]\n",
" amount = Decimal(balance[\"uiTokenAmount\"][\"amount\"])\n",
" decimals = Decimal(balance[\"uiTokenAmount\"][\"decimals\"])\n",
" divisor = Decimal(10) ** decimals\n",
" value = amount / divisor\n",
" token = TokenLookup.find_by_mint(context, mint)\n",
" return OwnedTokenValue(account, TokenValue(token, value))\n",
"\n",
" try:\n",
" accounts = list(map(PublicKey, response[\"transaction\"][\"message\"][\"accountKeys\"]))\n",
" instructions = []\n",
" for instruction_data in response[\"transaction\"][\"message\"][\"instructions\"]:\n",
" instruction = TransactionInstruction.from_response(context, accounts, instruction_data)\n",
" if instruction is not None:\n",
" instructions += [instruction]\n",
"\n",
" group_name = context.lookup_group_name(instructions[0].group)\n",
" timestamp = datetime.datetime.fromtimestamp(response[\"blockTime\"])\n",
" signatures = response[\"transaction\"][\"signatures\"]\n",
" messages = response[\"meta\"][\"logMessages\"]\n",
" pre_token_balances = list(map(lambda bal: balance_to_token_value(accounts, bal), response[\"meta\"][\"preTokenBalances\"]))\n",
" post_token_balances = list(map(lambda bal: balance_to_token_value(accounts, bal), response[\"meta\"][\"postTokenBalances\"]))\n",
" return TransactionScout(timestamp,\n",
" signatures,\n",
" group_name,\n",
" accounts,\n",
" instructions,\n",
" messages,\n",
" pre_token_balances,\n",
" post_token_balances)\n",
" except Exception as exception:\n",
" signature = \"Unknown\"\n",
" if response and (\"transaction\" in response) and (\"signatures\" in response[\"transaction\"]) and len(response[\"transaction\"][\"signatures\"]) > 0:\n",
" signature = \", \".join(response[\"transaction\"][\"signatures\"])\n",
" raise Exception(f\"Exception fetching transaction '{signature}'\", exception)\n",
"\n",
" def __str__(self) -> str:\n",
" def format_tokens(account_token_values: typing.List[OwnedTokenValue]) -> str:\n",
" if len(account_token_values) == 0:\n",
" return \"None\"\n",
" return \"\\n \".join([f\"{atv}\" for atv in account_token_values])\n",
"\n",
" instruction_names = \", \".join([ins.instruction_type.name for ins in self.instructions])\n",
" signatures = \"\\n \".join(self.signatures)\n",
" accounts = \"\\n \".join([f\"{acc}\" for acc in self.accounts])\n",
" messages = \"\\n \".join(self.messages)\n",
" instructions = \"\\n \".join([f\"{ins}\" for ins in self.instructions])\n",
" changes = OwnedTokenValue.changes(self.pre_token_balances, self.post_token_balances)\n",
" tokens_in = format_tokens(self.pre_token_balances)\n",
" tokens_out = format_tokens(self.post_token_balances)\n",
" token_changes = format_tokens(changes)\n",
" return f\"\"\"« TransactionScout {self.timestamp}: {instruction_names}\n",
" Sender:\n",
" {self.sender}\n",
" Group:\n",
" {self.group_name} [{self.group}]\n",
" Signatures:\n",
" {signatures}\n",
" Instructions:\n",
" {instructions}\n",
" Accounts:\n",
" {accounts}\n",
" Messages:\n",
" {messages}\n",
" Tokens In:\n",
" {tokens_in}\n",
" Tokens Out:\n",
" {tokens_out}\n",
" Token Changes:\n",
" {token_changes}\n",
"»\"\"\"\n",
"\n",
" def __repr__(self) -> str:\n",
" return f\"{self}\"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "australian-percentage",
"metadata": {},
"outputs": [],
"source": [
"def fetch_all_recent_transaction_signatures(in_the_last: datetime.timedelta = datetime.timedelta(days=1)) -> typing.List[str]:\n",
" now = datetime.datetime.now()\n",
" recency_cutoff = now - in_the_last\n",
" recency_cutoff_timestamp = recency_cutoff.timestamp()\n",
"\n",
" all_fetched = False\n",
" before = None\n",
" signature_results = []\n",
" while not all_fetched:\n",
" signature_response = default_context.client.get_confirmed_signature_for_address2(default_context.group_id, before=before)\n",
" signature_result = default_context.unwrap_or_raise_exception(signature_response)\n",
" signature_results += signature_result\n",
" if (len(signature_result) == 0) or (signature_result[-1][\"blockTime\"] < recency_cutoff_timestamp):\n",
" all_fetched = True\n",
" before = signature_results[-1][\"signature\"]\n",
"\n",
" recent = [result[\"signature\"] for result in signature_results if result[\"blockTime\"] > recency_cutoff_timestamp]\n",
" return recent\n"
]
},
{
"cell_type": "markdown",
"id": "legitimate-filter",
"metadata": {},
"source": [
"# 🏃 Running\n",
"\n",
"You can run the following cells to examine any Mango transaction.\n",
"\n",
"Enter the signature of the transaction you want to examine in the value for `TRANSACTION_TO_VIEW` 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.\n",
"\n",
"Alternatively you can uncomment the `rx` code to run through all recent Mango Solana transactions, printing a summary or (if it's a `PartialLiquidate` transaction) the full details.\n",
"\n",
"**NOTE**: Transactions disappear from most servers within a few hours. It is often not possible to go too far back searching for transactions or retrieving their data."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "binding-three",
"metadata": {},
"outputs": [],
"source": [
"TRANSACTION_TO_VIEW = \"\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "loaded-taiwan",
"metadata": {},
"outputs": [],
"source": [
"if __name__ == \"__main__\":\n",
" if TRANSACTION_TO_VIEW != \"\":\n",
" tx = TransactionScout.load(default_context, TRANSACTION_TO_VIEW)\n",
" print(tx.summary)\n",
" print(tx)\n",
"\n",
"# import rx\n",
"# import rx.operators as ops\n",
"# from Observables import debug_print_item, PrintingObserverSubscriber\n",
"\n",
"# signatures = fetch_all_recent_transaction_signatures()\n",
"# rx.from_(signatures).pipe(\n",
"# # ops.map(debug_print_item(\"Signature:\")),\n",
"# ops.map(lambda signature: TransactionScout.load_if_available(default_context, signature)),\n",
"# ops.filter(lambda item: item is not None),\n",
"# ops.filter(lambda item: item.has_any_instruction_of_type(InstructionType.PartialLiquidate)),\n",
"# ops.take(5),\n",
"# ).subscribe(PrintingObserverSubscriber(True))\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.6"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": true,
"toc_window_display": true
},
"varInspector": {
"cols": {
"lenName": 16,
"lenType": 16,
"lenVar": 40
},
"kernels_config": {
"python": {
"delete_cmd_postfix": "",
"delete_cmd_prefix": "del ",
"library": "var_list.py",
"varRefreshCmd": "print(var_dic_list())"
},
"r": {
"delete_cmd_postfix": ") ",
"delete_cmd_prefix": "rm(",
"library": "var_list.r",
"varRefreshCmd": "cat(var_dic_list()) "
}
},
"types_to_exclude": [
"module",
"function",
"builtin_function_or_method",
"instance",
"_Feature"
],
"window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 5
}