Building a MelonBot

Using MelonJS to trade programmatically on behalf of a Melon fund

Erin Koen
Erin Koen
May 6, 2020 · 9 min read
Make sure your bot buys low and sells high.

The Melon Protocol has been designed to automate and simplify the boring parts of asset management. For about $50 in Ethereum gas, you can spin up a fund that will perform all of its own accounting functions, manage investments and redemptions, and enforce compliance mandates programatically. And since all of these actions are performed on chain, it’s 100% transparent and able to be audited. As a manger you can build an immutable track record to show your prowess, and as an investor you can have full confidence in the fund where you’re parking your assets.

The fun part of an asset manager’s job, managing risk, has remained largely manual on Melon to date. To the best of our knowledge, trades have exclusively been made via the Melon Terminal. However, with the recent release of MelonJS, it’s possible to automate trading strategies that run in the background, without having to manually enter and execute trades.

This series of blog posts will be an evolving document, starting with outlining the most basic functions that a trading bot requires before moving on to deployment and potentially strategy.

To start, I’ll make the assumption that we’re building a very basic if this then that bot. In other words, it will monitor the markets for a certain condition (this), and when that condition is met, adjust the user's portfolio according to whatever rules the trader has put in place (that).

The this portion of this bot will be up to you. My example will be simple, bordering on stupid. It will only trade two tokens - WETH and MLN - and will be 100% allocated to one or the other at all times. It will make decisions about when and what to trade based on a pseudo-random condition. It will likely do better than my current Melon Fund.

I will, however, delve into the that portion by touching on the following topics in depth:

  • How to check your Melon fund’s current balances
  • How to check asset prices on Uniswap
  • How to trade on Uniswap

Housekeeping

Helpful Links

First we’ll start a project and install the necessary dependencies. For the three methods above, we’ll need a few web3 packages and MelonJS. Use yarn or npm to install @melonproject/melonjs, web3-core, web3-eth, web3-providers and web3-utils. Additionally, I'm using bignumber.js to handle all numbers and token math, and dotenv-extended to access environment variables. I'm also writing this in TypeScript, so I added that as a dev dependency. When you're done, your package.json should look something like this:

Everything you need, nothing you don’t.

We’ll also need the latest protocol deployment configurations to track various contract addresses. These configuration files are available in JSON form in various Melon repositories. I took them from here since the Melon Terminal is very actively maintained and is highly likely to be the most current version. I added a couple of files to the tree to hold our various classes, methods, and utilities, so my directory now looks like this:

A simple directory, so far.

Utility Methods

I’ve taken the liberty of appropriating some utility methods that show up throughout the Melon and Avantgarde codebases. Two work with token math and the other queries ethgasstation.io to give us an appropriate gas price. I also stuck the function to create an environment (discussed below) in that folder.

Wallet, Account, and Environment Setup

Our final chore before we get to the fun part is to discuss private key management. In most cases, a user signs transactions using a browser extension or hardware wallet. In the case of this bot, we will automate the process of signing a transaction, so we need access to the wallet directly. This can be achieved through using the private key, or an encrypted JSON wallet file and password.

For simplicity’s sake, I’ll be storing mine as an environment variable and using dotenv-extended to access it. I have already added .env files to my .gitignore so there's no chance of accidentally pushing this to a hosted repo with keys intact for the world to see. There are a bunch of different ways of managing this interaction, and it's worth researching how you want to handle it. If you take one thing away from this paragraph, it should be:

Do not hardcode your private keys and do not store your private keys on Github.

I know that’s two things. But they’re both important. Cover your ass here.

Per the documentation, the Melon protocol contracts all have a JavaScript class representation exported from MelonJS. These classes each require that an Environment variable is passed to their constructor method in order to provide the class with context about the network within which it is operating and the specific Ethereum account that's accessing it. We'll write a couple of methods to correctly configure that Environment variable, along with your Ethereum wallet information, in order to successfully use the Melon contracts exported by MelonJS. I’ve stuck those methods in the util directory, and they look like this:

Tapping into the Ethereum blockchain.

The first step is to create a function that returns a provider, which is a web3 object that represents a connection to an Ethereum node of your choosing. We call that function and pass the returned provider into the constructor for a new Eth class which we'll call client. The Eth class is exported from web3. We then create an account linked to our private key (which remember, I've stored as an environment variable), and add that to our client. Note that as I mentioned above, this key must be to the account with which you've created the Melon fund for which this bot will trade.

With that, you’ve now proven that you are the owner of the account in question. Create a new environment using the newly instantiated client and the deployment JSON I described above.

The Bot Class

Next, we’ll create a class that will contain all the methods our bot will need to run, as well as hold the variables necessary to execute them in state. More explicitly, using that state in combination with those methods, our bot should be able to:

  • Query a fund’s token balances
  • Query a token’s price denominated in WETH or the price of WETH denominated in that token on Uniswap
  • Trade on Uniswap to adjust the fund’s balances of two distinct tokens tokens (in this case WETH and MLN)

It’s worth noting that I’m taking the simplest route possible here. Though you can trade any one token for another on the current Uniswap release, in order to generate a token to token price, you need to go through WETH. In other words, you need to make two calls (Token 1 to WETH, WETH to Token 2) and subsequently interpolate how many of Token 1 get you a Token 2. I’m just going to trade WETH for MLN and back again. However, if you’d like to see this complexity handled in a fairly seamless manner, check out the Melon Terminal code here.

