diff --git a/README.md b/README.md index fb311d6e..b7e39576 100644 --- a/README.md +++ b/README.md @@ -209,7 +209,46 @@ Cast current deployed addresses to vyper contract ### Jupyter Integration -You can use Jupyter to execute titanoboa code in network mode from your browser using any wallet, using `boa.integrations.jupyter.BrowserSigner` as a drop-in replacement for `eth_account.Account`. For a full example, please see [this example Jupyter notebook](examples/jupyter_browser_signer.ipynb) +You can use Jupyter to execute titanoboa code in network mode from your browser using any wallet. +We provide a `BrowserSigner` as a drop-in replacement for `eth_account.Account`. +The `BrowserRPC` may be used to interact with the RPC server from the browser. + +For a full example, please see [this example Jupyter notebook](examples/jupyter_browser_signer.ipynb) + +#### JupyterLab + +Before being able to use the plugin, you need to install it. +You can do this by running the following command in the terminal: + +```bash +pip install titanoboa +jupyter lab extension enable boa +``` +To activate our IPython extension, you need to run the following command in the notebook: +```jupyter +%load_ext boa.ipython +``` + +For ease of use, add the following to `ipython_config.py`: +```python +c.InteractiveShellApp.extensions = ["boa.ipython"] +c.InteractiveShellApp.exec_lines = ['import boa'] +``` + +We provide a multi-user setup with JupyterLab in [try.vyperlang.org](https://try.vyperlang.org/), where the extension is installed and activated. +The source code for this website is available in the [GitHub repository](https://github.com/vyperlang/try.vyperlang.org). + +#### Colab +It is also possible to run our plugin in [Google Colab](https://colab.research.google.com/). +To do this, you need to install the plugin by running the following commands: +```jupyter +!pip install titanoboa +%load_ext boa.ipython +``` + +#### IPython extensions + +This activates the `%%vyper`, `%%contract` and `%%eval` magics. ### Basic tests diff --git a/boa/integrations/jupyter/jupyter.js b/boa/integrations/jupyter/jupyter.js index 310989a4..047ded94 100644 --- a/boa/integrations/jupyter/jupyter.js +++ b/boa/integrations/jupyter/jupyter.js @@ -87,10 +87,22 @@ /** Call the backend when the given function is called, handling errors */ const handleCallback = func => async (token, ...args) => { if (!colab) { - // Check if the cell was already executed. In Colab, eval_js() doesn't replay. + // Check backend and whether cell was executed. In Colab, eval_js() doesn't replay. const response = await fetch(`${base}/titanoboa_jupyterlab/callback/${token}`); - // !response.ok indicates the cell has already been executed - if (!response.ok) return; + if (response.status === 404 && response.headers.get('Content-Type') === 'application/json') { + return; // the cell has already been executed + } + if (!response.ok) { + const error = 'Could not connect to the titanoboa backend. Please make sure the Jupyter extension is installed by running the following command:'; + const command = 'jupyter lab extension enable boa'; + if (element) { + element.style.display = "block"; // show the output element in JupyterLab + element.innerHTML = `
${command}`; + } else { + prompt(error, command); + } + return; + } } const body = stringify(await parsePromise(func(...args))); diff --git a/docs/source/testing.rst b/docs/source/testing.rst index 71075f27..cbfc90e1 100644 --- a/docs/source/testing.rst +++ b/docs/source/testing.rst @@ -151,7 +151,8 @@ ipython Vyper Cells Titanoboa supports ipython Vyper cells. This means that you can write Vyper code in a ipython/Jupyter Notebook environment and execute it as if it was a Python cell (the contract will be compiled instead, and a ``ContractFactory`` will be returned). -To enable this feature, execute ``%load_ext boa.ipython`` in a cell. +You can use Jupyter to execute titanoboa code in network mode from your browser using any wallet, using your wallet to sign transactions and call the RPC. +For a full example, please see `this example Jupyter notebook <../../examples/jupyter_browser_signer.ipynb>`_. .. code-block:: python diff --git a/examples/jupyter_browser_signer.ipynb b/examples/jupyter_browser_signer.ipynb index 886eb00b..5da5f571 100644 --- a/examples/jupyter_browser_signer.ipynb +++ b/examples/jupyter_browser_signer.ipynb @@ -6,130 +6,22 @@ "id": "94744db8", "metadata": {}, "outputs": [], - "source": [ - "import boa; from boa.network import NetworkEnv" - ] + "source": "%load_ext boa.ipython" }, { - "cell_type": "code", - "execution_count": 2, - "id": "ff9dfb06", "metadata": {}, - "outputs": [], - "source": [ - "%load_ext boa.ipython" - ] - }, - { "cell_type": "code", - "execution_count": 3, - "id": "9f241bf5", - "metadata": {}, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "require.config({\n", - " paths: {\n", - " //ethers: \"https://cdnjs.cloudflare.com/ajax/libs/ethers/5.7.2/ethers.umd.min\"\n", - " ethers: \"https://cdnjs.cloudflare.com/ajax/libs/ethers/6.4.2/ethers.umd.min\"\n", - " }\n", - "});\n", - "\n", - "require(['ethers'], function(ethers) {\n", - " // Initialize ethers\n", - " let provider = new ethers.BrowserProvider(window.ethereum);\n", - "\n", - " // check that we have a signer for this account\n", - " Jupyter.notebook.kernel.comm_manager.register_target('get_signer', function(c, msg) {\n", - " // console.log(\"get_signer created\", c)\n", - " c.on_msg(function(msg) {\n", - " // console.log(\"get_signer called\", c)\n", - " let account = msg.content.data.account\n", - " provider.getSigner(account).then(signer => {\n", - " // console.log(\"success\", signer)\n", - " c.send({\"success\": signer});\n", - " }).catch(function(error) {\n", - " console.error(\"got error, percolating up:\", error);\n", - " c.send({\"error\": error});\n", - " });\n", - " });\n", - " });\n", - "\n", - " Jupyter.notebook.kernel.comm_manager.register_target(\"send_transaction\", function(c, msg) {\n", - " c.on_msg(function(msg) {\n", - " let tx_data = msg.content.data.transaction_data;\n", - " let account = msg.content.data.account\n", - " provider.getSigner(account).then(signer => {\n", - " signer.sendTransaction(tx_data).then(response => {\n", - " console.log(response);\n", - " c.send({\"success\": response});\n", - " }).catch(function(error) {\n", - " console.error(\"got error, percolating up:\", error);\n", - " c.send({\"error\": error});\n", - " });\n", - " }).catch(function(error) {\n", - " console.error(\"got error, percolating up:\", error);\n", - " c.send({\"error\": error});\n", - " });\n", - " });\n", - " });\n", - "});\n", - "\n", - "Jupyter.notebook.kernel.comm_manager.register_target(\"test_comm\", function(comm, msg) {\n", - " console.log(\"ENTER\", comm);\n", - " /*comm.on_close(function(msg) {\n", - " console.log(\"CLOSING\", msg);\n", - " });\n", - " */\n", - "\n", - " comm.on_msg(function(msg) {\n", - " console.log(\"ENTER 2\", comm);\n", - " console.log(\"ENTER 3\", msg.content.data);\n", - " setTimeout(() => {\n", - " comm.send({\"success\": \"hello\", \"echo\": msg.content.data});\n", - " comm.close();\n", - " console.log(comm);\n", - " }, 350);\n", - " });\n", - "});\n" - ], - "text/plain": [ - "