1+ import Big , { BigSource } from 'big.js' ;
2+
13import { MAX_U32 , MAX_U64 } from '../constants.js' ;
24import type * as Proto from '../grpc-api/v2/concordium/protocol-level-tokens.js' ;
35
@@ -24,6 +26,10 @@ export enum ErrorType {
2426 NEGATIVE = 'NEGATIVE' ,
2527 /** Error type indicating the token amount has more decimals than allowed. */
2628 EXCEEDS_MAX_DECIMALS = 'EXCEEDS_MAX_DECIMALS' ,
29+ /** Error type indicating the token value is specified as a fractional value when multiplied by `10 ** decimals` */
30+ FRACTIONAL_VALUE = 'FRACTIONAL_VALUE' ,
31+ /** Error type indicating the token decimals were specified as a fractional number. */
32+ FRACTIONAL_DECIMALS = 'FRACTIONAL_DECIMALS' ,
2733}
2834
2935/**
@@ -47,10 +53,10 @@ export class Err extends Error {
4753 }
4854
4955 /**
50- * Creates a TokenAmount.Err indicating that the token amount is negative.
56+ * Creates a TokenAmount.Err indicating that the token amount/decimals is negative.
5157 */
5258 public static negative ( ) : Err {
53- return new Err ( ErrorType . NEGATIVE , 'Token amounts cannot be negative' ) ;
59+ return new Err ( ErrorType . NEGATIVE , 'Token amounts/decimals cannot be negative' ) ;
5460 }
5561
5662 /**
@@ -59,6 +65,16 @@ export class Err extends Error {
5965 public static exceedsMaxDecimals ( ) : Err {
6066 return new Err ( ErrorType . EXCEEDS_MAX_DECIMALS , `Token amounts cannot have more than than ${ MAX_U32 } ` ) ;
6167 }
68+
69+ /** Creates a TokenAmount.Err indicating the token decimals were specified as a fractional number. */
70+ public static fractionalDecimals ( ) : Err {
71+ return new Err ( ErrorType . FRACTIONAL_DECIMALS , `Token decimals must be specified as whole numbers` ) ;
72+ }
73+
74+ /** Creates a TokenAmount.Err indicating the token decimals were specified as a fractional number. */
75+ public static fractionalValue ( ) : Err {
76+ return new Err ( ErrorType . FRACTIONAL_VALUE , `Can not create TokenAmount from a non-whole number` ) ;
77+ }
6278}
6379
6480/**
@@ -72,7 +88,7 @@ class TokenAmount {
7288 * Constructs a new TokenAmount instance.
7389 * Validates that the value is within the allowed range and is non-negative.
7490 *
75- * @throws {Err } If the value/digits exceeds the maximum allowed or is negative.
91+ * @throws {Err } If the value/decimals exceeds the maximum allowed or is negative.
7692 */
7793 constructor (
7894 /** The unsigned integer representation of the token amount. */
@@ -89,6 +105,12 @@ class TokenAmount {
89105 if ( decimals > MAX_U32 ) {
90106 throw Err . exceedsMaxDecimals ( ) ;
91107 }
108+ if ( decimals < 0 ) {
109+ throw Err . negative ( ) ;
110+ }
111+ if ( Math . floor ( decimals ) !== decimals ) {
112+ throw Err . fractionalDecimals ( ) ;
113+ }
92114 }
93115
94116 /**
@@ -125,12 +147,54 @@ export function instanceOf(value: unknown): value is TokenAmount {
125147 return value instanceof TokenAmount ;
126148}
127149
150+ /**
151+ * Creates a TokenAmount from a number, string, {@linkcode Big}, or bigint.
152+ *
153+ * @param amount The amount of tokens as a number, string, big or bigint.
154+ * @returns {TokenAmount } The token amount.
155+ *
156+ * @throws {Err } If the value/decimals exceeds the maximum allowed or is negative.
157+ */
158+ export function fromDecimal ( amount : BigSource | bigint , decimals : number ) : TokenAmount {
159+ let parsed : BigSource ;
160+ if ( typeof amount !== 'bigint' ) {
161+ parsed = newBig ( amount ) ;
162+ } else {
163+ parsed = amount . toString ( ) ;
164+ }
165+
166+ const intAmount = newBig ( parsed ) . mul ( Big ( 10 ** decimals ) ) ;
167+ // Assert that the number is whole
168+ if ( ! intAmount . mod ( Big ( 1 ) ) . eq ( Big ( 0 ) ) ) {
169+ throw Err . fractionalValue ( ) ;
170+ }
171+
172+ return new TokenAmount ( BigInt ( intAmount . toString ( ) ) , decimals ) ;
173+ }
174+
175+ function newBig ( bigSource : BigSource ) : Big {
176+ if ( typeof bigSource === 'string' ) {
177+ return Big ( bigSource . replace ( ',' , '.' ) ) ;
178+ }
179+ return Big ( bigSource ) ;
180+ }
181+
182+ /**
183+ * Convert a token amount into a decimal value represented as a {@linkcode Big}
184+ *
185+ * @param {TokenAmount } amount
186+ * @returns {Big } The token amount as a {@linkcode Big}.
187+ */
188+ export function toDecimal ( amount : TokenAmount ) : Big {
189+ return Big ( amount . toString ( ) ) ;
190+ }
191+
128192/**
129193 * Converts {@linkcode JSON} to a token amount.
130194 *
131195 * @param {string } json The JSON representation of the CCD amount.
132- * @returns {CcdAmount } The CCD amount.
133- * @throws {Err } If the value/digits exceeds the maximum allowed or is negative.
196+ * @returns {TokenAmount } The CCD amount.
197+ * @throws {Err } If the value/decimals exceeds the maximum allowed or is negative.
134198 */
135199export function fromJSON ( json : JSON ) : TokenAmount {
136200 return new TokenAmount ( BigInt ( json . value ) , Number ( json . decimals ) ) ;
@@ -142,7 +206,7 @@ export function fromJSON(json: JSON): TokenAmount {
142206 * @param {bigint } value The integer representation of the token amount.
143207 * @param {number } decimals The decimals of the token amount, defining the precision at which amounts of the token can be specified.
144208 * @returns {TokenAmount } The token amount.
145- * @throws {Err } If the value/digits exceeds the maximum allowed or is negative.
209+ * @throws {Err } If the value/decimals exceeds the maximum allowed or is negative.
146210 */
147211export function create ( value : bigint , decimals : number ) : TokenAmount {
148212 return new TokenAmount ( value , decimals ) ;
@@ -153,7 +217,7 @@ export function create(value: bigint, decimals: number): TokenAmount {
153217 *
154218 * @param {number } decimals The decimals of the token amount, defining the precision at which amounts of the token can be specified.
155219 * @returns {TokenAmount } The token amount.
156- * @throws {Err } If the digits exceeds the maximum allowed.
220+ * @throws {Err } If the decimals exceeds the maximum allowed.
157221 */
158222export function zero ( decimals : number ) : TokenAmount {
159223 return new TokenAmount ( BigInt ( 0 ) , decimals ) ;
@@ -163,7 +227,7 @@ export function zero(decimals: number): TokenAmount {
163227 * Convert token amount from its protobuf encoding.
164228 * @param {Proto.TokenAmount } amount
165229 * @returns {Type } The token amount.
166- * @throws {Err } If the value/digits exceeds the maximum allowed or is negative.
230+ * @throws {Err } If the value/decimals exceeds the maximum allowed or is negative.
167231 */
168232export function fromProto ( amount : Proto . TokenAmount ) : Type {
169233 return create ( amount . digits , amount . nrOfDecimals ) ;
0 commit comments