Skip to content

Commit 6560fde

Browse files
authored
Merge pull request #7654 from BitGo/WIN-8098
feat: onboard thypeevm:usdc
2 parents 7e78329 + 533ac0e commit 6560fde

File tree

8 files changed

+147
-10
lines changed

8 files changed

+147
-10
lines changed

modules/bitgo/src/v2/coinFactory.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -911,9 +911,9 @@ export const buildEthLikeChainToTestnetMap = (): {
911911
const testnetToMainnetMap: Record<string, string> = {};
912912
const mainnetToTestnetMap: Record<string, string> = {};
913913

914-
const enabledEvmCoins = ['ip'];
914+
const enabledEvmCoins = ['ip', 'hypeevm'];
915915

916-
// TODO: remove ip coin here and remove other evm coins from switch block, once changes are tested (Ticket: https://bitgoinc.atlassian.net/browse/WIN-7835)
916+
// TODO: remove ip and hypeeevm coins here and remove other evm coins from switch block, once changes are tested (Ticket: https://bitgoinc.atlassian.net/browse/WIN-7835)
917917
coins.forEach((coin) => {
918918
if (coin.network.type === NetworkType.TESTNET && !coin.isToken && enabledEvmCoins.includes(coin.family)) {
919919
if (coins.get(coin.family)?.features.includes(CoinFeature.SUPPORTS_ERC20)) {
@@ -926,7 +926,6 @@ export const buildEthLikeChainToTestnetMap = (): {
926926
return { mainnetToTestnetMap, testnetToMainnetMap };
927927
};
928928

929-
// TODO: add IP token here and test changes (Ticket: https://bitgoinc.atlassian.net/browse/WIN-7835)
930929
const { mainnetToTestnetMap, testnetToMainnetMap } = buildEthLikeChainToTestnetMap();
931930

932931
export function getTokenConstructor(tokenConfig: TokenConfig): CoinConstructor | undefined {

modules/bitgo/test/v2/resources/amsTokenConfig.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,26 @@ export const reducedAmsTokenConfig = {
4343
contractAddress: '0x1234567890123456789012345678901234567890',
4444
},
4545
],
46+
'thypeevm:faketoken': [
47+
{
48+
id: 'b2c3d4e5-f6a7-4890-9bcd-ef012345678a',
49+
fullName: 'Hyperliquid EVM Testnet Faketoken',
50+
name: 'thypeevm:faketoken',
51+
prefix: '',
52+
suffix: 'THYPEEVM:FAKETOKEN',
53+
baseUnit: 'wei',
54+
kind: 'crypto',
55+
family: 'hypeevm',
56+
isToken: true,
57+
additionalFeatures: [],
58+
excludedFeatures: [],
59+
decimalPlaces: 18,
60+
asset: 'thypeevm:faketoken',
61+
network: {
62+
name: 'HyperliquidTestnet',
63+
},
64+
primaryKeyCurve: 'secp256k1',
65+
contractAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd',
66+
},
67+
],
4668
};

modules/bitgo/test/v2/unit/ams/ams.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,5 +131,28 @@ describe('Asset metadata service', () => {
131131
}
132132
staticsCoin.family.should.equal('ip');
133133
});
134+
135+
it('should register a thypeevm EVM coin token from AMS', async () => {
136+
const bitgo = TestBitGo.decorate(BitGo, { env: 'mock', microservicesUri, useAms: true } as BitGoOptions);
137+
bitgo.initializeTestVars();
138+
139+
const tokenName = 'thypeevm:faketoken';
140+
141+
// Setup nocks for AMS API call
142+
nock(microservicesUri).get(`/api/v1/assets/name/${tokenName}`).reply(200, reducedAmsTokenConfig[tokenName][0]);
143+
144+
await bitgo.registerToken(tokenName);
145+
const coin = bitgo.coin(tokenName);
146+
should.exist(coin);
147+
coin.type.should.equal(tokenName);
148+
const staticsCoin = coin.getConfig();
149+
staticsCoin.name.should.equal('thypeevm');
150+
staticsCoin.decimalPlaces.should.equal(18);
151+
// For EVM tokens, contractAddress is available on the statics coin
152+
if ('contractAddress' in staticsCoin && staticsCoin.contractAddress) {
153+
(staticsCoin.contractAddress as string).should.equal('0xabcdefabcdefabcdefabcdefabcdefabcdefabcd');
154+
}
155+
staticsCoin.family.should.equal('hypeevm');
156+
});
134157
});
135158
});

