iso-filecoin
Version:
Isomorphic filecoin abstractions for RPC, signatures, address, token and wallet
278 lines (242 loc) • 5.13 kB
JavaScript
import BigNumber from 'bignumber.js'
import { base16 } from 'iso-base/rfc4648'
import { concat } from 'iso-base/utils'
export const ATTO_DECIMALS = 18
export const FEMTO_DECIMALS = 15
export const PICO_DECIMALS = 12
export const NANO_DECIMALS = 9
export const MICRO_DECIMALS = 6
export const MILLI_DECIMALS = 3
export const WHOLE_DECIMALS = 0
const FEMTO_MUL = 10n ** BigInt(MILLI_DECIMALS)
const PICO_MUL = 10n ** BigInt(MICRO_DECIMALS)
const NANO_MUL = 10n ** BigInt(NANO_DECIMALS)
const MICRO_MUL = 10n ** BigInt(PICO_DECIMALS)
const MILLI_MUL = 10n ** BigInt(FEMTO_DECIMALS)
const WHOLE_MUL = 10n ** BigInt(ATTO_DECIMALS)
const symbol = Symbol.for('filecoin-token')
BigNumber.config({
EXPONENTIAL_AT: 1e9,
DECIMAL_PLACES: 40,
ALPHABET: '0123456789abcdef',
ROUNDING_MODE: BigNumber.ROUND_HALF_DOWN,
})
/**
* @noImplicitAny: false
* @typedef {number | string | BigNumber.Instance | bigint | Token} Value
*/
/**
* Check if object is a {@link Token} instance
*
* @param {any} val
* @returns {val is Token}
*/
export function isToken(val) {
return Boolean(val?.[symbol])
}
/**
* @param {unknown} val
* @returns {val is bigint}
*/
function isBigInt(val) {
return typeof val === 'bigint'
}
/**
* @param {Value} val
*/
function bn(val) {
if (isBigInt(val)) {
return new BigNumber(val.toString())
}
if (isToken(val)) {
return val.val
}
return new BigNumber(val)
}
/**
* Class to work with different Filecoin denominations.
*
* @see https://docs.filecoin.io/basics/assets/the-fil-token/#denomonations
*/
export class Token {
/** @type {boolean} */
[symbol] = true
/**
* @param {Value} val
*/
constructor(val) {
/** @type {BigNumber} */
this.val = bn(val)
}
/**
* @param {Value} val
*/
static fromAttoFIL(val) {
return new Token(val)
}
/**
* @param {Value} val
*/
static fromFemtoFIL(val) {
return new Token(val).mul(FEMTO_MUL)
}
/**
* @param {Value} val
*/
static fromPicoFIL(val) {
return new Token(val).mul(PICO_MUL)
}
/**
* @param {Value} val
*/
static fromNanoFIL(val) {
return new Token(val).mul(NANO_MUL)
}
/**
* @param {Value} val
*/
static fromMicroFIL(val) {
return new Token(val).mul(MICRO_MUL)
}
/**
* @param {Value} val
*/
static fromMilliFIL(val) {
return new Token(val).mul(MILLI_MUL)
}
/**
* @param {Value} val
*/
static fromFIL(val) {
return new Token(val).mul(WHOLE_MUL)
}
/**
* @param {Value} val
*/
mul(val) {
return new Token(this.val.times(bn(val)))
}
/**
* @param {Value} val
*/
div(val) {
return new Token(this.val.div(bn(val)))
}
abs() {
return new Token(this.val.abs())
}
/**
* @param {Value} val
*/
add(val) {
return new Token(this.val.plus(bn(val)))
}
/**
* @param {Value} val
*/
sub(val) {
return new Token(this.val.minus(bn(val)))
}
/**
* Serialize the number to a string using the given base.
*
* @param {number | undefined} [base]
*/
toString(base = 10) {
return this.val.toString(base)
}
/**
* Format the number using the given options.
*
* @param {import('./types.js').FormatOptions} [options]
* @see https://mikemcl.github.io/bignumber.js/#toFor
*/
toFormat(options = {}) {
options = {
prefix: '',
decimalSeparator: '.',
groupSeparator: ',',
groupSize: 3,
secondaryGroupSize: 0,
fractionGroupSeparator: ' ',
fractionGroupSize: 0,
suffix: '',
...options,
}
const {
decimalPlaces = 18,
roundingMode = BigNumber.ROUND_HALF_DOWN,
...rest
} = options
return this.val.toFormat(decimalPlaces, roundingMode, rest)
}
toAttoFIL() {
this.toFormat({
decimalPlaces: 2,
roundingMode: 1,
})
return this
}
toFemtoFIL() {
return this.div(FEMTO_MUL)
}
toPicoFIL() {
return this.div(PICO_MUL)
}
toNanoFIL() {
return this.div(NANO_MUL)
}
toMicroFIL() {
return this.div(MICRO_MUL)
}
toMilliFIL() {
return this.div(MILLI_MUL)
}
toFIL() {
return this.div(WHOLE_MUL)
}
toBigInt() {
return BigInt(this.val.toString())
}
toBytes() {
if (this.val.isZero()) {
return new Uint8Array(0)
}
const sign = this.val.isNegative() ? '01' : '00'
return concat([
base16.decode(sign),
bigToUint8Array(BigInt(this.val.abs().toString())),
])
}
}
const big0 = BigInt(0)
const big1 = BigInt(1)
const big8 = BigInt(8)
/**
*
* @see https://jackieli.dev/posts/bigint-to-uint8array/
* @param {bigint} big
*/
function bigToUint8Array(big) {
// @ts-ignore
if (big < big0) {
const bits = (BigInt(big.toString(2).length) / big8 + big1) * big8
const prefix1 = big1 << bits
// @ts-ignore
big += prefix1
}
let hex = big.toString(16)
if (hex.length % 2) {
hex = `0${hex}`
}
const len = hex.length / 2
const u8 = new Uint8Array(len)
let i = 0
let j = 0
while (i < len) {
u8[i] = Number.parseInt(hex.slice(j, j + 2), 16)
i += 1
j += 2
}
return u8
}