From e2ef45826903f546950c4821d8cdc5bf0f362f01 Mon Sep 17 00:00:00 2001 From: Nayan Das Date: Sat, 31 Oct 2026 18:43:24 +0530 Subject: [PATCH] fix(sdk-coin-xlm): accept alphanumeric Stellar asset codes in StellarToken StellarToken.tokenPattern only allowed uppercase letters in the asset code, so any token whose code contains a digit (e.g. xlm:USDM1, xlm:USDT0) failed to instantiate and threw `invalid token: ...`. That blocked explainTransaction and signTransaction in OVC for those tokens, leaving clients unable to move funds. The pattern was also unanchored, so it silently matched codes it should have rejected. Stellar asset codes are alphanumeric (a-z, A-Z, 0-9) and 1-12 characters long: see stellar-core `isAsciiAlphaNumeric`/`isAssetValid` and the AssetCode4 and AssetCode12 XDR definitions. Lowercase is legal and already onboarded (xlm:gBENJI-...), so the code class must not be uppercase-only. Anchor the pattern and accept [a-zA-Z0-9] in the asset code. Ticket: COINS-2284 --- modules/sdk-coin-xlm/src/stellarToken.ts | 6 ++- modules/sdk-coin-xlm/test/unit/xlm.ts | 52 +++++++++++++++++++++++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/modules/sdk-coin-xlm/src/stellarToken.ts b/modules/sdk-coin-xlm/src/stellarToken.ts index 9c2c06eda03..31f302b2ad7 100644 --- a/modules/sdk-coin-xlm/src/stellarToken.ts +++ b/modules/sdk-coin-xlm/src/stellarToken.ts @@ -10,7 +10,11 @@ import { StellarTokenConfig, tokens } from '@bitgo/statics'; export { StellarTokenConfig }; export class StellarToken extends Xlm { - static readonly tokenPattern: RegExp = /[A-Z]{1,12}-G[A-Z0-9]{55}/; + // Stellar asset codes are alphanumeric (a-z, A-Z, 0-9) and 1-12 characters long — see + // stellar-core `isAsciiAlphaNumeric`/`isAssetValid` and the `AssetCode4`/`AssetCode12` + // XDR definitions. Lowercase is legal and already in use (e.g. xlm:gBENJI-...), so the + // code class must not be uppercase-only. Anchored so the whole token is validated. + static readonly tokenPattern: RegExp = /^[a-zA-Z0-9]{1,12}-G[A-Z0-9]{55}$/; public readonly tokenConfig: StellarTokenConfig; private readonly _code: string; private readonly _issuer: string; diff --git a/modules/sdk-coin-xlm/test/unit/xlm.ts b/modules/sdk-coin-xlm/test/unit/xlm.ts index e8171128048..6e60115f4dc 100644 --- a/modules/sdk-coin-xlm/test/unit/xlm.ts +++ b/modules/sdk-coin-xlm/test/unit/xlm.ts @@ -12,7 +12,7 @@ import { Wallet, } from '@bitgo/sdk-core'; import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test'; -import { Txlm, getStellarKeys } from '../../src'; +import { StellarToken, Txlm, getStellarKeys, register } from '../../src'; import { KeyPair } from '../../src/lib/keyPair'; import * as assert from 'assert'; @@ -1982,4 +1982,54 @@ describe('XLM:', function () { clawbackOperation.amount.should.equal('100000000'); }); }); + + describe('StellarToken:', () => { + const usdm1Issuer = 'GDM5QWWXCMDTQMZAKMYTCI52LA7FWBHAZMU5NJLMIFHDJISJRP2ZWPKC'; + + it('should register every Stellar token in statics, including alphanumeric asset codes', () => { + const tokenSdk = TestBitGo.decorate(BitGoAPI, { env: 'test' }); + register(tokenSdk); + + for (const { name } of StellarToken.createTokenConstructors()) { + tokenSdk.coin(name).type.should.equal(name); + } + }); + + it('should accept an asset code containing digits', () => { + const usdm1 = new StellarToken(bitgo, { + coin: 'xlm', + network: 'Mainnet', + name: 'USDM1', + type: `xlm:USDM1-${usdm1Issuer}`, + decimalPlaces: 7, + }); + + usdm1.code.should.equal('USDM1'); + usdm1.issuer.should.equal(usdm1Issuer); + }); + + it('should reject an asset code with a non-alphanumeric character', () => { + (() => { + new StellarToken(bitgo, { + coin: 'xlm', + network: 'Mainnet', + name: 'Invalid', + type: `xlm:USD!1-${usdm1Issuer}`, + decimalPlaces: 7, + }); + }).should.throw(/invalid token:/); + }); + + it('should reject an asset code longer than 12 characters', () => { + (() => { + new StellarToken(bitgo, { + coin: 'xlm', + network: 'Mainnet', + name: 'Invalid', + type: `xlm:TOOLONGASSETCODE-${usdm1Issuer}`, + decimalPlaces: 7, + }); + }).should.throw(/invalid token:/); + }); + }); });