Building a Balancer Price Retrieval API with NestJS

Burgossrodrigo
CoinsBench
Published in
3 min readMay 7, 2024

--

In this tutorial, we will develop a simple API using NestJS that interacts with the Balancer Protocol to retrieve pool IDs and token prices. The Balancer Protocol enables automated market-making on the Ethereum blockchain, and our application will leverage its subgraph API and SDK.

Prerequisites

  • Node.js installed (version 12.x or later)
  • NestJS CLI installed (npm i -g @nestjs/cli)
  • Basic understanding of TypeScript and asynchronous programming

Step 1: Setup Your NestJS Project

First, create a new NestJS project if you haven’t already:

nest new balancer-api
cd balancer-api

Step 2: Install Dependencies

You’ll need the graphql-request library to make GraphQL requests and the @balancer-labs/sdk for interacting with the Balancer SDK.

npm install graphql-request @balancer-labs/sdk

Step 3: Setting Up Enums and Interfaces

Define necessary enums and interfaces to help with type safety and readability.

export enum BalancerSubgraphUrl {
MAINNET = 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-v2',
POLYGON = 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-polygon-v2'
}
export interface BalancerPoolsResponse {
id: string;
poolType: string;
poolTypeVersion: string;
}

Step 4: Ethereum Service

This service will provide an RPC URL for the Balancer SDK configuration. Here’s a simple setup:

import { Injectable } from '@nestjs/common';

@Injectable()
export class EthereumService {
async getWorkingProviderUrl(chainId: number): Promise<string> {
// Mock function: replace with actual logic to retrieve RPC URL
return 'https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID';
}
}

Step 5: Balancer Service

This service interacts with the Balancer subgraph and SDK.

import { Injectable } from '@nestjs/common';
import { BalancerSubgraphUrl } from './balancer.enum';
import { BalancerPoolsResponse } from './balancer.interface';
import { GraphQLClient } from 'graphql-request';
import { BalancerSDK, BalancerSdkConfig } from '@balancer-labs/sdk';
import { EthereumService } from '../ethereum/ethereum.service';

@Injectable()
export class BalancerService {
constructor(private readonly ethereumService: EthereumService) {}

private subgraphUrl = (chainId: number) => BalancerSubgraphUrl[chainId];

private graphQlClient = (chainId: number) => new GraphQLClient(this.subgraphUrl(chainId));

private balanceSDKInstance = async (chainId: number) => {
const config: BalancerSdkConfig = {
network: chainId,
rpcUrl: await this.ethereumService.getWorkingProviderUrl(chainId),
};
return new BalancerSDK(config);
};

async getPoolId(chainId: number, token0: string, token1: string): Promise<BalancerPoolsResponse> {
const query = `{ pools(first: 100, where: { tokensList_contains: ["${token0}", "${token1}"] }) { id poolType poolTypeVersion } }`;
const client = this.graphQlClient(chainId);
return client.request(query);
}

async getPoolPrice(chainId: number, token0: string, token1: string): Promise<any> {
const balancer = await this.balanceSDKInstance(chainId);
const poolId = await this.getPoolId(chainId, token0, token1);
const pool = await balancer.pools.find(poolId.pools[0].id);
return pool.calcSpotPrice(token0, token1);
}
}

Step 6: Create Controller

The controller will expose endpoints to interact with the Balancer service.

import { Body, Controller, Post, UsePipes, ValidationPipe } from '@nestjs/common';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { BalancerService } from './balancer.service';
import { BalancerPoolIdRequestDto } from './dto/BalancerPoolIdRequest.dto';
import { BalancerPoolPriceRequestDto } from './dto/BalancerPoolPriceRequest.dto';

@ApiTags('balancer')
@Controller('balancer')
export class BalancerController {
constructor(public readonly balancerService: BalancerService) {}

@Post('get-pool-id')
@ApiOperation({ summary: 'Get pool ID', description: 'Retrieve the pool ID for a given pair of tokens.' })
@ApiResponse({ status: 200, description: 'The pool ID has been successfully retrieved.' })
@ApiResponse({ status: 400, description: 'Invalid input.' })
@UsePipes(new ValidationPipe({ transform: true }))
async getPoolId(@Body() body: BalancerPoolIdRequestDto) {
return this.balancerService.getPoolId(Number(body.chainId), body.token0, body.token1);
}

@Post('get-pool-price')
@ApiOperation({ summary: 'Get pool price', description: 'Retrieve the price of a given pair of tokens.' })
@ApiResponse({ status: 200, description: 'The pool price has been successfully retrieved.' })
@ApiResponse({ status: 400, description: 'Invalid input.' })
@UsePipes(new ValidationPipe({ transform: true }))
async getPoolPrice(@Body() body: BalancerPoolPriceRequestDto) {
return this.balancerService.getPoolPrice(Number(body.chainId), body.token0, body.token1);
}
}

Step 7: Testing

Create unit tests to ensure your services are functioning correctly.

import { Test, TestingModule } from '@nestjs/testing';
import { BalancerService } from './balancer.service';
import { EthereumService } from '../ethereum/ethereum.service';

describe('BalancerService', () => {
let service: BalancerService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [BalancerService, EthereumService],
}).compile();

service = module.get<BalancerService>(BalancerService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});

it('should return a pool ID for given tokens on Polygon', async () => {
const chainId = 137; // Polygon
const token0 = '0x4eD141110F6EeeAbA9A1df36d8c26f684d2475Dc';
const token1 = '0xf2f77FE7b8e66571E0fca7104c4d670BF1C8d722';
const result = await service.getPoolId(chainId, token0, token1);
const poolId = result.pools[0].id;
expect(poolId).toEqual('0x42942cdec85078cf0e28e9cb5acd40cb53997ed6000000000000000000000bea');
});
});

This tutorial provided a foundation for building a NestJS application that interacts with the Balancer Protocol using GraphQL and the Balancer SDK. You can expand upon this by integrating additional functionality like handling more complex queries or supporting additional blockchain networks

--

--