In this post, we are going to see how we can set up monitoring for AMM’s. This might be required for use-cases like

  • Developing an API service which allows clients to query price of some asset in AMM
  • Develop an arbitrage bot

I am referring to AMM’s on the Solana blockchain. But the idea should be generally applicable to the AMMs of any blockchain.

We are going to look into simple CFMM based AMM for example Orca. There are few AMMs whose liquidity is distributed into a pool and orderbook like Serum and Raydium.

CFMM based AMM

Assuming you are already aware of “Constant Function Market Maker (CFMM)”. There are great resources to understand it. Linking a few below:

To understand with a simple example, let’s assume there is 100 ETH and 100 USDC in a pool. So the current price is 1 ETH = 1 USDC. This doesn’t mean you can take all 100 ETH for 100 USDC, liquidity is distributed according to constant function X*Y = Constant

So if you want to take 1 ETH, the amount of USDC required will be 100*100 = (100-1) * (100+n)
Which gives “n = 1.0101”

We want to monitor the price of the SOL-USDC pool, so finding reserves of SOL and USDC in a pool is the first step.

Code Snippets

In Solana, the programming model is different from Ethereum. For simplicity let’s assume there are 2 token accounts, one account holds USDC and other holds SOL. Thus we need to monitor 2 addresses.

ORCA SOL-USDC AMM address - https://solscan.io/account/EGZ7tiLeH62TPV1gL8WwbXGzEPa9zmcpVnnkPKKnrE2U

SOL token account - ANP74VNsHwSrq9uUSjiSNyNWvf6ZPrKTmE4gHoNd13Lg
USDC token account - 75HgnSvXbWKZBpZHveX68ZzAhDqMzNDS29X6BGLtxMo1

We are using solana-go library to interact with Solana blockchain and “genesysgo” validator.

Creating a web-socket client

client, err := ws.Connect(context.Background(), "wss://ssc-dao.genesysgo.net")
if err != nil {
    panic(err)
}

Listen to data change of account

func listenToAddress(publicKey solana.PublicKey, client *ws.Client, p *PoolUpdateEvent, id int) {
	sub, err := client.AccountSubscribe(
		publicKey,
		rpc.CommitmentConfirmed,
	)
	if err != nil {
		panic(err)
	}
	defer sub.Unsubscribe()
	for {
		got, err := sub.Recv()
		if err != nil {
			panic(err)
		}
		borshDec := bin.NewBorshDecoder(got.Value.Data.GetBinary())
		var meta token.Account
		err = borshDec.Decode(&meta)
		if err != nil {
			panic(err)
		}
		p.mu.Lock()
		if meta.Amount != p.amounts[id] {
			p.amounts[id] = meta.Amount
			p.slots[id] = got.Context.Slot
			fmt.Println(fmt.Sprintf("%s   %d  SLOT:%d", meta.Mint, p.amounts[id], p.slots[id]))
			if p.slots[0] == p.slots[1] {
				fmt.Println(fmt.Sprintf("Price %f", (float64(p.amounts[1])/USDC)/(float64(p.amounts[0])/SOL)))
			}
		}
		p.mu.Unlock()
	}
}

Data decoding example can be found in readme of solana-go.

You can see complete code in this repo - https://github.com/viveksb007/solana-pool-monitor. Repo has instructions to build and run the code.

Running the code should produce output similar to below image

AMM Monitor terminal

I have also written code to find price from Raydium pool. I adapted logic from their discord. Attaching the image below:

Raydium discord screenshot

NOTE - This was just a POC project for me to understand how to monitor changes in blockchain state. So if you see any bug or in-correct explanation, feel free to comment or dm @viveksb007

Resources