Skip to content

Commit d28381a

Browse files
feat(sdk-coin-eth): add registerWithCoinMap for dynamic token registration
TICKET: CSHLD-24 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ea42d2e commit d28381a

3 files changed

Lines changed: 93 additions & 2 deletions

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-coin-eth/src/register.ts

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

0 commit comments

Comments
 (0)