modules/statics/src/allCoinsAndTokens.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1634,6 +1634,7 @@ export const allCoinsAndTokens = [
16341634
CoinFeature.EVM_COMPATIBLE_UI,
16351635
CoinFeature.EVM_NON_BITGO_RECOVERY,
16361636
CoinFeature.EVM_UNSIGNED_SWEEP_RECOVERY,
1637+
CoinFeature.SUPPORTS_ERC20,
16371638
]
16381639
),
16391640
account(
@@ -2771,6 +2772,17 @@ export const allCoinsAndTokens = [
27712772
Networks.main.mon
27722773
),
27732774

2775+
// hypeeevm testnet tokens
2776+
erc20Token(
2777+
'2460e83c-e819-42c3-83c9-3974e08a45c8',
2778+
'thypeevm:usdc',
2779+
'Testnet HypeEVM USDC',
2780+
6,
2781+
'0x421cdf5e890070c28db0fd8e4bf87deac0cd0ffc',
2782+
UnderlyingAsset['thypeevm:usdc'],
2783+
Networks.test.hypeevm
2784+
),
2785+
27742786
// Story testnet tokens
27752787
erc20Token(
27762788
'f9a9c36f-8938-4206-bf0d-5016a861c58f',
@@ -2784,7 +2796,7 @@ export const allCoinsAndTokens = [
27842796

27852797
// Story mainnet tokens
27862798
erc20Token(
2787-
'2460e83c-e819-42c3-83c9-3974e08a45c8',
2799+
'a2460e83-e819-42c3-83c9-3974e08a45c9',
27882800
'ip:aria',
27892801
'Aria',
27902802
18,

modules/statics/src/base.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2919,6 +2919,9 @@ export enum UnderlyingAsset {
29192919
'xdc:srx' = 'xdc:srx',
29202920
'xdc:weth' = 'xdc:weth',
29212921

2922+
// hypeeevm testnet tokens
2923+
'thypeevm:usdc' = 'thypeevm:usdc',
2924+
29222925
// Story testnet tokens
29232926
'tip:usdc' = 'tip:usdc',
29242927

modules/statics/src/coins.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ export const coins = CoinMap.fromCoins([
5151
// Maps family -> coin name (e.g., 'ip' -> 'ip')
5252
const erc20ChainToNameMap: Record<string, string> = {};
5353

54-
// TODO: remove ip coin here and remove other evm coins from switch block, once changes are tested (Ticket: https://bitgoinc.atlassian.net/browse/WIN-7835)
55-
const enabledEvmCoins = ['ip'];
54+
// TODO: remove ip and hypeeevm coins here and remove other evm coins from switch block, once changes are tested (Ticket: https://bitgoinc.atlassian.net/browse/WIN-7835)
55+
const enabledEvmCoins = ['ip', 'hypeevm'];
5656
allCoinsAndTokens.forEach((coin) => {
5757
if (
5858
coin.features.includes(CoinFeature.SUPPORTS_ERC20) &&

modules/statics/src/tokenConfig.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1097,7 +1097,7 @@ export const getEthLikeTokens = (network: 'Mainnet' | 'Testnet'): EthLikeTokenMa
10971097
const networkTokens = getFormattedEthLikeTokenConfig().filter((token) => token.network === network);
10981098
const ethLikeTokenMap = {} as EthLikeTokenMap;
10991099
// TODO: add IP token here and test changes (Ticket: https://bitgoinc.atlassian.net/browse/WIN-7835)
1100-
const enabledChains = ['ip'] as string[];
1100+
const enabledChains = ['ip', 'hypeevm'] as string[];
11011101

11021102
coins.forEach((coin) => {
11031103
// TODO: remove enabled chains once changes are done (Ticket: https://bitgoinc.atlassian.net/browse/WIN-7835)

modules/statics/test/unit/tokenConfigTests.ts

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,64 @@ describe('EthLike Token Config Functions', function () {
123123
config.coin.should.equal('ip');
124124
config.type.should.equal('ip:usdc');
125125
});
126+
127+
it('should convert an EthLikeERC20Token to EthLikeTokenConfig for hypeevm mainnet', function () {
128+
// Create a mock mainnet EthLikeERC20Token for hypeevm
129+
const mockMainnetToken = new EthLikeERC20Token({
130+
id: 'a1234567-1234-4234-8234-123456789012',
131+
name: 'hypeevm:testtoken',
132+
fullName: 'HypeEVM Test Token',
133+
network: Networks.main.hypeevm,
134+
contractAddress: '0x9876543210987654321098765432109876543210',
135+
decimalPlaces: 18,
136+
asset: UnderlyingAsset.HYPEEVM,
137+
features: [...AccountCoin.DEFAULT_FEATURES, CoinFeature.EIP1559],
138+
prefix: '',
139+
suffix: 'TESTTOKEN',
140+
primaryKeyCurve: KeyCurve.Secp256k1,
141+
isToken: true,
142+
baseUnit: BaseUnit.ETH,
143+
});
144+
145+
const config = getFormattedEthLikeTokenConfig(CoinMap.fromCoins([mockMainnetToken]))[0];
146+
147+
config.should.not.be.undefined();
148+
config.type.should.equal('hypeevm:testtoken');
149+
config.coin.should.equal('hypeevm');
150+
config.network.should.equal('Mainnet');
151+
config.name.should.equal('HypeEVM Test Token');
152+
config.tokenContractAddress.should.equal('0x9876543210987654321098765432109876543210');
153+
config.decimalPlaces.should.equal(18);
154+
});
155+
156+
it('should convert an EthLikeERC20Token to EthLikeTokenConfig for thypeevm testnet', function () {
157+
// Create a mock testnet EthLikeERC20Token for thypeevm
158+
const mockTestnetToken = new EthLikeERC20Token({
159+
id: 'b2234567-2234-4234-9234-223456789012',
160+
name: 'thypeevm:testtoken',
161+
fullName: 'HypeEVM Test Token Testnet',
162+
network: Networks.test.hypeevm,
163+
contractAddress: '0xfedcba0987654321fedcba0987654321fedcba09',
164+
decimalPlaces: 18,
165+
asset: UnderlyingAsset.HYPEEVM,
166+
features: [...AccountCoin.DEFAULT_FEATURES, CoinFeature.EIP1559],
167+
prefix: '',
168+
suffix: 'TESTTOKEN',
169+
primaryKeyCurve: KeyCurve.Secp256k1,
170+
isToken: true,
171+
baseUnit: BaseUnit.ETH,
172+
});
173+
174+
const config = getFormattedEthLikeTokenConfig(CoinMap.fromCoins([mockTestnetToken]))[0];
175+
176+
config.should.not.be.undefined();
177+
config.type.should.equal('thypeevm:testtoken');
178+
config.coin.should.equal('thypeevm');
179+
config.network.should.equal('Testnet');
180+
config.name.should.equal('HypeEVM Test Token Testnet');
181+
config.tokenContractAddress.should.equal('0xfedcba0987654321fedcba0987654321fedcba09');
182+
config.decimalPlaces.should.equal(18);
183+
});
126184
});
127185

128186
describe('getFormattedEthLikeTokenConfig', function () {
@@ -254,11 +312,15 @@ describe('EthLike Token Config Functions', function () {
254312
const result = getEthLikeTokens('Mainnet');
255313

256314
result.should.be.an.Object();
257-
// The function filters by enabledChains which currently includes 'ip'
315+
// The function filters by enabledChains which currently includes 'ip' and 'hypeevm'
258316
if (result.ip) {
259317
result.ip.should.have.property('tokens');
260318
result.ip.tokens.should.be.an.Array();
261319
}
320+
if (result.hypeevm) {
321+
result.hypeevm.should.have.property('tokens');
322+
result.hypeevm.tokens.should.be.an.Array();
323+
}
262324
});
263325

264326
it('should filter mainnet tokens correctly', function () {
@@ -289,6 +351,11 @@ describe('EthLike Token Config Functions', function () {
289351
token.coin.should.equal('tip');
290352
});
291353
}
354+
if (result.hypeevm && result.hypeevm.tokens.length > 0) {
355+
result.hypeevm.tokens.forEach((token) => {
356+
token.coin.should.equal('thypeevm');
357+
});
358+
}
292359
});
293360

294361
it('should not prepend "t" to coin name for mainnet tokens', function () {
@@ -299,6 +366,11 @@ describe('EthLike Token Config Functions', function () {
299366
token.coin.should.equal('ip');
300367
});
301368
}
369+
if (result.hypeevm && result.hypeevm.tokens.length > 0) {
370+
result.hypeevm.tokens.forEach((token) => {
371+
token.coin.should.equal('hypeevm');
372+
});
373+
}
302374
});
303375

304376
it('should only include tokens from chains with SUPPORTS_ERC20 feature', function () {
@@ -318,8 +390,8 @@ describe('EthLike Token Config Functions', function () {
318390
const mainnetResult = getEthLikeTokens('Mainnet');
319391
const testnetResult = getEthLikeTokens('Testnet');
320392

321-
// Current implementation only enables 'ip' chain
322-
const enabledChains = ['ip'];
393+
// Current implementation enables 'ip' and 'hypeevm' chains
394+
const enabledChains = ['ip', 'hypeevm'];
323395

324396
Object.keys(mainnetResult).forEach((family) => {
325397
enabledChains.should.containEql(family);
@@ -348,6 +420,12 @@ describe('EthLike Token Config Functions', function () {
348420
token.coin.should.equal('ip');
349421
});
350422
}
423+
if (result.hypeevm && result.hypeevm.tokens.length > 0) {
424+
result.hypeevm.tokens.forEach((token) => {
425+
// All tokens in hypeevm group should have coin 'hypeevm'
426+
token.coin.should.equal('hypeevm');
427+
});
428+
}
351429
});
352430

353431
it('should return tokens with correct structure', function () {

0 commit comments

Comments
 (0)