From b693d2d18296356d132dd9a830c5303b607be04f Mon Sep 17 00:00:00 2001 From: Oleg Nikonychev Date: Tue, 3 Oct 2023 13:22:22 +0300 Subject: [PATCH] refactor: examples by module (#262) --- .github/workflows/notebooks.yml | 7 +- README.md | 49 ++- ...py and query.ipynb => 1_quick_start.ipynb} | 94 +++-- examples/2- My first transaction.ipynb | 219 ------------ examples/2_bank.ipynb | 178 ++++++++++ examples/3_perp.ipynb | 292 +++++++++++++++ examples/4_spot.ipynb | 250 +++++++++++++ examples/5_staking.ipynb | 214 +++++++++++ examples/README.md | 12 +- examples/colab_notebook.ipynb | 333 ------------------ nibiru/chain_client.py | 6 +- nibiru/tx.py | 69 ++-- 12 files changed, 1052 insertions(+), 671 deletions(-) rename examples/{1- Connect to nibiru py and query.ipynb => 1_quick_start.ipynb} (56%) delete mode 100644 examples/2- My first transaction.ipynb create mode 100644 examples/2_bank.ipynb create mode 100644 examples/3_perp.ipynb create mode 100644 examples/4_spot.ipynb create mode 100644 examples/5_staking.ipynb delete mode 100644 examples/colab_notebook.ipynb diff --git a/.github/workflows/notebooks.yml b/.github/workflows/notebooks.yml index 30f3916d..1ab8cc64 100644 --- a/.github/workflows/notebooks.yml +++ b/.github/workflows/notebooks.yml @@ -33,6 +33,7 @@ jobs: # https://www.notion.so/nibiru/Resources-and-Repo-Configs-b31aa8074a2b419d80b0c946ed5efab0 DEVNET_NUMBER: ${{ secrets.DEVNET_NUMBER }} VALIDATOR_MNEMONIC: ${{ secrets.VALIDATOR_MNEMONIC }} + EXAMPLES_WALLET_MNEMONIC: ${{ secrets.EXAMPLES_WALLET_MNEMONIC }} steps: # ---------------------------------------------- # check-out repo and set-up python @@ -99,9 +100,9 @@ jobs: cd examples for filename in *; do if [[ $filename == *.ipynb ]] ; then - if [ $filename != "3- Arbitrage.ipynb" ] && [ $filename != "colab_notebook.ipynb" ]; then - jupyter nbconvert --to python "$filename" - fi + sed -i '/pip install nibiru/d' "$filename" + sed -i "s/put your mnemonic here.../$EXAMPLES_WALLET_MNEMONIC/g" "$filename" + jupyter nbconvert --to python "$filename" fi for filename in *; do if [[ $filename == *.py ]]; then diff --git a/README.md b/README.md index 8ff7c292..2cea618d 100644 --- a/README.md +++ b/README.md @@ -34,18 +34,6 @@ The `nibiru` package allows you to index, query, and send transactions on Nibiru The package is intended to be used by coders, developers, technically-skilled traders and data-scientists for building trading algorithms. -## Try running nibiru sdk online - -Open the google collab link below to try running Niburu code online: - - -

- -

-
- -Or go to the [examples folder](examples) to see the codes and run Jupyter notebooks locally. - ## Installation ```bash pip install nibiru # requires Python 3.8+ @@ -59,19 +47,16 @@ pip install nibiru # requires Python 3.8+ import json from nibiru import Network, ChainClient -client = ChainClient(Network.testnet(2)) +client = ChainClient(Network.testnet(3)) # Query perp markets -print(json.dumps(client.query.perp.markets(), indent=4)) +perp_markets = client.query.perp.markets() +print(json.dumps(perp_markets, indent=4)) # Query trader's positions -print( - json.dumps( - client.query.perp.all_positions( - trader="nibi1jle8khj3aennq24zx6g93aam9rt0fqhgyp4h52" - ), - indent=4) -) +trader_addr = "nibi1jle8khj3aennq24zx6g93aam9rt0fqhgyp4h52" +positions = client.query.perp.all_positions(trader=trader_addr) +print(json.dumps(positions, indent=4)) ``` ### Submitting transactions to the chain @@ -104,7 +89,7 @@ Use faucet to get some test tokens into your wallet: https://app.nibiru.fi/fauce ```python mnemonic = "put your mnemonic here..." -client = ChainClient(network=Network.testnet(2)) +client = ChainClient(network=Network.testnet(3)) client.authenticate(mnemonic=mnemonic) print(client.address) ``` @@ -124,19 +109,29 @@ output = client.tx.execute_msgs( is_long=True, margin=10, leverage=2, - ) + ), + wait_for_tx_resp=True, # wait for block and get tx response ) print(output) ``` You can broadcast any available transaction by passing its corresponding `Msg` to the `client.tx.execute_msgs` function. -## Documentation Website +## Nibiru SDK By Example + +The [examples folder](examples) folder contains jupyter notebooks for a quick introduction with example to the nibiru python sdk. The goal is to teach how to query and interact with any chain (testnet, localnet, mainnet). + +All the examples are available for running online in Google Colab. + -Documentation can be found here: [Nibiru-py documentation](https://nibiru-py.readthedocs.io/en/latest/index.html) +| Local | Google Colab | +|---------------------------------------------|--------------| +| [Quick start](examples/1_quick_start.ipynb) | [![Open in collab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/NibiruChain/py-sdk/blob/main/examples/1_quick_start.ipynb) | +| [Bank](examples/2_bank.ipynb) | [![Open in collab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/NibiruChain/py-sdk/blob/main/examples/2_bank.ipynb) | +| [Perpetuals](examples/3_perp.ipynb) | [![Open in collab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/NibiruChain/py-sdk/blob/main/examples/3_perp.ipynb) | +| [Spots](examples/4_spot.ipynb) | [![Open in collab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/NibiruChain/py-sdk/blob/main/examples/4_spot.ipynb) | +| [Staking](examples/5_staking.ipynb) | [![Open in collab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/NibiruChain/py-sdk/blob/main/examples/5_staking.ipynb) | -- Learn more about opening and managing your spot and perp positions [here](https://nibiru-py.readthedocs.io/en/latest/nibiru.sdks.tx.html#nibiru-sdks-tx-package) -- Learn about querying the chain using the Sdk [here](https://nibiru-py.readthedocs.io/en/latest/nibiru.clients.html#nibiru-clients-package) ## Contributing diff --git a/examples/1- Connect to nibiru py and query.ipynb b/examples/1_quick_start.ipynb similarity index 56% rename from examples/1- Connect to nibiru py and query.ipynb rename to examples/1_quick_start.ipynb index 460bbadc..736bbbfb 100644 --- a/examples/1- Connect to nibiru py and query.ipynb +++ b/examples/1_quick_start.ipynb @@ -1,40 +1,42 @@ { "cells": [ { - "cell_type": "code", - "execution_count": null, + "attachments": {}, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "import nibiru\n", - "from nibiru import Network, ChainClient\n", - "import json" + "# Connect to Nibiru Chain and Run Queries" ] }, + { + "cell_type": "markdown", + "source": [ + "### 1. Install Nibiru package" + ], + "metadata": { + "collapsed": false + } + }, { "cell_type": "code", "execution_count": null, - "metadata": {}, "outputs": [], "source": [ - "nibiru.__version__" - ] + "!pip install nibiru" + ], + "metadata": { + "collapsed": false + } }, { - "attachments": {}, "cell_type": "markdown", - "metadata": {}, "source": [ - "# Connect to a chain and run queries" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can connect to any chain (localnet, testnet or mainnet) using this python SDK. Let's connect to testnet-2 (aka itn-2)." - ] + "### 2. Create chain client\n", + "Using testnet-3 (aka itn-3) endpoint run queries." + ], + "metadata": { + "collapsed": false + } }, { "cell_type": "code", @@ -42,13 +44,20 @@ "metadata": {}, "outputs": [], "source": [ - "client = ChainClient(Network.testnet(2))" + "from nibiru import Network, ChainClient\n", + "import json\n", + "\n", + "# Shortcut to print responses\n", + "def print_json(obj):\n", + " print(json.dumps(obj, indent=2))\n", + "\n", + "client = ChainClient(Network.testnet(3))" ] }, { "cell_type": "markdown", "source": [ - "Run PERP module queries:" + "### 3. Run PERP module queries" ], "metadata": { "collapsed": false @@ -59,7 +68,8 @@ "execution_count": null, "outputs": [], "source": [ - "print(json.dumps(client.query.perp.markets(), indent=4))" + "perp_markets = client.query.perp.markets()\n", + "print_json(perp_markets)" ], "metadata": { "collapsed": false @@ -70,7 +80,10 @@ "execution_count": null, "outputs": [], "source": [ - "print(json.dumps(client.query.perp.all_positions(trader=\"nibi1jle8khj3aennq24zx6g93aam9rt0fqhgyp4h52\"), indent=4))" + "# Query perp positions of the trader\n", + "trader = \"nibi1jle8khj3aennq24zx6g93aam9rt0fqhgyp4h52\"\n", + "positions = client.query.perp.all_positions(trader=trader)\n", + "print_json(positions)" ], "metadata": { "collapsed": false @@ -79,7 +92,7 @@ { "cell_type": "markdown", "source": [ - "Run SPOT module queries:" + "### 4. Run SPOT module queries" ], "metadata": { "collapsed": false @@ -91,7 +104,8 @@ "metadata": {}, "outputs": [], "source": [ - "print(json.dumps(client.query.spot.pools(), indent=4))" + "spot_pools = client.query.spot.pools()\n", + "print_json(spot_pools)" ] }, { @@ -100,31 +114,9 @@ "metadata": {}, "outputs": [], "source": [ - "print(json.dumps(client.query.spot.total_pool_liquidity(1), indent=4))" + "pool_liquidity = client.query.spot.total_pool_liquidity(1)\n", + "print(pool_liquidity)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(json.dumps(client.query.spot.total_liquidity(), indent=4))" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can find the documentation on query you can execute on the official documentation website: https://nibiru-py.readthedocs.io/en/latest/nibiru.query_clients.html" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [] } ], "metadata": { diff --git a/examples/2- My first transaction.ipynb b/examples/2- My first transaction.ipynb deleted file mode 100644 index 39d4b2f9..00000000 --- a/examples/2- My first transaction.ipynb +++ /dev/null @@ -1,219 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import json\n", - "import nibiru\n", - "from nibiru import Network, ChainClient, Msg, PrivateKey" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "nibiru.__version__" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create a wallet and get test tokens from faucet" - ] - }, - { - "cell_type": "markdown", - "source": [ - "To create a new wallet, generate a mnemonic key using any service or using SDK call:" - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "mnemonic, private_key = PrivateKey.generate()\n", - "print(mnemonic)\n", - "# Example OUTPUT:\n", - "# enlist satisfy inspire hobby romance caught great neither kitchen unfair cage awesome update fade object eagle sun ordinary again journey spell gown tiger spin\n", - "\n", - "# Your wallet address\n", - "print(private_key.to_public_key().to_address().to_acc_bech32())" - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "markdown", - "source": [ - "Store your mnemonic key in a safe place and use it going forward.\n", - "Use faucet to get some test tokens into your wallet: https://app.nibiru.fi/faucet" - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "markdown", - "source": [ - "Сreate your chain client and authenticate with the mnemoniс generated" - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "mnemonic = \"put your mnemonic here...\"\n", - "client = ChainClient(network=Network.testnet(2))\n", - "client.authenticate(mnemonic=mnemonic)\n", - "print(client.address)" - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "markdown", - "source": [ - "Check your bank balances. If the faucet succeded - your wallet should not be empty." - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "print(client.query.get_bank_balances(client.address))" - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pair = \"ubtc:unusd\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "output = client.tx.execute_msgs(\n", - " Msg.perp.open_position(\n", - " pair=pair,\n", - " is_long=True,\n", - " margin=10,\n", - " leverage=1,\n", - " )\n", - ")\n", - "print(output)" - ] - }, - { - "cell_type": "markdown", - "source": [ - "If your TX has succeeded, you should get output something like:\n", - "\n", - "`ExecuteTxResp(code=0, tx_hash='A2E9935BC06C360209FA7A9760F0CF54FA0A9DE0CAB6AF2D8687886E7A5F613F', log='[]')`\n", - "\n", - "Query your TX hash to get TX details:" - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "output = client.query.tx_by_hash(\"A2E9935BC06C360209FA7A9760F0CF54FA0A9DE0CAB6AF2D8687886E7A5F613F\")\n", - "print(output)" - ] - }, - { - "cell_type": "markdown", - "source": [ - "Or directly query the state of your position:" - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "position = client.query.perp.position(pair, client.address)\n", - "print(json.dumps(position, indent=4))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can now query the balance of the trader address using the query we saw in the previous notebook" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Documentation for msgs can be found there: https://nibiru-py.readthedocs.io/en/latest/nibiru.msg.html" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3.10.4 ('nibi')", - "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.10.4" - }, - "orig_nbformat": 4, - "vscode": { - "interpreter": { - "hash": "23c82724f6800107d27a1bc097df076b7f523b299f39e774e7f2b8b0c7154a18" - } - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/2_bank.ipynb b/examples/2_bank.ipynb new file mode 100644 index 00000000..ea9ab402 --- /dev/null +++ b/examples/2_bank.ipynb @@ -0,0 +1,178 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Nibiru Bank Queries and Transactions\n", + "Examples of checking balances and token transfers." + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "### 1. Install Nibiru package" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "!pip install nibiru" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "### 2. Create chain client and run queries\n", + "Using testnet-3 (aka itn-3) endpoint run queries." + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "import json\n", + "from nibiru import Network, ChainClient, Msg, Coin, TxConfig\n", + "\n", + "client = ChainClient(network=Network.testnet(3))" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Shortcut to print responses\n", + "def print_json(obj):\n", + " print(json.dumps(obj, indent=2))" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Query all balances of the user\n", + "user_address = \"nibi1jle8khj3aennq24zx6g93aam9rt0fqhgyp4h52\"\n", + "balances = client.query.get_bank_balances(user_address)[\"balances\"]\n", + "print_json(balances)" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Query the balance of the specific token\n", + "balance = client.query.get_bank_balance(user_address, \"unusd\")[\"balance\"]\n", + "print_json(balance)" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "### 3. Authenticate to run transactions\n", + "Assuming you have your wallet mnemonic key (or private key) and some test tokens from the faucet.\n", + "See: https://app.nibiru.fi/faucet" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "mnemonic = \"put your mnemonic here...\"\n", + "\n", + "# Let's set high gas multiplier to avoid out of gas errors. Could be customized per tx.\n", + "tx_config = TxConfig(gas_multiplier=5)\n", + "client = ChainClient(Network.testnet(3), tx_config=tx_config)\n", + "client.authenticate(mnemonic=mnemonic)" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "### 4. Transfer tokens" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "receiver_addr = \"nibi1etuegt9m6m975xrkrzrxdnu00gz99pke9w44t6\"\n", + "output = client.tx.execute_msgs(\n", + " Msg.bank.send(\n", + " to_address=receiver_addr,\n", + " coins=[Coin(1, \"unibi\"), Coin(2, \"unusd\")]\n", + " ),\n", + " wait_for_tx_resp=True, # wait for block and get tx response\n", + ")\n", + "# Expected tx response code 0\n", + "print(f\"tx response Code: {output['code']}\")\n", + "if output[\"code\"] == 0:\n", + " balances = client.query.get_bank_balances(receiver_addr)[\"balances\"]\n", + " print_json(balances)" + ], + "metadata": { + "collapsed": false + } + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/examples/3_perp.ipynb b/examples/3_perp.ipynb new file mode 100644 index 00000000..730de00e --- /dev/null +++ b/examples/3_perp.ipynb @@ -0,0 +1,292 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Nibiru PERP (Perpetuals) Queries and Transactions\n", + "Perpetuals is an instrument for margin trading.\n", + "See documentation here: https://nibiru.fi/docs/ecosystem/nibi-perps/" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "### 1. Install Nibiru package" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "!pip install nibiru" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "### 2. Create chain client and run queries\n", + "Using testnet-3 (aka itn-3) endpoint run queiries" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "import json\n", + "from nibiru import Network, ChainClient, Msg, Coin, TxConfig\n", + "from nibiru.exceptions import QueryError\n", + "\n", + "client = ChainClient(network=Network.testnet(3))" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Shortcut to print responses\n", + "def print_json(obj):\n", + " print(json.dumps(obj, indent=2))\n", + "\n", + "# Pretty print trader positions\n", + "def print_trader_positions(trader_address):\n", + " print(f\"Trader {trader_address} positions:\")\n", + " print_json(\n", + " client.query.perp.all_positions(trader_address)\n", + " )" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Query perp markets\n", + "markets = client.query.perp.markets()\n", + "print_json(markets)" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Query perp positions of a trader\n", + "trader_address = \"nibi1jle8khj3aennq24zx6g93aam9rt0fqhgyp4h52\"\n", + "print_trader_positions(trader_address)" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Query perp position of a trader on a specific market\n", + "pair = \"ubtc:unusd\"\n", + "try:\n", + " position = client.query.perp.position(pair, trader_address)\n", + " print_json(position)\n", + "except QueryError:\n", + " print(\"Position not found\")" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "### 3. Authenticate to run transactions\n", + "Assuming you have your wallet mnemonic key (or private key) and some test tokens from the faucet.\n", + "See: https://app.nibiru.fi/faucet" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "mnemonic = \"put your mnemonic here...\"\n", + "\n", + "# Let's set high gas multiplier to avoid out of gas errors. Could be customized per tx.\n", + "tx_config = TxConfig(gas_multiplier=5)\n", + "client = ChainClient(Network.testnet(3), tx_config=tx_config)\n", + "client.authenticate(mnemonic=mnemonic)\n", + "\n", + "# Balances check\n", + "balances = client.query.get_bank_balances(client.address)\n", + "print_json(balances[\"balances\"])" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "### 4. Run PERP transactions\n", + "Open perp position with 5x leverage:" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "pair = \"ubtc:unusd\" # u-prefix means micro (1 unusd = 1/1000_000 NUSD)\n", + "\n", + "output = client.tx.execute_msgs(\n", + " Msg.perp.open_position(\n", + " pair=pair,\n", + " is_long=True,\n", + " margin=10,\n", + " leverage=5,\n", + " ),\n", + " wait_for_tx_resp=True, # wait for block and get tx response\n", + ")\n", + "# Expected tx response code 0\n", + "print(f\"tx response Code: {output['code']}\")\n", + "if output[\"code\"] == 0:\n", + " print_trader_positions(client.address)" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "Add some margin if position becomes unhealthy (low margin ratio):" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "output = client.tx.execute_msgs(\n", + " Msg.perp.add_margin(pair=pair, margin=Coin(1, \"unusd\")),\n", + " wait_for_tx_resp=True,\n", + ")\n", + "# Expected tx response code 0\n", + "print(f\"tx response code: {output['code']}\")\n", + "if output[\"code\"] == 0:\n", + " print_trader_positions(client.address)" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "Remove margin if needed:" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "output = client.tx.execute_msgs(\n", + " Msg.perp.remove_margin(pair=pair, margin=Coin(1, \"unusd\")),\n", + " wait_for_tx_resp=True,\n", + ")\n", + "# Expected tx response code 0\n", + "print(f\"tx response code: {output['code']}\")\n", + "if output[\"code\"] == 0:\n", + " print_trader_positions(client.address)" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "Close position:" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Close position\n", + "output = client.tx.execute_msgs(\n", + " Msg.perp.close_position(pair=pair),\n", + " wait_for_tx_resp=True,\n", + ")\n", + "# Expected tx response code 0\n", + "print(f\"tx response code: {output['code']}\")\n", + "if output[\"code\"] == 0:\n", + " print_trader_positions(client.address)" + ], + "metadata": { + "collapsed": false + } + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/examples/4_spot.ipynb b/examples/4_spot.ipynb new file mode 100644 index 00000000..6f72a4e6 --- /dev/null +++ b/examples/4_spot.ipynb @@ -0,0 +1,250 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Nibiru Spots (Swaps) Queries and Transactions\n", + "Examples of executing asset swaps." + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "### 1. Install Nibiru package" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "!pip install nibiru" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "### 2. Create chain client and run queries\n", + "Using testnet-3 (aka itn-3) endpoint run queries." + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "import json\n", + "from nibiru import Network, ChainClient, Msg, Coin, TxConfig\n", + "from nibiru.exceptions import QueryError\n", + "\n", + "client = ChainClient(network=Network.testnet(3))" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Shortcut to print responses\n", + "def print_json(obj):\n", + " print(json.dumps(obj, indent=2))" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Query spot params (fees and whitelisted assets)\n", + "spot_params = client.query.spot.params()\n", + "print_json(spot_params)" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Query spot pools\n", + "pools = client.query.spot.pools()\n", + "print_json(pools)" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "### 3. Authenticate to run transactions\n", + "Assuming you have your wallet mnemonic key (or private key) and some test tokens from the faucet.\n", + "See: https://app.nibiru.fi/faucet" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "mnemonic = \"put your mnemonic here...\"\n", + "\n", + "# Let's set high gas multiplier to avoid out of gas errors. Could be customized per tx.\n", + "tx_config = TxConfig(gas_multiplier=5)\n", + "client = ChainClient(Network.testnet(3), tx_config=tx_config)\n", + "client.authenticate(mnemonic=mnemonic)\n", + "\n", + "# Balances check\n", + "balances = client.query.get_bank_balances(client.address)\n", + "print_json(balances[\"balances\"])" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "### 4. Run Spot transactions\n", + "Swap tokens:" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "pool_id = 1 # select the pool from the query above\n", + "token_in = \"unusd\" # check that token is in the pool\n", + "token_out = \"uusdt\" # check that token is in the pool\n", + "\n", + "output = client.tx.execute_msgs(\n", + " Msg.spot.swap(\n", + " pool_id=pool_id,\n", + " token_in=Coin(10, token_in),\n", + " token_out_denom=token_out,\n", + " ),\n", + " wait_for_tx_resp=True, # wait for block and get tx response\n", + ")\n", + "# Expected tx response code 0\n", + "print(f\"tx response Code: {output['code']}\")\n", + "if output[\"code\"] == 0:\n", + " balances = client.query.get_bank_balances(client.address)\n", + " print_json(balances[\"balances\"])" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "Join spot pool. After joining the pool you will have a pool shares in your balance with asset name like \"nibiru/pool/1\"." + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "output = client.tx.execute_msgs(\n", + " Msg.spot.join_pool(\n", + " pool_id=pool_id,\n", + " tokens=[Coin(10, token_in), Coin(5, token_out)]\n", + " ),\n", + " wait_for_tx_resp=True,\n", + ")\n", + "# Expected tx response code 0\n", + "print(f\"tx response code: {output['code']}\")\n", + "if output[\"code\"] == 0:\n", + " balance = client.query.get_bank_balance(client.address, f\"nibiru/pool/{pool_id}\")\n", + " print_json(balance[\"balance\"])\n", + " total_shares_amount = balance[\"balance\"][\"amount\"]" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "Exit pool. Pool shares will disappear from balances list." + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "output = client.tx.execute_msgs(\n", + " Msg.spot.exit_pool(\n", + " pool_id=pool_id,\n", + " pool_shares=Coin(total_shares_amount, f\"nibiru/pool/{pool_id}\")\n", + " ),\n", + " wait_for_tx_resp=True,\n", + ")\n", + "# Expected tx response code 0\n", + "print(f\"tx response code: {output['code']}\")\n", + "if output[\"code\"] == 0:\n", + " balances = client.query.get_bank_balances(client.address)\n", + " print_json(balances[\"balances\"])" + ], + "metadata": { + "collapsed": false + } + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/examples/5_staking.ipynb b/examples/5_staking.ipynb new file mode 100644 index 00000000..61e6fd44 --- /dev/null +++ b/examples/5_staking.ipynb @@ -0,0 +1,214 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Nibiru Staking Queries and Transactions\n", + "Examples of querying validators, delegating and unbonding." + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "### 1. Install Nibiru package" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "!pip install nibiru" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "### 2. Create chain client and run queries\n", + "Using testnet-3 (aka itn-3) endpoint run queries." + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "import json\n", + "from nibiru import Network, ChainClient, Msg, Coin, TxConfig\n", + "\n", + "client = ChainClient(network=Network.testnet(3))" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Shortcut to print responses\n", + "def print_json(obj):\n", + " print(json.dumps(obj, indent=2))" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Query validators\n", + "validators = client.query.staking.validators()[\"validators\"]\n", + "print_json(validators)\n", + "\n", + "first_validator = validators[0]" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Query delegations to the first validator\n", + "delegations = client.query.staking.delegations_to(first_validator[\"operator_address\"])[\"delegation_responses\"]\n", + "print_json(delegations)" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "### 3. Authenticate to run transactions\n", + "Assuming you have your wallet mnemonic key (or private key) and some test tokens from the faucet.\n", + "See: https://app.nibiru.fi/faucet" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "mnemonic = \"put your mnemonic here...\"\n", + "\n", + "# Let's set high gas multiplier to avoid out of gas errors. Could be customized per tx.\n", + "tx_config = TxConfig(gas_multiplier=5)\n", + "client = ChainClient(Network.testnet(3), tx_config=tx_config)\n", + "client.authenticate(mnemonic=mnemonic)\n", + "\n", + "# Balances check\n", + "balances = client.query.get_bank_balances(client.address)\n", + "print_json(balances[\"balances\"])" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "### 4. Run Staking Transactions\n", + "Delegate to a validator:" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "output = client.tx.execute_msgs(\n", + " Msg.staking.delegate(\n", + " validator_address=first_validator[\"operator_address\"],\n", + " amount=10, # amount in unibi\n", + " ),\n", + " wait_for_tx_resp=True, # wait for block and get tx response\n", + ")\n", + "# Expected tx response code 0\n", + "print(f\"tx response Code: {output['code']}\")\n", + "if output[\"code\"] == 0:\n", + " delegations = client.query.staking.delegations(delegator_addr=client.address)[\"delegation_responses\"]\n", + " print_json(delegations)" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "Undelegate (unbond) tokens from validator:" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "output = client.tx.execute_msgs(\n", + " Msg.staking.undelegate(\n", + " validator_address=first_validator[\"operator_address\"],\n", + " amount=10, # amount in unibi\n", + " ),\n", + " wait_for_tx_resp=True,\n", + ")\n", + "# Expected tx response code 0\n", + "print(f\"tx response code: {output['code']}\")\n", + "if output[\"code\"] == 0:\n", + " unbondings = client.query.staking.unbonding_delegations(delegator_addr=client.address)[\"unbonding_responses\"]\n", + " print_json(unbondings)" + ], + "metadata": { + "collapsed": false + } + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/examples/README.md b/examples/README.md index e87e19ff..d5ffbd4b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,3 +1,13 @@ -# Example folder +~~# Examples The example folder contains jupyter notebooks for a quick introduction with example to the nibiru python sdk. The goal is to teach how to query and interact with any chain (testnet, localnet, mainnet). + +All the examples are available for running online in Google Colab. + +| Local | Google Colab | +| ------------- |--------------| +| [Quick start](1_quick_start.ipynb) | [![Open in collab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/NibiruChain/py-sdk/blob/main/examples/1_quick_start.ipynb) | +| [Bank](2_bank.ipynb) | [![Open in collab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/NibiruChain/py-sdk/blob/main/examples/2_bank.ipynb) | +| [Perpetuals](3_perp.ipynb) | [![Open in collab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/NibiruChain/py-sdk/blob/main/examples/3_perp.ipynb) | +| [Spots](4_spot.ipynb) | [![Open in collab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/NibiruChain/py-sdk/blob/main/examples/4_spot.ipynb) | +| [Staking](5_staking.ipynb) | [![Open in collab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/NibiruChain/py-sdk/blob/main/examples/5_staking.ipynb) | diff --git a/examples/colab_notebook.ipynb b/examples/colab_notebook.ipynb deleted file mode 100644 index c4c0dc51..00000000 --- a/examples/colab_notebook.ipynb +++ /dev/null @@ -1,333 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Introduction to Nibiru SDK\n", - "\n", - "The python sdk allows you to create queries and send transactions to a Nibiru chain.\n", - "It allows to interact with all the modules and can be leveraged to automate trading strategies or monitor them.\n", - "This notebook will guide you on opening and closing positions." - ] - }, - { - "cell_type": "markdown", - "source": [ - "### 1. Install Nibiru package" - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "!pip install nibiru" - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2. Prepare wallet mnemonic key (or private key)\n", - "If you don't have a mnemonic key, generate it using some online service or run the code below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "from nibiru import PrivateKey\n", - "mnemonic, private_key = PrivateKey.generate()\n", - "print(mnemonic)" - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "markdown", - "source": [ - "### 3. Get test tokens from the nibiru faucet\n", - "Faucet URL: [https://app.nibiru.fi/faucet](https://app.nibiru.fi/faucet)" - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "markdown", - "source": [ - "### 4. Create nibiru chain client and authenticate using mnemonic\n", - "The client below uses ITN-2 Nibiru testnet chain.\n", - "Use your mnemonic key or private key to authenticate.\n", - "If you took money from the faucet, your balances should not be empty." - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import json\n", - "from nibiru import Network, ChainClient, Msg, Coin\n", - "\n", - "mnemonic = \"put your mnemonic here\"\n", - "client = ChainClient(network=Network.testnet(2))\n", - "client.authenticate(mnemonic=mnemonic) # alternatively use private_key_hex = your_key_hex\n", - "\n", - "# Check balance\n", - "print(json.dumps(client.query.get_bank_balances(client.address)[\"balances\"], indent=2))" - ] - }, - { - "cell_type": "markdown", - "source": [ - "### 5. Create a tx to open perp trading position\n", - "Position is: LONG (=BUY),\n", - "\n", - "pair = ubtc:unusd: prefix `u` means `μ` or micro. So `unusd = micro unusd = 1/1000_000 nusd`, same for ubtc.\n", - "\n", - "margin = 10: (value in quote amount which is unusd in our case)\n", - "\n", - "leverage = 2: your position notional will be 2 * 10" - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pair = \"ubtc:unusd\"\n", - "\n", - "output = client.tx.execute_msgs(\n", - " Msg.perp.open_position(\n", - " pair=pair,\n", - " is_long=True,\n", - " margin=10,\n", - " leverage=2,\n", - " )\n", - ")\n", - "print(output)" - ] - }, - { - "cell_type": "markdown", - "source": [ - "If your TX has succeeded, you should get output something like:\n", - "```\n", - "ExecuteTxResp(code=0, tx_hash='946A4252423A0AC183C1BAA2B76CCB25512DC9A18861D8782EFE7F14512CBDF6', log='[]')\n", - "```\n", - "Query your TX hash to get TX details:" - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "output = client.query.tx_by_hash(\"707F725DA2A04FD865FA6FEAF431DF89459F6A5C076B5AF93C627979C9B6CDF1\")\n", - "print(output)" - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "markdown", - "source": [ - "Or directly query the state of your position:" - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "position = client.query.perp.position(pair, client.address)\n", - "print(json.dumps(position, indent=4))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can then add margin to our position:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "output = client.tx.execute_msgs(\n", - " Msg.perp.add_margin(\n", - " pair=pair,\n", - " margin=Coin(1, \"unusd\"),\n", - " )\n", - ")\n", - "print(output)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Or remove margin:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "output = client.tx.execute_msgs(\n", - " Msg.perp.remove_margin(\n", - " pair=pair,\n", - " margin=Coin(1, \"unusd\")\n", - " )\n", - ")\n", - "print(output)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can then totally close the position:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "output = client.tx.execute_msgs(\n", - " Msg.perp.close_position(\n", - " pair=pair,\n", - " )\n", - ")\n", - "print(output)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Multi-transactions\n", - "We can build multiple messages into a single transaction to be sure that they are executed consecutively.\n", - "\n", - "It can be useful for example to send tokens after removing margins from a position." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Lets create a transction with:\n", - "- Agent open a position\n", - "- Agent add margin\n", - "- Agent close the position" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "output = client.tx.execute_msgs(\n", - " [\n", - " Msg.perp.open_position(\n", - " pair=pair,\n", - " is_long=True,\n", - " margin=10,\n", - " leverage=1,\n", - " ),\n", - " Msg.perp.add_margin(\n", - " pair=pair,\n", - " margin=Coin(1, \"unusd\"),\n", - " ),\n", - " Msg.perp.close_position(\n", - " pair=pair,\n", - " ),\n", - " ]\n", - ")\n", - "print(output)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "position = client.query.perp.all_positions(client.address)\n", - "print(json.dumps(position, indent=4))" - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "markdown", - "source": [ - "That's it! Good luck and happy Nibiru coding!" - ], - "metadata": { - "collapsed": false - } - } - ], - "metadata": { - "kernelspec": { - "display_name": "nibiru-py-k3GTQe1V", - "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.9.16" - }, - "orig_nbformat": 4, - "vscode": { - "interpreter": { - "hash": "b332d8d531602c345a00d4444116ed5f8f40664116717716e6e6b2e5640a7573" - } - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/nibiru/chain_client.py b/nibiru/chain_client.py index e293f5ba..5e4a9e06 100644 --- a/nibiru/chain_client.py +++ b/nibiru/chain_client.py @@ -107,13 +107,13 @@ def query(self): @property def tx(self): if not self.is_authenticated(): - return ValueError("Not authenticated! Call authenticate() to use tx client") + raise ValueError("Not authenticated! Call authenticate() to use tx client") return self._tx @property def private_key(self): if not self.is_authenticated(): - return ValueError( + raise ValueError( "Not authenticated! Call authenticate() to get private key" ) return self._private_key @@ -121,7 +121,7 @@ def private_key(self): @property def address(self): if not self.is_authenticated(): - return ValueError( + raise ValueError( "Not authenticated! Call authenticate() to get your wallet address" ) return self._address diff --git a/nibiru/tx.py b/nibiru/tx.py index c3c40b23..d0c0dbca 100644 --- a/nibiru/tx.py +++ b/nibiru/tx.py @@ -7,18 +7,19 @@ """ import logging import pprint +import time from numbers import Number -from typing import Iterable, List, Optional, Tuple, Union +from typing import Dict, Iterable, List, Optional, Tuple, Union from google.protobuf import any_pb2, message +from grpc import StatusCode +from grpc._channel import _InactiveRpcError from nibiru import exceptions, jsonrpc from nibiru import pytypes as pt from nibiru import tmrpc, wallet from nibiru.exceptions import SimulationError, TxError from nibiru.grpc_client import GrpcClient - -# from google.protobuf.json_format import MessageToDict from nibiru_proto.cosmos.base.abci.v1beta1 import abci_pb2 as abci_type from nibiru_proto.cosmos.base.v1beta1 import coin_pb2 as cosmos_base_coin_pb from nibiru_proto.cosmos.base.v1beta1.coin_pb2 import Coin @@ -64,7 +65,8 @@ def execute_msgs( msgs: Union[pt.PythonMsg, List[pt.PythonMsg]], sequence: Optional[int] = None, tx_config: Optional[pt.TxConfig] = None, - ) -> pt.ExecuteTxResp: + wait_for_tx_resp=False, + ) -> Union[pt.ExecuteTxResp, Dict]: """ Broadcasts messages to a node in a single transaction. This function first simulates the corresponding transaction to estimate the amount of @@ -84,7 +86,9 @@ def execute_msgs( get_sequence_from_node (bool, optional): Specifies whether the sequence comes from the local value or the lcd endpoint. Defaults to False. - + wait_for_tx_resp (bool): If True then waits for block results and + returns tx response, otherwize returns just tx hash and status. + Defaults to False. Raises: SimulationError: If broadcasting fails during the simulation. TxError: If the response code is nonzero, the 'TxError' includes @@ -122,6 +126,7 @@ def execute_msgs( msgs=msgs, sequence=sequence, tx_config=tx_config, + wait_for_tx_resp=wait_for_tx_resp, ) if address: address.decrease_sequence() @@ -139,26 +144,31 @@ def execute_msgs( tx_hash=jsonrcp_resp.result.get("hash"), log=jsonrcp_resp.result.get("log"), ) - if execute_resp.code != 0: + + if execute_resp.code == 0: + if not wait_for_tx_resp: + return execute_resp + + # Poll tx response + tx_hash = execute_resp.tx_hash + timeout = 10000 # milliseconds until failure + step = 500 # milliseconds between checks + for attempt in range(0, timeout, step): + try: + return self.client.tx_by_hash(tx_hash)["tx_response"] + except _InactiveRpcError as ie: + if ie.code() == StatusCode.NOT_FOUND: + time.sleep(step / 1000) + continue + else: + raise ie + except Exception as ex: + raise ex + + else: address.decrease_sequence() raise TxError(execute_resp.log) - return execute_resp - # ------------------------------------------------ - # gRPC version: TODO - add back as feature. - # ------------------------------------------------ - # tx_resp: dict[str, Any] = MessageToDict(tx_resp) - # tx_hash: Union[str, None] = tx_resp.get("txhash") - # assert tx_hash, f"null txhash on tx_resp: {tx_resp}" - # tx_output: tx_service.GetTxResponse = self.client.tx_by_hash( - # tx_hash=tx_hash - # ) - # - # if tx_output.get("tx_response").get("code") != 0: - # address.decrease_sequence() - # raise TxError(tx_output.raw_log) - # - # tx_output["rawLog"] = json.loads(tx_output.get("rawLog", "{}")) - # return pt.RawSyncTxResp(tx_output) + except exceptions.ErrorQueryTx as err: logging.info("ErrorQueryTx") logging.error(err) @@ -180,7 +190,7 @@ def execute_tx( def compute_gas_wanted() -> float: # Related to https://github.com/cosmos/cosmos-sdk/issues/14405 # TODO We should consider adding the behavior mentioned by tac0turtle. - gas_wanted = gas_estimate * 1.25 # apply gas multiplier + gas_wanted = gas_estimate if conf.gas_wanted > 0: gas_wanted = conf.gas_wanted elif conf.gas_multiplier > 0: @@ -350,16 +360,7 @@ def ensure_tx_config( Returns: (pt.TxConfig): The new value for the TxClient.tx_config. """ - tx_config: pt.TxConfig - if new_tx_config is not None: - tx_config = new_tx_config - elif self.tx_config is None: - # Set as the default if the TxConfig has not been initialized. - tx_config = pt.TxConfig() - else: - pass - tx_config = self.tx_config - return tx_config + return new_tx_config or self.tx_config or pt.TxConfig() # TODO: Refactor this into a dataclass for brevity.