From d797752471757f530308089f14f5d54d3e572622 Mon Sep 17 00:00:00 2001 From: Nexory Date: Wed, 27 May 2026 19:36:08 +0200 Subject: [PATCH 1/3] fix(Coin): align fromString denom regex with Cosmos denom spec Coin.fromString used the denom character class [0-9a-zA-Z/], which is both narrower and looser than the Cosmos SDK denom spec ([a-zA-Z][a-zA-Z0-9/:._-]{2,127}): - denoms containing ':' '.' '_' '-' (e.g. tokenfactory subdenoms) failed to parse, so fromString(coin.toString()) did not round-trip; - numeric-only strings (e.g. '5000') matched and were mis-parsed into a garbage Coin('0', 500) instead of throwing. Require the denom to start with a letter and allow the full Cosmos denom charset. Existing denoms (uinit, ibc/, decimal amounts) are unaffected. Adds round-trip and numeric-only tests. --- src/core/Coin.spec.ts | 14 ++++++++++++++ src/core/Coin.ts | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/core/Coin.spec.ts b/src/core/Coin.spec.ts index a9caf8a..e48485b 100644 --- a/src/core/Coin.spec.ts +++ b/src/core/Coin.spec.ts @@ -147,5 +147,19 @@ describe('Coin', () => { ) expect(coin3).toEqual(coin4) }) + + it('parses denoms containing "-", "." and "_" (e.g. tokenfactory subdenoms)', () => { + const coin1 = new Coin('factory/init1abc/my-token', 1001) + const coin2 = Coin.fromString('1001factory/init1abc/my-token') + expect(coin1).toEqual(coin2) + + // fromString(toString()) must round-trip for any valid Cosmos denom + expect(Coin.fromString(coin1.toString())).toEqual(coin1) + }) + + it('throws on a numeric-only string (a denom must start with a letter)', () => { + expect(() => Coin.fromString('5000')).toThrow() + expect(() => Coin.fromString('12')).toThrow() + }) }) }) diff --git a/src/core/Coin.ts b/src/core/Coin.ts index 6d02557..99fcdeb 100644 --- a/src/core/Coin.ts +++ b/src/core/Coin.ts @@ -69,7 +69,7 @@ export class Coin extends JSONSerializable { } public static fromString(str: string): Coin { - const m = str.match(/^(-?[0-9]+(\.[0-9]+)?)([0-9a-zA-Z/]+)$/) + const m = str.match(/^(-?[0-9]+(\.[0-9]+)?)([a-zA-Z][0-9a-zA-Z/:._-]*)$/) if (m === null) { throw new Error(`failed to parse to Coin: ${str}`) } From 312c518627c772801b4bd14dbdde8d6346a78128 Mon Sep 17 00:00:00 2001 From: Nexory Date: Wed, 27 May 2026 22:16:59 +0200 Subject: [PATCH 2/3] test(Coin): enforce Cosmos denom length bound (3-128) and cover . and _ in round-trip test Address review feedback on the denom regex: - bound the denom to the Cosmos default length range ([a-zA-Z][...]{2,127}), matching the SDK's reDnmString (total length 3-128) instead of an unbounded quantifier; - extend the round-trip test denom to include '.', '_' and '-' so it matches the cases named in the test title. --- src/core/Coin.spec.ts | 4 ++-- src/core/Coin.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/Coin.spec.ts b/src/core/Coin.spec.ts index e48485b..04cfb71 100644 --- a/src/core/Coin.spec.ts +++ b/src/core/Coin.spec.ts @@ -149,8 +149,8 @@ describe('Coin', () => { }) it('parses denoms containing "-", "." and "_" (e.g. tokenfactory subdenoms)', () => { - const coin1 = new Coin('factory/init1abc/my-token', 1001) - const coin2 = Coin.fromString('1001factory/init1abc/my-token') + const coin1 = new Coin('factory/init1abc/my-token_v1.5', 1001) + const coin2 = Coin.fromString('1001factory/init1abc/my-token_v1.5') expect(coin1).toEqual(coin2) // fromString(toString()) must round-trip for any valid Cosmos denom diff --git a/src/core/Coin.ts b/src/core/Coin.ts index 99fcdeb..aa532d7 100644 --- a/src/core/Coin.ts +++ b/src/core/Coin.ts @@ -69,7 +69,7 @@ export class Coin extends JSONSerializable { } public static fromString(str: string): Coin { - const m = str.match(/^(-?[0-9]+(\.[0-9]+)?)([a-zA-Z][0-9a-zA-Z/:._-]*)$/) + const m = str.match(/^(-?[0-9]+(\.[0-9]+)?)([a-zA-Z][0-9a-zA-Z/:._-]{2,127})$/) if (m === null) { throw new Error(`failed to parse to Coin: ${str}`) } From 6ef7adef37341a5de74dbf27f98cadab34fe65ab Mon Sep 17 00:00:00 2001 From: Nexory Date: Wed, 27 May 2026 22:22:57 +0200 Subject: [PATCH 3/3] style(Coin): wrap fromString regex to satisfy Prettier line width --- src/core/Coin.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/Coin.ts b/src/core/Coin.ts index aa532d7..6a2ab29 100644 --- a/src/core/Coin.ts +++ b/src/core/Coin.ts @@ -69,7 +69,9 @@ export class Coin extends JSONSerializable { } public static fromString(str: string): Coin { - const m = str.match(/^(-?[0-9]+(\.[0-9]+)?)([a-zA-Z][0-9a-zA-Z/:._-]{2,127})$/) + const m = str.match( + /^(-?[0-9]+(\.[0-9]+)?)([a-zA-Z][0-9a-zA-Z/:._-]{2,127})$/ + ) if (m === null) { throw new Error(`failed to parse to Coin: ${str}`) }