diff --git a/src/walletSdk/Utils/soroban.ts b/src/walletSdk/Utils/soroban.ts index d2cc814..305a393 100644 --- a/src/walletSdk/Utils/soroban.ts +++ b/src/walletSdk/Utils/soroban.ts @@ -1,4 +1,5 @@ import { Operation, StrKey, scValToNative, xdr } from "@stellar/stellar-sdk"; +import BigNumber from "bignumber.js"; import { ArgsForTokenInvocation, @@ -82,3 +83,85 @@ export const getTokenInvocationArgs = ( ...opArgs, }; }; + +// Adopted from https://github.com/ethers-io/ethers.js/blob/master/packages/bignumber/src.ts/fixednumber.ts#L27 +/* + This function formats an integer amount (e.g. a bigint value which is broadly + used in the soroban env) to a stringfied floating amount. This function is + basically the counterpart of the 'parseTokenAmount' function below. + + E.g: formatTokenAmount(BigInt(10000001234567), 7) => "1000000.1234567" +*/ +export const formatTokenAmount = ( + amount: bigint | BigNumber | number | string, + decimals: number, +): string => { + const bigNumberAmount = new BigNumber(amount.toString()); + + let formatted = bigNumberAmount.toString(); + + if (decimals > 0) { + formatted = bigNumberAmount + .shiftedBy(-decimals) + .toFixed(decimals) + .toString(); + + // Trim trailing zeros + while (formatted[formatted.length - 1] === "0") { + formatted = formatted.substring(0, formatted.length - 1); + } + + if (formatted.endsWith(".")) { + formatted = formatted.substring(0, formatted.length - 1); + } + } + + return formatted; +}; + +/* + This function parses a floating amount to a bigint amount, which is broadly + used in the soroban env. This function is basically the counterpart of the + 'formatTokenAmount' function above. + + E.g: parseTokenAmount("1000000.1234567", 7) => BigInt(10000001234567) +*/ +export const parseTokenAmount = ( + amount: string | number | BigNumber | bigint, + decimals: number, +): bigint => { + const comps = amount.toString().split("."); + + let whole = comps[0]; + let fraction = comps[1]; + if (!whole) { + whole = "0"; + } + if (!fraction) { + fraction = "0"; + } + + // Trim trailing zeros + while (fraction[fraction.length - 1] === "0") { + fraction = fraction.substring(0, fraction.length - 1); + } + + // If decimals is 0, we have an empty string for fraction + if (fraction === "") { + fraction = "0"; + } + + // Fully pad the string with zeros to get to value + while (fraction.length < decimals) { + fraction += "0"; + } + + const wholeValue = new BigNumber(whole); + const fractionValue = new BigNumber(fraction); + + // This basically appends the 'whole' and 'fraction' values into + // an integer value + const parsed = wholeValue.shiftedBy(decimals).plus(fractionValue); + + return BigInt(parsed.toString()); +}; diff --git a/test/soroban.test.ts b/test/soroban.test.ts index 2fe2f67..1f15a64 100644 --- a/test/soroban.test.ts +++ b/test/soroban.test.ts @@ -6,8 +6,13 @@ import { Transaction, TransactionBuilder, } from "@stellar/stellar-sdk"; +import BigNumber from "bignumber.js"; -import { getTokenInvocationArgs } from "../src/walletSdk/Utils"; +import { + formatTokenAmount, + getTokenInvocationArgs, + parseTokenAmount, +} from "../src/walletSdk/Utils"; import { SorobanTokenInterface } from "../src/walletSdk/Types"; const transactions = { @@ -73,4 +78,42 @@ describe("Soroban Utils", () => { expect(args).toBe(null); }); + + it("should format different types of token amount values", () => { + const formatted = "1000000.1234567"; + + const value1 = BigInt(10000001234567); + expect(formatTokenAmount(value1, 7)).toStrictEqual(formatted); + + const value2 = new BigNumber("10000001234567"); + expect(formatTokenAmount(value2, 7)).toStrictEqual(formatted); + + const value3 = Number("10000001234567"); + expect(formatTokenAmount(value3, 7)).toStrictEqual(formatted); + + const value4 = 10000001234567; + expect(formatTokenAmount(value4, 7)).toStrictEqual(formatted); + + const value5 = "10000001234567"; + expect(formatTokenAmount(value5, 7)).toStrictEqual(formatted); + }); + + it("should parse different types of token amount values", () => { + const parsed = BigInt(10000001234567); + + const value5 = "1000000.1234567"; + expect(parseTokenAmount(value5, 7) === parsed).toBeTruthy(); + + const value4 = 1000000.1234567; + expect(parseTokenAmount(value4, 7) === parsed).toBeTruthy(); + + const value3 = Number("1000000.1234567"); + expect(parseTokenAmount(value3, 7) === parsed).toBeTruthy(); + + const value2 = new BigNumber("1000000.1234567"); + expect(parseTokenAmount(value2, 7) === parsed).toBeTruthy(); + + const value1 = BigInt("123"); + expect(parseTokenAmount(value1, 3) === BigInt(123000)).toBeTruthy(); + }); });