ICP to cycles in JavaScript

How to programmatically convert ICP to cycles in NodeJS?

Jul 15, 2022

#javascript #nodejs #web3 #tutorial

https://unsplash.com/photos/4ApmfdVo32Q

Photo by Michal Matlon on Unsplash

Until there is a library (e.g. cmc-js 👀) that facilitates the conversion of ICP to cycles in JavaScript, the following tutorial may help you implement such a transformation.


Introduction

Long story short, to convert ICP to cycles you need two things:

  1. the exchange rate to transform the values which is returned by the cycles-minting canister (CMC) through the function get_icp_xdr_conversion_rate
  2. a function that, well, does the conversion

Dependencies

To query the CMC in JavaScript easily you need agent-js (and its peer dependencies). In addition, because following solution runs in a NodeJS context and not in a browser, node-fetch is required as well to provide previous library a way to perform XMLHttpRequest.

npm i node-fetch @dfinity/agent @dfinity/candid @dfinity/principal

Candid

The description of the interface of the cycles-minting canister has to be used to initialize the communication channel.

The CMC candid file can be downloaded on the Internet Computer repo. Its binding files ( .idl.js, .d.ts etc. ) can be generated with the didc command line tool or Canlista (a GUI version of the tool) but, if you want to spare yourself such operations, you can download all these files directly from the backend repo of my project Papyrs.

In following code snippets I use these particular bindings.


XDR conversion rate

On mainnet, the canister ID of the CMC is rkp4c-7iaaa-aaaaa-aaaca-cai. This ID should be used to instantiate the actor in order to query get_icp_xdr_conversion_rate which returns xdr_permyriad_per_icp, the actual conversion rate in XDR we are looking for.

1 XDR being equal to 1 Trillion cycles, I convert the result to trillion ratio before returning it.

import pkgAgent from "@dfinity/agent"; import fetch from "node-fetch"; import { idlFactory } from "./cmc/cmc.utils.did.mjs"; const { HttpAgent, Actor } = pkgAgent; const icpXdrConversionRate = async () => { const agent = new HttpAgent({ fetch, host: "https://ic0.app" }); const actor = Actor.createActor(idlFactory, { agent, canisterId: "rkp4c-7iaaa-aaaaa-aaaca-cai", }); const { data: { xdr_permyriad_per_icp }, } = await actor.get_icp_xdr_conversion_rate(); const CYCLES_PER_XDR = BigInt(1_000_000_000_000); // Return conversion rate in trillion ratio return (xdr_permyriad_per_icp * CYCLES_PER_XDR) / BigInt(10_000); };

Conversion ICP to cycles

We aim to convert readable numbers. That is why, we first need a small utility that maps number to BigInt (because the conversion function will process such types).

const E8S_PER_ICP = BigInt(100000000); const toBigint = (amount) => { const [integral, fractional] = `${amount}`.split("."); if ((fractional ?? "0").length > 8) { throw new Error("More than 8 decimals not supported."); } return ( BigInt(integral ?? 0) * E8S_PER_ICP + BigInt((fractional ?? "0").padEnd(8, "0")) ); };

A few samples of above function result (number -> bigint):

  • 1 -> 100000000n
  • 1.2 -> 120000000n
  • 34.456 -> 3445600000n

Finally, to effectively convert the ICP to cycles, we can implement a cross-multiplication with the conversion rate.

const icpToCycles = async ({icp, conversionRate}) => { const e8ToCycleRatio = conversionRate / E8S_PER_ICP; return toBigint(icp) * e8ToCycleRatio; };

Demo

Put together in a small demo, e.g. above functions can be chained to log the result of the conversion to the console.

const convertICPToCycles = async (icp) => { const conversionRate = await icpXdrConversionRate(); const cycles = await icpToCycles({icp, conversionRate}); const ONE_TRILLION = BigInt(1000000) * BigInt(1000000); console.log( `${icp} ICP equals ${ Number(cycles) / Number(ONE_TRILLION) } (${cycles}) cycles` ); } await convertICPToCycles(123.56);

If run in a command line, the sample should output such a result:

❯ node index.mjs 123.56 ICP equals 652.0879 (652087900000000) cycles

Conclusion

I hope this small tutorial will be useful. If you have any idea of improvements, let me know!

To infinity and beyond
David