diff --git a/modules/bitgo/src/v2/coinFactory.ts b/modules/bitgo/src/v2/coinFactory.ts index 109c9e339b..0594157217 100644 --- a/modules/bitgo/src/v2/coinFactory.ts +++ b/modules/bitgo/src/v2/coinFactory.ts @@ -516,6 +516,12 @@ export function registerCoinConstructors(coinFactory: CoinFactory, coinMap: Coin } ); + CantonToken.createTokenConstructors([...tokens.bitcoin.canton.tokens, ...tokens.testnet.canton.tokens]).forEach( + ({ name, coinConstructor }) => { + coinFactory.register(name, coinConstructor); + } + ); + HbarToken.createTokenConstructors([...tokens.bitcoin.hbar.tokens, ...tokens.testnet.hbar.tokens]).forEach( ({ name, coinConstructor }) => { coinFactory.register(name, coinConstructor); diff --git a/modules/sdk-coin-canton/src/register.ts b/modules/sdk-coin-canton/src/register.ts index fbc63c8f44..d8816e783d 100644 --- a/modules/sdk-coin-canton/src/register.ts +++ b/modules/sdk-coin-canton/src/register.ts @@ -1,4 +1,5 @@ -import { BitGoBase } from '@bitgo/sdk-core'; +import { BitGoBase, GlobalCoinFactory } from '@bitgo/sdk-core'; +import { type CoinMap, getFormattedCantonTokens } from '@bitgo/statics'; import { Canton } from './canton'; import { Tcanton } from './tcanton'; import { CantonToken } from './cantonToken'; @@ -10,3 +11,15 @@ export const register = (sdk: BitGoBase): void => { sdk.register(name, coinConstructor); }); }; + +export const registerWithCoinMap = (sdk: BitGoBase, coinMap: CoinMap): void => { + sdk.register('canton', Canton.createInstance); + sdk.register('tcanton', Tcanton.createInstance); + // Registration for Canton tokens from the coin map (includes both hardcoded and dynamic tokens from AMS). + CantonToken.createTokenConstructors(getFormattedCantonTokens(coinMap)).forEach(({ name, coinConstructor }) => { + sdk.register(name, coinConstructor); + if (coinMap.has(name)) { + GlobalCoinFactory.registerToken(coinMap.get(name), coinConstructor); + } + }); +}; diff --git a/modules/sdk-coin-canton/test/unit/register.ts b/modules/sdk-coin-canton/test/unit/register.ts new file mode 100644 index 0000000000..5a258dd28c --- /dev/null +++ b/modules/sdk-coin-canton/test/unit/register.ts @@ -0,0 +1,75 @@ +import sinon from 'sinon'; +import assert from 'assert'; +import { BitGoAPI } from '@bitgo/sdk-api'; +import { GlobalCoinFactory } from '@bitgo/sdk-core'; +import { coins, CantonToken } from '@bitgo/statics'; +import { register, registerWithCoinMap } from '../../src/register'; +import { CantonToken as SdkCantonToken } from '../../src/cantonToken'; + +describe('Canton Register', function () { + let bitgo: BitGoAPI; + let registerSpy: sinon.SinonSpy; + let registerTokenSpy: sinon.SinonSpy; + + beforeEach(function () { + bitgo = new BitGoAPI({ env: 'test' }); + registerSpy = sinon.spy(bitgo, 'register'); + registerTokenSpy = sinon.spy(GlobalCoinFactory, 'registerToken'); + }); + + afterEach(function () { + registerSpy.restore(); + registerTokenSpy.restore(); + }); + + describe('register', function () { + it('should register base coins and token constructors', function () { + register(bitgo); + + const registeredNames = registerSpy.getCalls().map((call) => call.args[0]); + + // Base coins should be registered + assert.ok(registeredNames.includes('canton')); + assert.ok(registeredNames.includes('tcanton')); + + // Canton tokens from statics should be registered + const cantonTokenCount = SdkCantonToken.createTokenConstructors().length; + assert.strictEqual(registerSpy.callCount, 2 + cantonTokenCount); + }); + }); + + describe('registerWithCoinMap', function () { + it('should call register internally for base coins and tokens', function () { + registerWithCoinMap(bitgo, coins); + + const registeredNames = registerSpy.getCalls().map((call) => call.args[0]); + + // Base coins should be registered + assert.ok(registeredNames.includes('canton')); + assert.ok(registeredNames.includes('tcanton')); + }); + + it('should add dynamic Canton tokens to the global coin map', function () { + registerWithCoinMap(bitgo, coins); + + // registerToken should have been called for Canton tokens in the coin map + assert.ok(registerTokenSpy.callCount > 0); + + // Each call should pass a valid coin from the coinMap + for (let i = 0; i < registerTokenSpy.callCount; i++) { + const call = registerTokenSpy.getCall(i); + const staticsCoin = call.args[0]; + assert.ok(coins.has(staticsCoin.name), `${staticsCoin.name} should exist in the coin map`); + } + }); + + it('should not add tokens to the global coin map when coin map has no Canton tokens', function () { + const limitedCoinMap = coins.filter((coin) => !(coin instanceof CantonToken)); + + registerWithCoinMap(bitgo, limitedCoinMap); + + // registerToken should not be called since no Canton tokens are in the map + assert.strictEqual(registerTokenSpy.callCount, 0); + }); + }); +});