Diving into Tempus’ AMM

How we designed the most capital efficient AMM for yield optimization

Đorđe Mijović
7 min readAug 19, 2021

[DISCLAIMER: This article refers to certain concepts/definitions that are no longer used within Tempus and is therefore outdated. Please refer to docs.tempus.finance for the most up to date information on Tempus.]

Tempus is building a protocol that allows users to optimize their existing exposure to variable yield to better match their risk profile. We do so by splitting out a yield bearing token into two new primitives, Capitals and Yields, and allowing users to trade those primitives against each other.

Check out the previous blogs about our protocol design:

In order to allow the trading of those Capitals and Yields against each other, we have designed a custom AMM that takes into account the custom properties of these new primitives, and the economic relationship between each other through time. In this article, I will talk about the challenges of building an AMM tailored to our very specific use case.

Key properties of TempusAMM

Tempus has a simple but eloquent protocol design. Users can deposit their yield bearing tokens such as staking derivative stETH (Lido Staked ETH) into contracts with select maturities. These yield bearing tokens get separated into tokenized Capitals and Yields. Yields are zero coupon bonds that mature to the amount of yield that one Capital accrues between the Start Date and the Maturity Date of the contract.

However, we know much less about the value of the Yields at the beginning of the pool, but as time goes on we can check the exact value of yield that’s accruing in the pool and by the time we reach the Maturity Date we know the full value. This allows users to speculate on future yield, or to get fixed yield, along with other use-cases of Tempus protocol.

When designing the AMM, we wanted to optimize for two key properties:

  • If we are far from maturity, we want the market to drive the price between Capitals and Yields which means it will be traded more like x * y = k type of AMM
  • When approaching maturity, we want trades to be less sensitive of balances in the pool and closer to actual yield that is being accrued

Solution

We researched multiple options for the AMM, and not all of them worked for us.

For example:

  • dynamic weighted pools built on Balancer carries some benefits due to its easy adjustability, but it also has a lot of issues for this use case: the capital efficiency has a large dependence on initial weights, and it eliminates a lot of arbitrage opportunities
  • Uniswap v2 constant product market maker (x * y = k) doesn’t work well near maturity since the price can be expressed purely as the ratio of reserves in the pool, and in our case trading close to Maturity would not be capital efficient
  • Using Uniswap v3, we could have gotten to the same economic effect as our own implementation but only with an automated liquidity management strategy that carries significantly more complexity.
  • YieldSpace doesn’t work for trading between Capitals and Yields as it is designed for trading two tokens whose price will be equal at maturity. However, it would work nicely for trading Capitals against backing tokens (for example ETH in the case of stETH on Lido)

After some extensive research and tests, we found that stable pools, Curve’s Stableswap type of AMM with some small tweaks would work perfectly for our case. Stable pools are designed for swaps between stablecoins, or coins whose value is equal.

There is one very important parameter that we can adjust over time to change the price curve. It is called amplification — if it has a small value, the AMM will trade like a constant product market maker (x * y = k), but if it has a bigger value, closer to or at 100, the AMM will trade more like a constant sum market maker (x + y = k).

Figure 1: Change in curve shape as we get closer to maturity

Since it is designed to work for tokens that are equal in value, we ended up with a slightly modified formula for Tempus. Let’s see how we got there. Here’s the original formula:

Curve stable pool type of AMM where:

  • D is invariant
  • A is amplification coefficient
  • n is the number of tokens
  • xᵢ is a balance of i-th token in the pool

This formula works for the tokens that are equal in value. With low amplification, the AMM needs to have equal balances in order to have an equal price. However, on maximum amplification, the exchange rate of the tokens will be 1:1 regardless of the balances.

But, how do we make it work for our use case? Let’s have one example, in case of 10% yield, 1 Capital should be equal to 10 Yields (0.1*10 = 1).

Using the vanilla StableSwap formula, this would mean that when we are close to maturity and amplification is high,1 Capital would be equal in value to 1 Yield. In order to fix that, we ended up replacing the balances in formulas with scaled balances. This is what we came up with:

Since we are working on scaled balances, we need to do swaps using these scaled values.

When a user wants to swap 1 Capital for Yields (based on the same example as above where we assume a 10% market implied yield) we must add two additional steps, one before and one after calculation:

  • scale amount of Capitals as 1 * 1 = 1 (as price per share for Capitals is 1)
  • calculate amount of Yields received based on invariant with scaled balances (x_Capitals = balance_Capitals * 1, x_yields = balance_yields * 0.1)
  • we get back 1 as a result, and we need to undo scaling by dividing with pricePerShare so finalYieldsout = 1 / 0.1 = 10

Pricing Capitals and Yields

How do we determine the price per share? Price per share is not only used in swaps, but also in calculating the invariant. It is expressed as a sum of all weighted balances:

The actual rate for LP tokens (the token that liquidity providers receive in return for joining the pool) is calculated by dividing the invariant by the total LP token supply.

The only fact we know about prices is that Yield and Capital price add up to actual yield accrued plus the locked Capital. In order to price both Yields and Capitals we needed to add one more thing to the equation, and that is the estimated yield when constructing Tempus Pool. The first liquidity provider who deploys the pool can use data such as past yield in the underlying protocol for a certain time period leading up to the pool’s start date. This estimate has more weight at the beginning of the life of the pool, and will gradually be taken over by the actual accrued yield by the end of the pool. We calculate the estimated yield at any time as:

Where:

  • yield(curr) is how much yield has accrued so far
  • yield(initEst) is initially estimated yield for the lifetime of the pool
  • ttm is time to maturity expressed as a value between 0 to 1 where it is 0 at maturity and 1 at the beginning of the pool

We can then also derive the formulas for the price of Yields and the price of Capitals:

Implementation

Since we wrote our entire source code using the latest Solidity compiler, it was much easier for us to use Balancer v2 Stable pools with some changes rather than using Curve, which is implemented in Vyper. With all the new features in Balancer v2 such as Vaults and improved gas efficiency, the benefits are clear to all Tempus users interacting with the AMM.

What’s next

Tempus has received a grant from the Lido Ecosystem Grant Organisation (LEGO) to integrate stETH with Tempus. We will be publishing blogs on multiple topics such as protocol design and its benefits, the “how-to” for all our products, and more! The team is working tirelessly to provide you with one of the most capital efficient secondary markets for yields. Audit for Tempus starts in August and we will launch on testnet soon after.

To find our more about Tempus, follow us on:

Discord | Github | Medium | Twitter | Website

Disclaimer

The information provided in this article is provided for informational purposes only and does not constitute, and should not be construed as, investment advice, or a recommendation to buy, sell, or otherwise transact in any investment, including any products or services, or an invitation, offer, or solicitation to engage in any investment activity. You alone are responsible for determining whether any investment, investment strategy, or related transaction is appropriate for you based on your personal investment objectives, financial circumstances, and risk tolerance. In addition, nothing in this article shall, or is intended to, constitute financial, legal, accounting, or tax advice. We recommend that you seek independent advice if you are in any doubt.

--

--

Đorđe Mijović

Co-founder @ Tempus, previously at EF, Wolf3D, InsideMaps, and Microsoft