Buiding Curve Finance Interactions with typescript and typechain

Burgossrodrigo
CoinsBench
Published in
2 min readMay 3, 2024

--

Using TypeChain for Ethereum Smart Contracts

TypeChain generates TypeScript bindings for Ethereum smart contracts, providing static typing, autocompletion, and up-to-date contract ABIs. This ensures safer, faster development cycles by catching errors at compile time.

You can find more info about typechain in their github page. The command that we used to build the types was:

npx typechain --target==ethers-v6

Don’t forget to have the abi’s inside the root folder of the project.

Key Components of CurveService

Here are the crucial methods in CurveService along with code examples:

  1. Connecting to a Curve Pool Contract
public curvePoolInstance = async (poolAddress: string, chainId: number): Promise<CurvePool> => {
return CurvePool__factory.connect(
poolAddress,
await this.ethereumService.chooseRpc(chainId)
)
}

2. Fetching Pool Data

public async getCurvePoolData(poolAddress: string, chainId: number, numCoins: number): Promise<CurvePoolData> {
const pool = await this.curvePoolInstance(poolAddress, chainId);
const coins: string[] = [];
const balances: number[] = [];

for (let i = 0; i < numCoins; i++) {
const coin = await pool.coins(BigInt(i));
const [amount, decimals] = await Promise.all([
pool.balances(BigInt(i)),
this.erc20Service.getDecimals(coin, chainId)
]);
const formattedBalance = await this.erc20Service.formatUnitsFrom(Number(amount), decimals);

coins.push(coin);
balances.push(formattedBalance);
}
return { coins, balances };
}

3. Finding Pool Address

public async getCurvePoolAddress(registryAddress: string, token0: string, token1: string, chainId: number): Promise<string> {
const registry = await this.curvePoolRegistryInstance(registryAddress, chainId);
return registry['find_pool_for_coins(address,address)'](token0, token1);
}

4. Fetching pool price

    public async getPriceBetweenTokens(poolAddress: string, chainId: number, token0: number, token1: number, amount: number): Promise<bigint> {
try {
const pool = await this.curvePoolInstance(poolAddress, chainId)
return await pool.get_dy(token0, token1, amount)
} catch (error) {
throw new Error(`getPriceBetweenTokens: ${error.message}`)
}
}

Clarifing that the token0 and token’ aren’t the addresses of these tokens, but the position (index) they represent inside the pool aka [0, 1, 2]

Testing with NestJS

Testing ensures our API behaves as expected. Here’s how we test the functionality of fetching a pool address:

describe('CurveController', () => {
let controller: CurveController;
let service: CurveService;

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

controller = module.get<CurveController>(CurveController);
service = module.get<CurveService>(CurveService);
});

it('should return a hardcoded address', async () => {
expect(await service.getCurvePoolAddress(
'0x90E00ACe148ca3b23Ac1bC8C240C2a7Dd9c2d7f5',
'0xdAC17F958D2ee523a2206206994597C13D831ec7',
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
1
)).toBe('0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7');
})
});

This article is a overviewn but don’t get shy in asking me for more profound insights :)

--

--