This post is part of a series on DeFi. Here is the previous post, and this is the first post in the series.

We’ve seen how buying and selling from the Automated Market Maker (AMM) alters the token price. Now let’s see what it looks like when we have multiple people (counterparties) buying from and selling to the AMM.

Counterparty

We’ll create a Counterparty class to represent our traders. Each Counterparty has a name, and balances of ether and tokens.

class Counterparty
  attr_accessor :name, :ether, :tokens

  def initialize(params)
    @name = params.fetch(:name)
    @ether = params.fetch(:ether, 0).to_f
    @tokens = params.fetch(:tokens, 0).to_f
  end
end

Trading

Instead of entering commands like buy 1, and sell 9, we want to be able to say who is buying/selling, so we want commands like Alice buy 1 and Bob sell 9.

This means we need a way to refer to a particular Counterparty by its name. We’ll use a simple manager class for this, called Counterparties.

# Manager class for Counterparty objects
class Counterparties
  # list: Array of Counterparty objects
  def initialize(list)
    @counterparties = list.inject({}) { |acc, c| acc[c.name] = c; acc }
  end

  def find(name)
    @counterparties[name]
  end
end

This just takes a list of Counterparty objects and creates a hash keyed on the names, so we can easily refer to a specific Counterparty.

Adjusting counterparties` balances

We need to adapt the buy and sell methods of our AMM to adjust counterparties` balances of tokens and ether when they trade:

  def buy(counterparty, amount)
    ether = amount.to_f
    tokens_bought, @ether_reserve, @token_reserve = trade(ether, @ether_reserve, @token_reserve)
    counterparty.ether -= ether
    counterparty.tokens += tokens_bought
    return tokens_bought
  end

  def sell(counterparty, amount)
    tokens = amount.to_f
    ether, @token_reserve, @ether_reserve = trade(tokens, @token_reserve, @ether_reserve)
    counterparty.ether += ether
    counterparty.tokens -= tokens
    return ether
  end

Now we need to adjust our bin/amm.rb script to create some counterparties:

alice = Counterparty.new(name: "Alice", ether: 10)
bob = Counterparty.new(name: "Bob", ether: 10)

counterparties = Counterparties.new([alice, bob])

When we get buy and sell commands, we figure out who is trading, and send the Counterparty object in our call to buy or sell:

  when /(.*) buy (.*)/
    name = $1
    ether = $2.to_f
    counterparty = counterparties.find(name)
    amm.buy(counterparty, ether)
  when /(.*) sell (.*)/
    name = $1
    counterparty = counterparties.find(name)
    tokens = $2 == "all" ? counterparty.tokens : $2.to_f
    amm.sell(counterparty, tokens)

I’ve added a convenience amount all, to sell all of a Counterparty’s tokens, so we don’t have to copy/paste long floating-point numbers.

I’ve also added a bit of code to output all counterparties and their token/ether balances, so we can see what’s going on.

You can see the full code here.

Let’s see what happens when multiple counterparties trade on our AMM:

$ bin/amm.rb
Pool eth: 10.0, tokens: 1000.0

> Alice buy 1
Alice gets 90.90909090909088 tokens for 1.0 ether, price 0.0110
Pool eth: 11.0, tokens: 909.0909090909091

> Bob buy 1
Bob gets 75.75757575757575 tokens for 1.0 ether, price 0.0132
Pool eth: 12.0, tokens: 833.3333333333334

> Alice sell all
Alice gets 1.1803278688524586 ether for 90.90909090909088 tokens, price 0.0130
Pool eth: 10.819672131147541, tokens: 924.2424242424242

> Bob sell all
Bob gets 0.8196721311475414 ether for 75.75757575757575 tokens, price 0.0108
Pool eth: 10.0, tokens: 1000.0

> counterparties
Alice   ether: 10.1803, tokens: 0.0000
Bob     ether: 9.8197,  tokens: 0.0000

> q
bye.

Both counterparties buy 1 ETH worth of tokens, and then sell it back again, but because of the actions of the other traders in the market (i.e. Alice), Bob ends up losing some ether, and Alice gains it.

This shows how a very simple algorithm, x * y = k can form the basis for a marketplace where traders can win or lose by trading against one another.

So far, we’ve just looked at a very simple case where we spin up an AMM with a fixed pool of assets (tokens and ether). In practice, this isn’t very interesting because it suffers from slippage, and all traders can really do is redistribute that fixed supply of ether and tokens amongst themselves.

In the next post, we’ll look at how we can enable and incentivise people to add assets to our AMM, so that slippage is minimised and our AMM becomes more appealing to traders (counterparties).

2 thoughts on “Trading the Automated Market Maker

Leave a comment