Automated Portfolio Rebalancing with Uniswap

By utilizing Lit programmatic signing with Uniswap, developers can create an automated portfolio rebalancer that is triggered by a defined set of criteria.

Automated Portfolio Rebalancing with Uniswap

Lit enables decentralized programmatic signing through a distributed key management system that is powered by multi-party computation (MPC). Lit is a decentralized key management network that can be used to read and write data between blockchains and off-chain platforms, powering conditional decryption and programmatic signing. What does programmatic signing enable? The ability to sign arbitrary conditions that can be codified such as, “sign this transaction if Ethereum falls below $1500 and the weather is above 65 degrees Fahrenheit”.

Let’s dive into an example where programmatic signing can improve user experience within DeFi.

DeFi is still overly complex for newcomers, which is slowing down the adoption of the space. People shouldn’t have to undertake courses to understand how to develop decentralized trading strategies or be forced to manually rebalance a portfolio of multiple tokens through seemingly endless steps as well as trade them separately on a decentralized exchange, or DEX.
Users need to be able to decide how to rebalance their portfolio with a few clicks. Ideally, these parameters can be customized freely by the users to fit their risk profiles. The DeFi industry is growing rapidly, and it's time for portfolio risk management to keep up.

-Hisham Khan, Portfolio rebalancing through DeFi must be simplified to see adoption

By utilizing Lit’s programmatic signing technology developers can create an automated portfolio rebalancer that is triggered by a defined set of criteria.

How might that be possible?

Let’s take a look at Alice’s story.

For years, Alice has been investing in web3 and building a diversified portfolio to achieve her financial goals. While some of her investments have grown in value over time, others have not performed as well, causing Alice to worry about the ability of her portfolio to withstand market fluctuations. To maintain her desired asset allocation and reduce overall risk, Alice needs to rebalance her portfolio periodically, which involves selling some assets and buying others to bring her portfolio back to her target allocation. For Alice, this means having a portfolio with a mix of Ethereum and MATIC. Keeping track of their portfolio and signing for every transaction is time consuming. Alice already knows what types of assets they’d like to keep and in what proportion. If only there was a way to automate portfolio rebalancing based on a set of criteria and delegate signing…

To help Alice manage their portfolio more efficiently, we are excited to showcase an automated portfolio rebalancer using Lit Actions and Uniswap.

This Lit Action example is designed to rebalance portfolios based on predefined criteria, such as selling ETH when it drops below $1000 or having a percentage split across tokens. Signing is done through a Programmable Key Pair (PKP) and the Lit Action holds the logic for when signing should occur. The goal is to demonstrate the power of smart contracts in combination with utilizing off-chain information and signing via multi-party computation (MPC).

Flow of the application:

  1. Use a third-party API to obtain the current price of a token in USD
  2. Get the current balance on the pairing tokens we’ll be rebalancing.
  3. Create the strategy execution plan.
  4. The Lit Action then uses Uniswap to execute the strategy.

The Lit Action automates the process of rebalancing a portfolio and is triggered by an event listener.

Introduction to PKPs and Lit Actions

Lit Actions are Lit’s native implementation of JavaScript smart contracts that are blockchain agnostic. They can communicate data across blockchains, interoperate between previously disconnected ecosystems, and use off-chain data sources in their computation through arbitrary HTTP requests.

Lit Actions are used in conjunction with Programmable Key Pairs (PKPs) to give smart contracts signing capabilities. Each PKP is generated collectively by the Lit network where each node only holds a share of the underlying private key (a key-share) and the complete private key never exists in its entirety. To control this distributed key pair, you must mint it in the form of an ERC-721 NFT. The NFT stands as the “symbol” or method for controlling the distributed key custodied by the Lit network. This means that only the wallet address or smart contract holding the PKP NFT can authorize how it is used for signing.

PKP signatures are the validation result of Lit Actions code when using a signature to prove that a particular interaction took place. Lit Actions can validate information from external sources, such as from a Weather API, or data that is stateless such as checking if a number is prime.

Ideal cases for PKPs and Lit Actions

  • Generating proofs: ideal for usage with account abstraction (or smart contract) wallets, essentially this is programmable transaction validation through Lit’s network with a signer
  • Looking up permitted actions, addresses, and auth methods associated with a PKP
  • Checking access control conditions with conditional signing

Lit Actions must be triggered by an event, in this example we have an event listener to automate triggering the Lit Action for portfolio rebalancing.

Disclaimer

Lit Actions is still in heavy development. The following code is currently residing on the event-listener server, and what is being shown here is how a Portfolio Rebalancing Lit Action would look like in the very near future.

How it works

Application walkthrough

Link to event listener. Link to the project repo - event listener.

The code can be divided into four parts:

  1. Get the current price of the token in USD using a third-party API, in this case we call the cryptocompare.com API.
/**
 * It retrieves the current price of a specific symbol in USD. The symbol is passed as a parameter to the function. It uses the CryptoCompare API to fetch the price and returns the price data in the form of an object with a status field and a data field.
 *
 * @param { string } symbol eg. "ETH", "USDT", "DAI"
 * @return { { PriceData  } } eg. { status: 200, data: 1234.56 }
 */
