A cross-language library to handle currencies and money.
Building systems with money is pretty hard. Especially when the monetary amounts that the application has to deal with uses different decimals and have a different "origin", i.e., chains issuing custom tokens.
To tackle these issues, this library should serve as a specification for a common interface to handle monetary amounts as well as provide reference implementations. Specifically, the objective for this library is to:
- Provide a secure currency interface. This includes safe arithmetic, type checking to ensure conversions between currencies are done correctly, safe conversion between denominations, and correct rounding.
- Provide a universal currency interface. The interface should be able to handle different denominations, an identifiable name and ticker, and an "origin".
- A good portion of this specification was inspired by this excellent post on safe-money.
Install from npmjs:
npm i @interlay/monetary-jsOr
yarn add @interlay/monetary-jsimport Big from 'big.js';
import { BTCAmount, BTCUnit, ETHAmount, ETHUnit } from '@interlay/monetary-js';
const bitcoins = BTCAmount.from.BTC(0.5);
const ethers = ETHAmount.from.GWei(10000);
// conversions to string and Big of different units
console.log(`We have ${bitcoins.str.Satoshi()} Satoshi, and ${ethers.str.ETH()} whole ethers.`);
const weiBig: Big = ethers.to.Wei();
// the same conversions can be accessed through toString() and toBig(), by specifying the units
console.log(`We have ${bitcoins.toString(BTCUnit.Satoshi)} Satoshi, and ${ethers.toString(ETHUnit.ETH)} whole ethers.`);
const weiBig: Big = ethers.toBig(ETHUnit.Wei);
// converting between different currencies
const ETHBTCRate = new ExchangeRate<Ethereum, ETHUnit, Bitcoin, BTCUnit>(
Ethereum,
Bitcoin,
new Big(0.0598)
);
// for ETH/BTC, "base" is ETH, "counter" is BTC
const bitcoinsAsEthers: ETHAmount = ETHBTCRate.toBase(bitcoins);
// type-safe arithmetic
const totalEthers = ethers.add(bitcoinsAsEthers);
// ethers.add(bitcoins); // errorMonetary-js comes with Bitcoin, Ethereum and Polkadot predefined, but it is meant to be extensible for any currency. src/currencies/bitcoin.ts can be used as an example for the minimal work needed to define a currency. Another example is DummyCurrency defined inline for unit tests in test/monetary.test.ts
The first task is to define the units, which are just key-value pairs defining the decimal places for each unit. For instance, Bitcoin consists of 10^8 Satoshi, with Satoshi being the smallest (atomic) unit that only exists in integer amounts. Thus:
const BTCUnit = {
BTC: 8,
Satoshi: 0,
} as const;
export type BTCUnit = typeof BTCUnit;Among the units above, one entry should represent the rawBase unit (Satoshi in the case of Bitcoin, or an arbitrary value in the case of currencies like Tether). Intuitively, rawBase should be smaller than or equal to the base unit, described in the next step. For currencies like Tether that don't have a traditional rawBase value, the units definition may look like:
const TetherUnit = {
Tether: 6,
Raw: 0
} as const;
export type TetherUnit = typeof TetherUnit;The next step is to define our currency, parametrising the type with our units:
export const Bitcoin: Currency<typeof BTCUnit> = {
name: "Bitcoin",
base: BTCUnit.BTC,
rawBase: BTCUnit.Satoshi,
units: BTCUnit,
humanDecimals: 5,
} as const;
export type Bitcoin = typeof Bitcoin;The values should be self-explanatory. The base field defines the "primary" unit for the currency - BTC for Bitcoin, ETH for Ethereum, DOT for Polkadot, etc. This is used for human-friendly formatting. humanDecimals is used for pretty-printing approximate (truncated) stringified values using toHuman().
At this point, the currency is usable:
import { Bitcoin, BTCUnit } from 'src/currencies/bitcoin';
const btcAmount = new MonetaryAmount<Bitcoin, BTCUnit>(Bitcoin, 0.5, Bitcoin.units.BTC);However, this is a bit verbose. We can subclass MonetaryAmount for convenience:
export class BTCAmount extends MonetaryAmount<Bitcoin, BTCUnit> {
static from = generateFromConversions(Bitcoin, BTCUnit);
}Notice that we define a static member from - recall from the examples above that this can be used as syntactic sugar to bypass the constructor. generateFromConversions automatically populates the required object with the necessary functions, based on the currency and unit objects.
And that's all that's necessary to define a currency. Of course, this can be extended as required - for instance, src/currencies/ethereum.ts extends the Currency interface and add support for ETC20 contracts with configurable addresses.
Checkout the code and install the dependencies:
git clone https://github.com/interlay/monetary.git
cd monetary
yarn installBuild:
yarn buildAnd run tests:
yarn testMake sure you have yarn installed. Install docsify globally:
yarn global add docsify-cliClone the repository:
git clone [email protected]:interlay/monetary.gitBuild the documentation:
docsify serve ./docsContributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are greatly appreciated.
- Fork the project
- Create your feature branch (
git checkout -b feat/AmazingFeature) - Sign and commit your changes (
git commit -S -m 'feat: Add some AmazingFeature') - Push to the branch (
git push origin feat/AmazingFeature) - Open a pull request
If you are searching for a place to start or would like to discuss features, reach out to us:
(C) Copyright 2021 Interlay Ltd
monetary is licensed under the terms of the MIT License. See LICENSE.
Website: Interlay.io
Twitter: @interlayHQ
Email: [email protected]