​How to programmatically convert ICP to cycles in NodeJS?

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):

​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

For more adventures, follow me on Twitter
Made with Papyrs