Cookie Consent

By clicking “Accept”, you agree to the storing of cookies on your device to enhance site navigation, analyze site usage, and assist in our marketing efforts. View our Privacy Policy for more information.

DEFI

Building a Trading Bot on Enzyme — Update

A little over a year ago… Erin Koen (software engineer and head of Asset Management at Avantgarde Finance) created a trading bot that executes programmatically on an Enzyme vault.

A little over a year ago… Erin Koen (software engineer and head of Asset Management at Avantgarde Finance) created a trading bot that executes programmatically on an Enzyme vault. It was accompanied by a great how-to-article on using it to set up a bot of your own — which I will indeed be referencing here. So quick shout out to Erin Koen, who has given me permission to reuse material from his original article and who initially introduced me to his bot in the first place.

You can find the bot repo here, and the original article just below.

Building a Trading Bot on Enzyme

Enzyme allows asset managers to develop and deploy proprietary strategies within the burgeoning DeFi ecosystem. The…

medium.com

Unfortunately, the current release of Enzyme (Sulu) made significant improvements that introduced breaking changes to the bot. Fortunately, I’ve made all the necessary updates to the bot, and this article will explain how to get it up and running on Enzyme once again.

What was wrong with the old bot

First off, let’s answer the question — why did the bot quit working?

  1. The old bot employed the Kovan testnet, which is no longer supported by Enzyme. To compensate I’ve made a bot that trades over the Polygon network, which makes it possible to test with almost negligible fees (I opened a vault and tested the bot for less than 0.20 USD)
  2. It was using Uniswap V2, and in order to transact over Polygon it needed to be updated to work with Uniswap V3, which we can now access via Enzyme protocol’s new UniswapV3Adapter
  3. It needed to use the correct properties from the updated subgraph and Enzyme environment

The Nuts and Bolts of the New Bot

Updating our environment setup

For simplicity’s sake, we still store keys (for both Polygon and Ethereum) as environment variables. We’ve added .env files to the .gitignore, so there’s no chance of accidentally pushing these to a hosted repo for the world to see.

Functionally, the bot will be identical on both networks. However, some wallet, node, and subgraph configuration variables differ between the two. We’ll pass a NETWORK argument to our bot on instantiation to dynamically adjust those configuration variables.

Beyond the network-specific variables, we also need to store the Enzyme Vault contract address.

I’ve updated the .env.example file to reflect the necessary variables that need to be added, depending on which network you will be using. It looks like this:

ETHEREUM_PRIVATE_KEY=ETHEREUM_NODE_ENDPOINT=ETHEREUM_SUBGRAPH_ENDPOINT=https://api.thegraph.com/subgraphs/name/enzymefinance/enzyme-corePOLYGON_PRIVATE_KEY=POLYGON_NODE_ENDPOINT=POLYGON_SUBGRAPH_ENDPOINT=https://api.thegraph.com/subgraphs/name/enzymefinance/enzyme-core-polygonENZYME_VAULT_ADDRESS=

Use the Correct Subgraph Variable for Code Generation

We generate the correct GraphQL types based on the schema provided by the appropriate Enzyme subgraph. This occurs when you run the yarn codegen script. By default it is set to use the Polygon subgraph. If you wish to switch to Ethereum, you will need to change the first line of codegen.yaml from schema: ${POLYGON_SUBGRAPH_ENDPOINT} to schema: ${ETHEREUM_SUBGRAPH_ENDPOINT}.

Enzyme Bot Logic

The bot’s strategy has not changed. At a set interval the bot will initiate this process:

  • Call a function that returns a random asset from the Enzyme asset universe, or undefined
  • retrieve your vault’s current portfolio holdings
  • if the random asset is not undefined and is not already held in your portfolio, the bot will find your vault’s largest position (by number of tokens, not by asset value) and swap that position for as much of the random asset as possible via Uniswap

It is a simple strategy whose main goal is to provide an example of how a transaction can be setup and implemented. A proper trading strategy is left up to you.

The Constructor

