Buiding Curve Finance Interactions with typescript and typechain
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:
- 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 :)