Skip to content

Commit e03258b

Browse files
feat(sdk-api): add registerWithBaseCoin for dynamic token registration
TICKET: CSHLD-24 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 1e32098 commit e03258b

5 files changed

Lines changed: 99 additions & 1 deletion

File tree

modules/sdk-api/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"dependencies": {
4343
"@bitgo/argon2": "^1.1.0",
4444
"@bitgo/sdk-core": "^36.41.0",
45+
"@bitgo/statics": "^58.36.0",
4546
"@bitgo/sdk-hmac": "^1.9.0",
4647
"@bitgo/sjcl": "^1.1.0",
4748
"@bitgo/unspents": "^0.51.3",

modules/sdk-api/src/bitgoAPI.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
makeRandomKey,
2323
sanitizeLegacyPath,
2424
} from '@bitgo/sdk-core';
25+
import { BaseCoin as StaticsBaseCoin } from '@bitgo/statics';
2526
import * as sdkHmac from '@bitgo/sdk-hmac';
2627
import { DefaultHmacAuthStrategy, type IHmacAuthStrategy } from '@bitgo/sdk-hmac';
2728
import * as utxolib from '@bitgo/utxo-lib';
@@ -1582,6 +1583,10 @@ export class BitGoAPI implements BitGoBase {
15821583
GlobalCoinFactory.register(name, coin);
15831584
}
15841585

1586+
public registerWithBaseCoin(coin: CoinConstructor, baseCoin: Readonly<StaticsBaseCoin>): void {
1587+
GlobalCoinFactory.registerToken(baseCoin, coin);
1588+
}
1589+
15851590
/**
15861591
* Get bitcoin market data
15871592
*

modules/sdk-coin-eth/src/register.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,16 @@ export const register = (sdk: BitGoBase): void => {
2121
};
2222

2323
export const registerWithCoinMap = (sdk: BitGoBase, coinMap: CoinMap): void => {
24-
Erc20Token.createTokenConstructors(getFormattedErc20Tokens(coinMap)).forEach(({ name, coinConstructor }) => {
24+
register(sdk);
25+
26+
// Registration for dynamic ERC20 tokens that are not hardcoded in the SDK, but are present in the coin map generated using AMS.
27+
const formattedTokens = getFormattedErc20Tokens(coinMap);
28+
Erc20Token.createTokenConstructors(formattedTokens).forEach(({ name, coinConstructor }) => {
29+
// Register constructor for both type names and contract addresses
2530
sdk.register(name, coinConstructor);
2631
});
32+
// Add new tokens to the global coin map so they're available for lookup
33+
formattedTokens.forEach((token) => {
34+
sdk.registerWithBaseCoin(Erc20Token.createTokenConstructor(token), coinMap.get(token.type));
35+
});
2736
};
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import sinon from 'sinon';
2+
import assert from 'assert';
3+
import { BitGoAPI } from '@bitgo/sdk-api';
4+
import { coins, Erc20Coin } from '@bitgo/statics';
5+
import { register, registerWithCoinMap } from '../../src/register';
6+
import { Erc20Token } from '../../src/erc20Token';
7+
import { Erc721Token } from '../../src/erc721Token';
8+
9+
describe('ETH Register', function () {
10+
let bitgo: BitGoAPI;
11+
let registerSpy: sinon.SinonSpy;
12+
let registerWithBaseCoinSpy: sinon.SinonSpy;
13+
14+
beforeEach(function () {
15+
bitgo = new BitGoAPI({ env: 'test' });
16+
registerSpy = sinon.spy(bitgo, 'register');
17+
registerWithBaseCoinSpy = sinon.spy(bitgo, 'registerWithBaseCoin');
18+
});
19+
20+
afterEach(function () {
21+
registerSpy.restore();
22+
registerWithBaseCoinSpy.restore();
23+
});
24+
25+
describe('register', function () {
26+
it('should register base coins and token constructors', function () {
27+
register(bitgo);
28+
29+
const registeredNames = registerSpy.getCalls().map((call) => call.args[0]);
30+
31+
// Base coins should be registered
32+
assert.ok(registeredNames.includes('eth'));
33+
assert.ok(registeredNames.includes('gteth'));
34+
assert.ok(registeredNames.includes('teth'));
35+
assert.ok(registeredNames.includes('hteth'));
36+
37+
// ERC20 and ERC721 tokens should be registered
38+
const erc20Count = Erc20Token.createTokenConstructors().length;
39+
const erc721Count = Erc721Token.createTokenConstructors().length;
40+
assert.strictEqual(registerSpy.callCount, 4 + erc20Count + erc721Count);
41+
});
42+
});
43+
44+
describe('registerWithCoinMap', function () {
45+
it('should call register internally for base coins and tokens', function () {
46+
registerWithCoinMap(bitgo, coins);
47+
48+
const registeredNames = registerSpy.getCalls().map((call) => call.args[0]);
49+
50+
// Base coins should be registered via register()
51+
assert.ok(registeredNames.includes('eth'));
52+
assert.ok(registeredNames.includes('gteth'));
53+
assert.ok(registeredNames.includes('teth'));
54+
assert.ok(registeredNames.includes('hteth'));
55+
});
56+
57+
it('should register dynamic ERC20 tokens via registerWithBaseCoin', function () {
58+
registerWithCoinMap(bitgo, coins);
59+
60+
// registerWithBaseCoin should have been called for dynamic tokens
61+
assert.ok(registerWithBaseCoinSpy.callCount > 0);
62+
63+
// Each call should pass a valid baseCoin from the coinMap
64+
for (let i = 0; i < registerWithBaseCoinSpy.callCount; i++) {
65+
const call = registerWithBaseCoinSpy.getCall(i);
66+
const baseCoin = call.args[1];
67+
assert.ok(coins.has(baseCoin.name), `${baseCoin.name} should exist in the coin map`);
68+
}
69+
});
70+
71+
it('should not call registerWithBaseCoin when coin map has no ERC20 tokens', function () {
72+
// Create a coin map with only base coins (no ERC20 tokens)
73+
const limitedCoinMap = coins.filter((coin) => !(coin instanceof Erc20Coin));
74+
75+
registerWithCoinMap(bitgo, limitedCoinMap);
76+
77+
// registerWithBaseCoin should not be called since no ERC20 tokens are in the map
78+
assert.strictEqual(registerWithBaseCoinSpy.callCount, 0);
79+
});
80+
});
81+
});

modules/sdk-core/src/bitgo/bitgoBase.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
GetSharingKeyOptions,
77
IRequestTracer,
88
} from '../api';
9+
import { BaseCoin as StaticsBaseCoin } from '@bitgo/statics';
910
import { IBaseCoin } from './baseCoin';
1011
import { CoinConstructor } from './coinFactory';
1112
import { EnvironmentName } from './environments';
@@ -35,4 +36,5 @@ export interface BitGoBase {
3536
setRequestTracer(reqTracer: IRequestTracer): void;
3637
url(path: string, version?: number): string;
3738
register(name: string, coin: CoinConstructor): void;
39+
registerWithBaseCoin(coin: CoinConstructor, baseCoin: Readonly<StaticsBaseCoin>): void;
3840
}

0 commit comments

Comments
 (0)