276 lines
7.2 KiB
TypeScript
276 lines
7.2 KiB
TypeScript
export type BCHUnits = 'sats' | 'bits' | 'mBCH' | 'BCH';
|
|
|
|
/**
|
|
* Satoshis Primitive
|
|
*
|
|
* @example
|
|
*
|
|
* ```ts
|
|
* // Import our primitives
|
|
* import { Satoshis } from '@xocash/primitives';
|
|
*
|
|
* // Create an amount from BCH.
|
|
* const sats = Satoshis.fromBCH(0.54321);
|
|
*
|
|
* // Output as a satoshi number.
|
|
* console.log(satoshis.toSats());
|
|
*
|
|
* // Output as a satoshis bigint.
|
|
* console.log(satoshis.toBigInt());
|
|
* ```
|
|
*/
|
|
export class Satoshis {
|
|
protected readonly satoshis: bigint;
|
|
|
|
/**
|
|
* Construct a new instance of Satoshis.
|
|
*
|
|
* This is the same as using fromSatoshis().
|
|
*
|
|
* @param satoshis {number} The satoshi amount.
|
|
*/
|
|
protected constructor(satoshis: bigint | number) {
|
|
// Set the satoshi amount.
|
|
this.satoshis = BigInt(satoshis);
|
|
}
|
|
|
|
/**
|
|
* Instantiates from a Satoshi Amount.
|
|
*
|
|
* @param satoshis {number} The Satoshi amount.
|
|
*
|
|
* @returns {Satoshis} New instance of Satoshis.
|
|
*/
|
|
public static fromSats(satoshis: bigint | number): Satoshis {
|
|
return new this(satoshis);
|
|
}
|
|
|
|
/**
|
|
* Instantiates from a Satoshi Amount.
|
|
*
|
|
* @param satoshis {number} The Satoshi amount.
|
|
*
|
|
* @deprecated Use fromSats() instead.
|
|
*
|
|
* @returns {Satoshis} New instance of Satoshis.
|
|
*/
|
|
public static fromSatoshis(satoshis: bigint | number): Satoshis {
|
|
return new this(satoshis);
|
|
}
|
|
|
|
/**
|
|
* Instantiates from a Bits Amount (100 satoshis = 1 bit).
|
|
*
|
|
* @param bits {number} The Bits amount.
|
|
*
|
|
* @returns {Satoshis} New instance of Satoshis.
|
|
*/
|
|
public static fromBits(bits: number): Satoshis {
|
|
const satoshis = Satoshis.roundToDigits(bits * 100, 0);
|
|
|
|
return new this(satoshis);
|
|
}
|
|
|
|
/**
|
|
* Instantiates from a mBCH Amount (100,000 satoshis = 1 mBCH).
|
|
*
|
|
* @param mBCH {number} The mBCH amount.
|
|
*
|
|
* @returns {Satoshis} New instance of Satoshis.
|
|
*/
|
|
public static fromMBCH(mBCH: number): Satoshis {
|
|
const satoshis = Satoshis.roundToDigits(mBCH * 100_000, 0);
|
|
|
|
return new this(satoshis);
|
|
}
|
|
|
|
/**
|
|
* Instantiates from a BCH Amount (100,000,000 satoshis = 1 BCH).
|
|
*
|
|
* @param bch {number} The BCH amount.
|
|
*
|
|
* @returns {Satoshis} New instance of Satoshis.
|
|
*/
|
|
public static fromBCH(bch: number): Satoshis {
|
|
const satoshis = Satoshis.roundToDigits(bch * 100_000_000, 0);
|
|
|
|
return new this(satoshis);
|
|
}
|
|
|
|
/**
|
|
* Creates an instance from serialized data.
|
|
*
|
|
* @remarks This method is UNSAFE and MUST only be used where input is already verified or trusted.
|
|
*
|
|
* @param bytes - Serialized Primitive Data
|
|
*
|
|
* @returns A new instance of this primitive.
|
|
*/
|
|
public static fromRaw(sats: bigint) {
|
|
return new this(sats);
|
|
}
|
|
|
|
/**
|
|
* Rounds a number to the given number of digits.
|
|
*
|
|
* @param numberToRound {number} The number to round.
|
|
* @param digits {number} The number of digits to round to.
|
|
*
|
|
* @returns {Number} The rounded number.
|
|
*/
|
|
protected static roundToDigits(
|
|
numberToRound: number,
|
|
digits: number,
|
|
): number {
|
|
// Set the options of the Number Format object.
|
|
const options: Intl.NumberFormatOptions = {
|
|
style: 'decimal',
|
|
minimumFractionDigits: digits,
|
|
maximumFractionDigits: digits,
|
|
useGrouping: false,
|
|
numberingSystem: 'latn',
|
|
};
|
|
|
|
// Create an instance of number format using above options.
|
|
// NOTE: We force the locale to en-GB so that the number is formatted correctly (e.g. with a decimal, not a comma).
|
|
const numberFormat = new Intl.NumberFormat('en-GB', options);
|
|
|
|
// Format the number.
|
|
const formattedAmount = numberFormat.format(numberToRound);
|
|
|
|
// Return the formatted number.
|
|
return Number(formattedAmount);
|
|
}
|
|
|
|
/**
|
|
* Converts the amount to Satoshis (as a BigInt).
|
|
*
|
|
* @returns {bigint} The satoshi amount.
|
|
*/
|
|
public toBigInt(): bigint {
|
|
return this.satoshis;
|
|
}
|
|
|
|
/**
|
|
* Converts the amount to Satoshis.
|
|
*
|
|
* @returns {number} The satoshi amount.
|
|
*/
|
|
public toSats(): number {
|
|
return Number(this.satoshis);
|
|
}
|
|
|
|
/**
|
|
* Converts the amount to Satoshis.
|
|
*
|
|
* @deprecated Use toSats() instead.
|
|
*
|
|
* @returns {number} The satoshi amount.
|
|
*/
|
|
public toSatoshis(): number {
|
|
return Number(this.satoshis);
|
|
}
|
|
|
|
/**
|
|
* Converts the amount to Bits (100 satoshis = 1 bit).
|
|
*
|
|
* @returns {number} The bit amount.
|
|
*/
|
|
public toBits(): number {
|
|
return Satoshis.roundToDigits(Number(this.satoshis) / 100, 2);
|
|
}
|
|
|
|
/**
|
|
* Converts the amount to mBCH (100,000 satoshis = 1 mBCH).
|
|
*
|
|
* @returns {number} The mBCH amount.
|
|
*/
|
|
public toMBCH(): number {
|
|
return Satoshis.roundToDigits(Number(this.satoshis) / 100_000, 5);
|
|
}
|
|
|
|
/**
|
|
* Converts the amount to BCH (100,000,000 satoshis = 1 BCH).
|
|
*
|
|
* @returns {number} The BCH amount.
|
|
*/
|
|
public toBCH(): number {
|
|
return Satoshis.roundToDigits(Number(this.satoshis) / 100_000_000, 8);
|
|
}
|
|
|
|
/**
|
|
* Converts this primitive to a serializable raw object representation.
|
|
*
|
|
* @returns Serialzied primitive data.
|
|
*/
|
|
toRaw() {
|
|
return this.satoshis;
|
|
}
|
|
|
|
/**
|
|
* Formats the amount in the specified unit with formatting options.
|
|
*
|
|
* @param units - The unit to format as ('sats', 'bits', 'mBCH', 'BCH')
|
|
* @param opts - Formatting options
|
|
* @param opts.roundToDigits - Number of decimals to round to
|
|
* @param opts.appendSuffix - Whether to append the unit suffix
|
|
* @returns Formatted amount as string
|
|
*
|
|
* @example
|
|
* // Format as BCH with 2 decimal places and suffix
|
|
* satoshis.format('BCH', { roundToDigits: 2, appendSuffix: true }) // "0.12 BCH"
|
|
*
|
|
* @example
|
|
* // Format as satoshis without suffix
|
|
* satoshis.format('sats', { roundToDigits: 0, appendSuffix: false }) // "12345678"
|
|
*/
|
|
public format(
|
|
units: BCHUnits,
|
|
opts: {
|
|
roundToDigits?: number;
|
|
appendSuffix?: boolean;
|
|
showApproximationSymbol?: boolean;
|
|
} = {},
|
|
): string {
|
|
// Set default values if not provided
|
|
const roundToDigits = opts.roundToDigits ?? 4;
|
|
const appendSuffix = opts.appendSuffix ?? true;
|
|
const showApproximationSymbol = opts.showApproximationSymbol ?? true;
|
|
|
|
// Get the exact amount in the specified unit before rounding
|
|
let exactAmount: number;
|
|
|
|
// Use type assertion to help TypeScript understand this is a valid BCHUnits value
|
|
switch (units) {
|
|
case 'sats':
|
|
exactAmount = this.toSats();
|
|
break;
|
|
case 'bits':
|
|
exactAmount = Number(this.satoshis) / 100;
|
|
break;
|
|
case 'mBCH':
|
|
exactAmount = Number(this.satoshis) / 100_000;
|
|
break;
|
|
case 'BCH':
|
|
exactAmount = Number(this.satoshis) / 100_000_000;
|
|
break;
|
|
default:
|
|
throw new Error(`Unsupported unit: ${units}`);
|
|
}
|
|
|
|
// Format the amount with the specified number of decimal places
|
|
const roundedAmount = Satoshis.roundToDigits(exactAmount, roundToDigits);
|
|
|
|
// Check if rounding occurred
|
|
const wasRounded = exactAmount !== roundedAmount;
|
|
|
|
// Add approximation symbol if needed and enabled
|
|
const prefix = wasRounded && showApproximationSymbol ? '~' : '';
|
|
|
|
// Return the formatted amount with prefix and/or suffix
|
|
return appendSuffix
|
|
? `${prefix}${roundedAmount} ${units}`
|
|
: `${prefix}${roundedAmount}`;
|
|
}
|
|
}
|