const getUSDPrice = async (symbol) => {
  // this will both set the response to the client and return the data internally
  const respond = (data) => {
    Lit.Actions.setResponse({
      response: JSON.stringify(data),
    });

    return data;
  };

  const API =
    "<https://min-api.cryptocompare.com/data/price?fsym=>" +
    symbol +
    "&tsyms=USD";

  let res;
  let data;

  try {
    res = await fetch(API);
    data = await res.json();
  } catch (e) {
    console.log(e);
  }

  if (!res) {
    return respond({ status: 500, data: null });
  }

  return respond({ status: 200, data: data.USD });
};

2. Get the current balance on the pairing tokens. Pass in the pairing tokens into the jsParams. We’re going to do Wrapped Matic and USDC.

// js params input
"tokens": [
  {
    "chainId": 137,
    "decimals": 18,
    "address": "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270",
    "symbol": "WMATIC",
    "name": "Wrapped Matic"
  },
  {
    "chainId": 137,
    "decimals": 6,
    "address": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
    "symbol": "USDC",
    "name": "USD//C"
  }
]

// (example) returned portfolio balance
const balances = [
  {
     token: tokens[0], // WMATIC
     balance: 4.13, 
     value: 5.59 // in USD
  },
  {
    token: tokens[1], // USDC
    balance: 5.60,
    value: 5.60, // in USD
   }
];

3. Strategy execution plan. Pass in the balancing strategy to the jsParams. The strategy in the example is a portfolio that is 52% USDC and 48% WMATIC. With the conditions for the swap being:

  • gas price < 75 Gwei
  • percentage trigger is 1%
  • additional parameter for price spiking
  • if the price has spiked 15% or more, adjust the max gas price to cover the transaction
// jsParams
"strategy": [
  {
    "token": "USDC",
    "percentage": 52
  },
  {
    "token": "WMATIC",
    "percentage": 48
  }
],
"conditions": {
  "maxGasPrice": 75, // must be below in amount to swap
  "unit": "gwei",
  "minExceedPercentage": 1, // the pairing must exceed 1% to swap
  "unless": {
    "spikePercentage": 15, // if however, if it spiked 15%
    "adjustGasPrice": 500  // we will adjust the max gas price to 500
                           // to ensure we capture the gain on impulsive move
  }
},

Output:

The output below is the calculation done by the strategy execution plan. The amountToSell is how many WMATIC to sell to achieve the rebalancing strategy, which is 52% USDC and 48% WMATIC stated in the jsParams. This output is then passed to the execute function as an argument to do the actual swapping.

Example output: strategy has been calculated

4. Finally, execute the strategy execution plan and swap with Uniswap. Check out this Lit Action which shows how to swap tokens. For additional references on how to use Lit Actions to make swaps with Uniswap, check out this repository.

Uniswap Swapping Logic

We simply grant the following code (Lit Action) the permission to use our PKP to sign.

(async() => {
    const sigShare = await LitActions.signEcdsa({ 
			toSign: // unsigned approve or swap tx, 
			publicKey, 
			sigName
		});
})();

First, we need to check the allowance of the token that’s entering to the contract.

To do that:

  1. Get the unsigned approve tx.
  2. Get the signed approve transaction by passing in our unsigned approve tx, our PKP public key, and signature name to the above Lit Action(which only this particular Lit Action is able to sign with our PKP).
  3. Finally submit the transaction to the chain.

On the swapping side, the logical flow is the same. We obtain the unsigned swap tx, pass it to the Lit Action above, and send it off.

The first three parts of the code are dedicated to setting up logic and conditional guards, while the final part focuses on signing and sending the transaction. Therefore, we can divide the code into two categories: one for guarding it and the other for signing it.

But how do we auth?

On the triggering side, we do not perform authentication. Instead, we can use the event-listener authentication signature for the request.

But how does it work without authentication?

The Lit Action should verify the condition before running. The user permits the Lit Action to use their PKP, which anyone could run (please see the note section below), and the event listener runs the action as needed.

For example, if the action is "sell this token when ETH is below $1000," then the user should use a fetch() method in the Lit Action to check if ETH is below $1000 before signing.

Note

At the moment, we haven’t built in ways to track when the event listener triggers the Lit Action. For example, you can run the executeJs function yourself, but it won’t sign unless the specified conditions are met.

We are working on a solution to fix this, eg. creating a ZK proof for users to check for authentication. If you are interested in contributing the building the event listener tool, check out the repo and reach out the the team!

Closing

With Lit's programmatic signing technology, developers can easily create an automated portfolio rebalancer that is triggered by a defined set of criteria. The proof of concept portfolio rebalancer demonstrates the potential of programmatic signing to enhance the usability of web3 dApps. The future, where rebalancing a portfolio of multiple tokens takes just a few clicks, is near. We cannot achieve this without help from the ecosystem!

Call for Contributors

As the team rolls out more features to the Event Listener, we are hoping to get contributors to support this as an open source project. The goal of the event listener is to be a tool that people can set automations based on a variety of factors such as periodic time, webhooks, or another contract. If this sounds interesting to you, reach out on Discord or direct message us on Lenster or Twitter.

You can check out the work in progress code here.

If you have any questions or feedback, please feel free to reach out to us on Discord.