Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions modules/bitgo/src/v2/coinFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -911,9 +911,9 @@ export const buildEthLikeChainToTestnetMap = (): {
const testnetToMainnetMap: Record<string, string> = {};
const mainnetToTestnetMap: Record<string, string> = {};

const enabledEvmCoins = ['ip'];
const enabledEvmCoins = ['ip', 'hypeevm'];

// 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)
// 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)
coins.forEach((coin) => {
if (coin.network.type === NetworkType.TESTNET && !coin.isToken && enabledEvmCoins.includes(coin.family)) {
if (coins.get(coin.family)?.features.includes(CoinFeature.SUPPORTS_ERC20)) {
Expand All @@ -926,7 +926,6 @@ export const buildEthLikeChainToTestnetMap = (): {
return { mainnetToTestnetMap, testnetToMainnetMap };
};

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

export function getTokenConstructor(tokenConfig: TokenConfig): CoinConstructor | undefined {
Expand Down
22 changes: 22 additions & 0 deletions modules/bitgo/test/v2/resources/amsTokenConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,26 @@ export const reducedAmsTokenConfig = {
contractAddress: '0x1234567890123456789012345678901234567890',
},
],
'thypeevm:faketoken': [
{
id: 'b2c3d4e5-f6a7-4890-9bcd-ef012345678a',
fullName: 'Hyperliquid EVM Testnet Faketoken',
name: 'thypeevm:faketoken',
prefix: '',
suffix: 'THYPEEVM:FAKETOKEN',
baseUnit: 'wei',
kind: 'crypto',
family: 'hypeevm',
isToken: true,
additionalFeatures: [],
excludedFeatures: [],
decimalPlaces: 18,
asset: 'thypeevm:faketoken',
network: {
name: 'HyperliquidTestnet',
},
primaryKeyCurve: 'secp256k1',
contractAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd',
},
],
};
23 changes: 23 additions & 0 deletions modules/bitgo/test/v2/unit/ams/ams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,5 +131,28 @@ describe('Asset metadata service', () => {
}
staticsCoin.family.should.equal('ip');
});

it('should register a thypeevm EVM coin token from AMS', async () => {
const bitgo = TestBitGo.decorate(BitGo, { env: 'mock', microservicesUri, useAms: true } as BitGoOptions);
bitgo.initializeTestVars();

const tokenName = 'thypeevm:faketoken';

// Setup nocks for AMS API call
nock(microservicesUri).get(`/api/v1/assets/name/${tokenName}`).reply(200, reducedAmsTokenConfig[tokenName][0]);

await bitgo.registerToken(tokenName);
const coin = bitgo.coin(tokenName);
should.exist(coin);
coin.type.should.equal(tokenName);
const staticsCoin = coin.getConfig();
staticsCoin.name.should.equal('thypeevm');
staticsCoin.decimalPlaces.should.equal(18);
// For EVM tokens, contractAddress is available on the statics coin
if ('contractAddress' in staticsCoin && staticsCoin.contractAddress) {
(staticsCoin.contractAddress as string).should.equal('0xabcdefabcdefabcdefabcdefabcdefabcdefabcd');
}
staticsCoin.family.should.equal('hypeevm');
});
});
});
14 changes: 13 additions & 1 deletion modules/statics/src/allCoinsAndTokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1634,6 +1634,7 @@ export const allCoinsAndTokens = [
CoinFeature.EVM_COMPATIBLE_UI,
CoinFeature.EVM_NON_BITGO_RECOVERY,
CoinFeature.EVM_UNSIGNED_SWEEP_RECOVERY,
CoinFeature.SUPPORTS_ERC20,
]
),
account(
Expand Down Expand Up @@ -2762,6 +2763,17 @@ export const allCoinsAndTokens = [
Networks.main.mon
),

// hypeeevm testnet tokens
erc20Token(
'2460e83c-e819-42c3-83c9-3974e08a45c8',
'thypeevm:usdc',
'Testnet HypeEVM USDC',
6,
'0x421cdf5e890070c28db0fd8e4bf87deac0cd0ffc',
UnderlyingAsset['thypeevm:usdc'],
Networks.test.hypeevm
),

// Story testnet tokens
erc20Token(
'f9a9c36f-8938-4206-bf0d-5016a861c58f',
Expand All @@ -2775,7 +2787,7 @@ export const allCoinsAndTokens = [

// Story mainnet tokens
erc20Token(
'2460e83c-e819-42c3-83c9-3974e08a45c8',
'a2460e83-e819-42c3-83c9-3974e08a45c9',
'ip:aria',
'Aria',
18,
Expand Down
3 changes: 3 additions & 0 deletions modules/statics/src/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2909,6 +2909,9 @@ export enum UnderlyingAsset {
'xdc:srx' = 'xdc:srx',
'xdc:weth' = 'xdc:weth',

// hypeeevm testnet tokens
'thypeevm:usdc' = 'thypeevm:usdc',

// Story testnet tokens
'tip:usdc' = 'tip:usdc',

Expand Down
4 changes: 2 additions & 2 deletions modules/statics/src/coins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ export const coins = CoinMap.fromCoins([
// Maps family -> coin name (e.g., 'ip' -> 'ip')
const erc20ChainToNameMap: Record<string, string> = {};

// 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)
const enabledEvmCoins = ['ip'];
// 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)
const enabledEvmCoins = ['ip', 'hypeevm'];
allCoinsAndTokens.forEach((coin) => {
if (
coin.features.includes(CoinFeature.SUPPORTS_ERC20) &&
Expand Down
2 changes: 1 addition & 1 deletion modules/statics/src/tokenConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1097,7 +1097,7 @@ export const getEthLikeTokens = (network: 'Mainnet' | 'Testnet'): EthLikeTokenMa
const networkTokens = getFormattedEthLikeTokenConfig().filter((token) => token.network === network);
const ethLikeTokenMap = {} as EthLikeTokenMap;
// TODO: add IP token here and test changes (Ticket: https://bitgoinc.atlassian.net/browse/WIN-7835)
const enabledChains = ['ip'] as string[];
const enabledChains = ['ip', 'hypeevm'] as string[];

coins.forEach((coin) => {
// TODO: remove enabled chains once changes are done (Ticket: https://bitgoinc.atlassian.net/browse/WIN-7835)
Expand Down
84 changes: 81 additions & 3 deletions modules/statics/test/unit/tokenConfigTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,64 @@ describe('EthLike Token Config Functions', function () {
config.coin.should.equal('ip');
config.type.should.equal('ip:usdc');
});

it('should convert an EthLikeERC20Token to EthLikeTokenConfig for hypeevm mainnet', function () {
// Create a mock mainnet EthLikeERC20Token for hypeevm
const mockMainnetToken = new EthLikeERC20Token({
id: 'a1234567-1234-4234-8234-123456789012',
name: 'hypeevm:testtoken',
fullName: 'HypeEVM Test Token',
network: Networks.main.hypeevm,
contractAddress: '0x9876543210987654321098765432109876543210',
decimalPlaces: 18,
asset: UnderlyingAsset.HYPEEVM,
features: [...AccountCoin.DEFAULT_FEATURES, CoinFeature.EIP1559],
prefix: '',
suffix: 'TESTTOKEN',
primaryKeyCurve: KeyCurve.Secp256k1,
isToken: true,
baseUnit: BaseUnit.ETH,
});

const config = getFormattedEthLikeTokenConfig(CoinMap.fromCoins([mockMainnetToken]))[0];

config.should.not.be.undefined();
config.type.should.equal('hypeevm:testtoken');
config.coin.should.equal('hypeevm');
config.network.should.equal('Mainnet');
config.name.should.equal('HypeEVM Test Token');
config.tokenContractAddress.should.equal('0x9876543210987654321098765432109876543210');
config.decimalPlaces.should.equal(18);
});

it('should convert an EthLikeERC20Token to EthLikeTokenConfig for thypeevm testnet', function () {
// Create a mock testnet EthLikeERC20Token for thypeevm
const mockTestnetToken = new EthLikeERC20Token({
id: 'b2234567-2234-4234-9234-223456789012',
name: 'thypeevm:testtoken',
fullName: 'HypeEVM Test Token Testnet',
network: Networks.test.hypeevm,
contractAddress: '0xfedcba0987654321fedcba0987654321fedcba09',
decimalPlaces: 18,
asset: UnderlyingAsset.HYPEEVM,
features: [...AccountCoin.DEFAULT_FEATURES, CoinFeature.EIP1559],
prefix: '',
suffix: 'TESTTOKEN',
primaryKeyCurve: KeyCurve.Secp256k1,
isToken: true,
baseUnit: BaseUnit.ETH,
});

const config = getFormattedEthLikeTokenConfig(CoinMap.fromCoins([mockTestnetToken]))[0];

config.should.not.be.undefined();
config.type.should.equal('thypeevm:testtoken');
config.coin.should.equal('thypeevm');
config.network.should.equal('Testnet');
config.name.should.equal('HypeEVM Test Token Testnet');
config.tokenContractAddress.should.equal('0xfedcba0987654321fedcba0987654321fedcba09');
config.decimalPlaces.should.equal(18);
});
});

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

result.should.be.an.Object();
// The function filters by enabledChains which currently includes 'ip'
// The function filters by enabledChains which currently includes 'ip' and 'hypeevm'
if (result.ip) {
result.ip.should.have.property('tokens');
result.ip.tokens.should.be.an.Array();
}
if (result.hypeevm) {
result.hypeevm.should.have.property('tokens');
result.hypeevm.tokens.should.be.an.Array();
}
});

it('should filter mainnet tokens correctly', function () {
Expand Down Expand Up @@ -289,6 +351,11 @@ describe('EthLike Token Config Functions', function () {
token.coin.should.equal('tip');
});
}
if (result.hypeevm && result.hypeevm.tokens.length > 0) {
result.hypeevm.tokens.forEach((token) => {
token.coin.should.equal('thypeevm');
});
}
});

it('should not prepend "t" to coin name for mainnet tokens', function () {
Expand All @@ -299,6 +366,11 @@ describe('EthLike Token Config Functions', function () {
token.coin.should.equal('ip');
});
}
if (result.hypeevm && result.hypeevm.tokens.length > 0) {
result.hypeevm.tokens.forEach((token) => {
token.coin.should.equal('hypeevm');
});
}
});

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

// Current implementation only enables 'ip' chain
const enabledChains = ['ip'];
// Current implementation enables 'ip' and 'hypeevm' chains
const enabledChains = ['ip', 'hypeevm'];

Object.keys(mainnetResult).forEach((family) => {
enabledChains.should.containEql(family);
Expand Down Expand Up @@ -348,6 +420,12 @@ describe('EthLike Token Config Functions', function () {
token.coin.should.equal('ip');
});
}
if (result.hypeevm && result.hypeevm.tokens.length > 0) {
result.hypeevm.tokens.forEach((token) => {
// All tokens in hypeevm group should have coin 'hypeevm'
token.coin.should.equal('hypeevm');
});
}
});

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