Throughout the code blocks below, I’ll be referencing class properties. That being the case, I’ll start out my discussion with a section on the parameters that we need to pass to the class’ constructor. The repo contains a fully-typed example class.

Parameters

All the relevant contracts addresses can be referenced using the fund’s hub (whose address is available at your-fund-name.melon.fund). We’ll use a factory pattern and the hub contract to find those addresses and store them in the state’s class when you call it. We’ll also instantiate the various JavaScript contract representations and store them in state. The result is that we need to pass only three variables to instantiate the class: the fund’s hubAddress and the the symbols of the two tokens you want the bot to trade.

Note also, we’ll need to import and call the createEnvironment method when the class is created.

The bot begins.

Fund Methods

We’ll start by using the Accounting contract exported by MelonJS to see what your fund is currently holding.

Querying your Melon fund.

FundHolding is a type that's also exported from MelonJS. It is an object with the size of the holding and the token's address.

Uniswap Methods

We’ll use various contract classes related to Uniswap to query the current prices of the fund holdings that we just returned and then eventually to trade into and out of positions.

To check a price, the order of operations is as follows:

  • figure out which token you’re trading against WETH (the base currency is that which is leaving your inventory, the quote currency is what you’re buying)
  • find the address of the Uniswap exchange contract for that token using the UniswapFactory we instantiated above
  • create a contract object for that exchange using UniswapExchange
  • call a function on that contract to return the rate (in the base currency) to trade an amount (of that base currency).

In our case, we’re going to interpolate the size and rate for the quote currency side of the equation as well, since trading on Uniswap requires a parameters object with some of that information. The method ends up looking something like this:

Asking Uniswap for current prices and interpolating the results.

MelonJS imports in that code block include UniswapFactory and UniswapExchange.

Creating a trade transaction is executed in a similar fashion. We’ll take the uniswapTradingAdapter that we instantiated with the class and call the takeOrder method on it. We’ll pass that method the manager’s address and an object of order arguments that we’ll construct from the result of the price query.

Using the info you got from your fund and from Uniswap to execute a trade.

Worth noting here — I built a slippage variable into this method because if you pass an amount greater than is currently available on Uniswap, the transaction will be rejected. If you pass an amount that’s less than what’s currently available, Uniswap will execute at the best available price. I ballparked 3%; in reality my test trades have been so small that I’ve gotten almost the entire size quoted on every trade I’ve done. Slippage in the liquidity pools is similar to slippage in traditional exchanges — the bigger the size you’re trying to trade, the more there’s likely to be. This will be something you’ll have to optimize on your own.

Our trade function here returns a Promise of a Transaction. I’ve left it up to my execution script to handle that promise’s resolution.

Pulling it All Together

So far, we have a class that contains methods to check your funds token balances, check the prices of those tokens on Uniswap, and trade those tokens on Uniswap. What’s missing is the secret sauce that you’ll provide to inject some logic into the bot. How should it trade and when? It’s easy to envision a method that checks balances, then checks prices, then runs those prices through some sort of indicator algorithm, then trades if the indicator comes back positive. For me, that magic function looks like this:

Do not use this strategy.

I don’t have any of that secret sauce for you. This is where you can get creative — use your best TA or on-chain metrics or other voodoo. As I mentioned above, my strategy will be to trade at random. That being the case, I’ve written a script that calls itself recursively every 15 minutes, generates a random number between 0 and 1 and trades if that number is greater than .50. If it does trade, the script handles the transaction execution. There are a multitude of edge and corner cases that will make this implementation fail, many related to ethereum transaction logic.

The script that runs our bot.

It’s worth digging into web3’s promievents to think about how you’ll handle any errors that occur, and that seems like it could be a good topic for me to cover in a future blog post. Other topics on my list include:

  • managing more complex state and persisting it in a database
  • implementing an actual strategy using that complex state and various market metrics
  • checking all available trading venues and executing at the best possible rate
  • doing the devops work to run this bot indefinitely

As it stands, my bot is operational and burning through gas, trading on my behalf. When I run it in my terminal, it looks like so:

yarn devyarn run v1.21.1$ cross-env NODE_ENV=development ts-node --require dotenv-extended/config --transpile-only srcINSTANTIATING ENVIRONMENT ==>FIRING UP THE BOT ==>CONSULTING THE ORACLE ==>NO TRADING BY ORDER OF THE ORACLEGoing to sleep.INSTANTIATING ENVIRONMENT ==>FIRING UP THE BOT ==>CONSULTING THE ORACLE ==>THE ORACLE SAYS TO TRADEBuying 26270323039658927422.53 MLN by selling 430703553862299497 WETHTransaction successful.blockHash: 0xe0a94b99ec8a3ca8d5ba2d7e71a2f4ca0301cd96764d3d5f0afe9db76f71204ftransactionHash: 0xaa0edecb4c741c58b44daf5742305abb5c055949028b913d18f84cdd260bec33gasUsed: 684251Going to sleep.

Helpful Links

Contact Info

Enzyme Finance (formerly Melon)

A Blog Detailing the Endeavours of the Enzyme ecosystem…

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store