From 38439550bbc71facf117f046fe967d597595db81 Mon Sep 17 00:00:00 2001 From: Sasha Mitchell Date: Fri, 25 Sep 2026 22:47:02 +0700 Subject: [PATCH] fix: keep lovelace amounts that do not fit in a JS number MeshValue.toJSON passed the quantity through Number, so 9007199254740993 lovelace became 9007199254740992. --- packages/mesh-common/src/data/value.ts | 17 ++++++++++++----- .../test/data/value/convertor.test.ts | 9 +++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/packages/mesh-common/src/data/value.ts b/packages/mesh-common/src/data/value.ts index 5d3d4d689..1a26c9fce 100644 --- a/packages/mesh-common/src/data/value.ts +++ b/packages/mesh-common/src/data/value.ts @@ -23,6 +23,12 @@ import { const compareByteOrder = (a: string, b: string): number => a < b ? -1 : a > b ? 1 : 0; +// A JS number cannot hold every lovelace amount. 2^53 + 1 is still under max ADA. +const jsInteger = (amount: bigint): number | bigint => { + const asNumber = Number(amount); + return BigInt(asNumber) === amount ? asNumber : amount; +}; + /** * Aiken alias * Value is the JSON representation of Cardano data Value @@ -377,7 +383,7 @@ export class MeshValue { toJSON = (): Value => { const valueMapToParse: [CurrencySymbol, AssocMap][] = []; - const valueMap: { [key: string]: { [key: string]: number } } = {}; + const valueMap: { [key: string]: { [key: string]: bigint } } = {}; this.toAssets().forEach((asset) => { const sanitizedName = asset.unit.replace("lovelace", ""); @@ -388,10 +394,11 @@ export class MeshValue { valueMap[policy] = {}; } - if (!valueMap[policy]![token]) { - valueMap[policy]![token] = Number(asset.quantity); + const quantity = BigInt(asset.quantity); + if (valueMap[policy]![token] === undefined) { + valueMap[policy]![token] = quantity; } else { - valueMap[policy]![token] += Number(asset.quantity); + valueMap[policy]![token] += quantity; } }); @@ -406,7 +413,7 @@ export class MeshValue { ); const tokens: [TokenName, Integer][] = sortedTokenNames.map((name) => [ tokenName(name), - integer(valueMap[policy]![name]!), + integer(jsInteger(valueMap[policy]![name]!)), ]); const policyMap = assocMap(tokens); diff --git a/packages/mesh-common/test/data/value/convertor.test.ts b/packages/mesh-common/test/data/value/convertor.test.ts index 673e8700f..7de6299e6 100644 --- a/packages/mesh-common/test/data/value/convertor.test.ts +++ b/packages/mesh-common/test/data/value/convertor.test.ts @@ -305,6 +305,15 @@ describe("MeshValue class", () => { expect(JSON.stringify(jsonValue)).toEqual(JSON.stringify(expectedValue)); }); + test("keeps a lovelace quantity that does not fit in a JS number", () => { + const quantity = "9007199254740993"; + const meshValue = new MeshValue(); + meshValue.toAssets = () => [{ unit: "lovelace", quantity }]; + + const jsonValue = meshValue.toJSON(); + expect(jsonValue.map[0].v.map[0].v.int).toBe(BigInt(quantity)); + }); + test("should correctly convert MeshValue to JSON with no asset", () => { const assets: Asset[] = [];