On-Chain Market MakerΒΆ

Warning

This is example code for learning purposes. Do not use in production without thorough review and testing.

A simple automated market maker (AMM) using the constant product formula (x * y = k).

 1#pragma version >0.3.10
 2
 3from ethereum.ercs import IERC20
 4
 5
 6totalEthQty: public(uint256)
 7totalTokenQty: public(uint256)
 8# Constant set in `initiate` that's used to calculate
 9# the amount of ether/tokens that are exchanged
10invariant: public(uint256)
11token: IERC20
12owner: public(address)
13finalized: bool
14
15# Sets the on chain market maker with its owner, initial token quantity,
16# and initial ether quantity
17@external
18@payable
19def initiate(token_addr: address, token_quantity: uint256):
20    assert self.invariant == 0
21    self.token = IERC20(token_addr)
22    extcall self.token.transferFrom(msg.sender, self, token_quantity)
23    self.owner = msg.sender
24    self.totalEthQty = msg.value
25    self.totalTokenQty = token_quantity
26    self.invariant = msg.value * token_quantity
27    assert self.invariant > 0
28
29# Sells ether to the contract in exchange for tokens (minus a fee)
30@external
31@payable
32def ethToTokens():
33    assert not self.finalized
34
35    fee: uint256 = msg.value // 500
36    eth_in_purchase: uint256 = msg.value - fee
37    new_total_eth: uint256 = self.totalEthQty + eth_in_purchase
38    new_total_tokens: uint256 = self.invariant // new_total_eth
39    extcall self.token.transfer(msg.sender, self.totalTokenQty - new_total_tokens)
40    self.totalEthQty = new_total_eth
41    self.totalTokenQty = new_total_tokens
42
43# Sells tokens to the contract in exchange for ether
44@external
45def tokensToEth(sell_quantity: uint256):
46    assert not self.finalized
47
48    extcall self.token.transferFrom(msg.sender, self, sell_quantity)
49    new_total_tokens: uint256 = self.totalTokenQty + sell_quantity
50    new_total_eth: uint256 = self.invariant // new_total_tokens
51    eth_to_send: uint256 = self.totalEthQty - new_total_eth
52    send(msg.sender, eth_to_send)
53    self.totalEthQty = new_total_eth
54    self.totalTokenQty = new_total_tokens
55
56# Owner can withdraw their funds and stop the exchange
57@external
58def ownerWithdraw():
59    assert self.owner == msg.sender
60
61    self.finalized = True
62
63    extcall self.token.transfer(self.owner, self.totalTokenQty)
64
65    if self.balance > 0:
66        send(self.owner, self.balance)

How it works:

  1. Owner calls initiate() with initial ETH and tokens, setting the invariant (k = ETH * tokens)

  2. Users swap ETH for tokens via ethToTokens()

  3. Users swap tokens for ETH via tokensToEth()

  4. The invariant is maintained: more ETH in = fewer tokens out

The 0.2% fee (msg.value // 500) on ETH-to-token swaps goes to the liquidity provider.

Note

Production AMMs need price oracles, slippage protection, and liquidity management. This example demonstrates the core swap mechanism only.