Upon its creation, our bot will know which network it’s operating on (Ethereum or Polygon). It will also use the getDeployment and getEnvironment functions exported from@enzymefinance/environment to access the addresses of all the deployed Enzyme contracts and assets in the Enzyme asset universe on that network. Additionally, we’ll configure our Ethereum provider and wallet instance properties. And finally, we’ll need to retrieve the provided Enzyme vault’s details in order to reference its comptroller and make calls on its behalf, and to access its holdings. Here is what that looks like:

The Bot’s Class Methods

chooseRandomAsset() remains the same. It generates a random number between 0 and the length of our bot class’s tokens array and returns the asset at that index. Easy-peasy.

swapTokens() takes the uniswapPrice data returned from the newly added uniswapV3Price() function as the first parameter, as well as the outgoingAssetAmount as its second parameter, which is the Vault’s biggestPosition.amount, and executes the trade via the ComptrollerLib contract. It also uses two helper functions exported from @enzymefinance/protocol .

The ComptrollerLib contains the canonical logic for interacting with vaults on the protocol. It can talk to Extensions, which extend that logic. One such extension is the IntegrationManager, which allows the exchange of the fund’s assets for other assets via “adapters” to external DeFi protocols (Uniswap, in this case). In the swapTokens() function, we prepare the arguments necessary for the Uniswap Adapter to execute the trade (via the uniswapV3TakeOrderArgs() helper), then pass those arguments to the callOnIntegrationArgs() helper, which ultimately gets passed to the ComptrollerLib's callOnExtension() function. Note that both of these ...Args() helpers are converting arguments passed in various JavaScript primitives to bytecode that’s readable by the Solidity contracts with which we are interacting.

swapTokens() returns a transaction object, which you can prepare with gas estimations and the like and then eventually execute.

tradeAlgorithmically() utilises the above two methods in its logic that can be summarised here:

  • It chooses an asset from the Enzyme asset universe at random using chooseRandomAsset()
  • It then checks the Enzyme vault’s portfolio to determine which current holding it will sell (the asset with the largest number of tokens), in order to buy the random asset
  • Next it gets the necessary Uniswap price info using the new uniswapV3Price() function, which is required in order to trade the current holding for the random asset
  • Finally, it calls the swapTokens() function to start the transaction

Quick note: uniswapV3Price() has been added in order to retrieve the proper Uniswap price data that is utilised by the callOnIntegrationArgs helper function exported from @enzymefinance/protocol. This function is using the newly available UniswapV3Adapter, and thus needs the correct Uniswap V3 price. You can find the this price function in src/utils/uniswap/price.ts. For a better understanding of how price works check out the Uniswap docs.

Transaction Execution Logic

index.ts will serve as our Bot’s entry point. The run function there holds the logic for when and how often the bot calls the tradeAlgorithmically() function, and what to do with the transaction that function returns. We also have to calculate the gas price required when sending the transaction. And that’s it!

Interacting with External Positions — An Aside

Enzyme now allows you to work with external positions. To understand how to interact with external positions, please read about them in the Enzyme docs. To quote the docs:

External positions interact with external protocols, just like adapters.

Below I’ve provided an example of how you might add collateral to a Compound debt position. I hope this gives an idea of how you can set up others. The relevant parameters are some that we’ve covered, and a few are particular to external positions. Here they are:

  • comptroller, provider, network: all have been detailed and used in the current Enzyme bot example, and so I believe have no need to cover here
  • amounts: an array of deposit token values
  • assets: an array of deposit token addresses
  • externalPositionAddress
  • externalPositionManagerAddress

As you can see when we createExternalPosition, we again use Enzyme’s ComptrollerLib to create a contract and use the callOnExtension helper function, but this time passing the encodeArgs using the external position data.

Conclusion

The main changes of the bot can be summarised thus:

  1. How we access Enzyme contracts and assets
  2. Updates to the subgraph for getting the vault holdings and comptroller
  3. How we get the uniswapV3Price details
  4. Using the new Uniswap V3adapter

We’ve created a bot utilising the bare bones setup of a Uniswap trade. Creating a viable trading strategy is now up to you. Have fun building!