Skip to content

Commit e7a5a20

Browse files
Merge pull request #7795 from BitGo/WIN-8435
fix: fix config for ofc erc20 tokens
2 parents 8bdea89 + 4969a36 commit e7a5a20

File tree

3 files changed

+495
-27
lines changed

3 files changed

+495
-27
lines changed
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
import 'should';
2+
import { TestBitGo } from '@bitgo/sdk-test';
3+
import { BitGo } from '../../../../src/bitgo';
4+
import { coins, CoinFeature } from '@bitgo/statics';
5+
6+
describe('OFC ERC20 Tokens Configuration:', function () {
7+
let bitgo;
8+
9+
before(function () {
10+
bitgo = TestBitGo.decorate(BitGo, { env: 'test' });
11+
bitgo.initializeTestVars();
12+
});
13+
14+
describe('validate addressCoin configuration for all OFC ERC20 tokens', function () {
15+
it('should have addressCoin matching the first part of underlying asset', function () {
16+
// Get all OFC ERC20 tokens (ofcerc20 and tofcerc20 instances)
17+
// These are identified by having an addressCoin property
18+
const ofcCoins = coins.filter((coin: any) => coin.family === 'ofc' && coin.addressCoin);
19+
20+
const misconfigurations: string[] = [];
21+
22+
ofcCoins.forEach((ofcCoin: any) => {
23+
// Get the underlying asset
24+
const asset = ofcCoin.asset;
25+
26+
// Skip if no addressCoin property (not all OFC coins have it)
27+
if (!ofcCoin.addressCoin) {
28+
return;
29+
}
30+
31+
// Skip testnet tokens - they use testnet-specific addressCoins
32+
// (e.g., gteth, hteth, tsol, tavaxc, tpolygon, tarbeth)
33+
if (ofcCoin.network.type === 'testnet') {
34+
return;
35+
}
36+
37+
// Determine expected addressCoin from the asset (mainnet only)
38+
let expectedAddressCoin;
39+
40+
if (asset.includes(':')) {
41+
// For assets like 'baseeth:spec', 'xdc:usdc', 'mon:usdc', etc.
42+
// The addressCoin should be the part before the colon
43+
expectedAddressCoin = asset.split(':')[0];
44+
} else if (ofcCoin.name.includes(':')) {
45+
// For tokens with ':' in the name like 'ofcbaseeth:spec'
46+
// Extract the chain from the name
47+
const nameParts = ofcCoin.name.replace(/^ofc/, '').split(':');
48+
if (nameParts.length > 1) {
49+
expectedAddressCoin = nameParts[0];
50+
} else {
51+
expectedAddressCoin = 'eth'; // Default to eth for standard ERC20 tokens
52+
}
53+
} else {
54+
// For standard tokens without ':' in asset (e.g., 'USDC', 'LINK')
55+
expectedAddressCoin = 'eth';
56+
}
57+
58+
// Check addressCoin matches expected value
59+
if (ofcCoin.addressCoin !== expectedAddressCoin) {
60+
misconfigurations.push(
61+
`Token ${ofcCoin.name} with asset ${asset} should have addressCoin='${expectedAddressCoin}' but has '${ofcCoin.addressCoin}'`
62+
);
63+
}
64+
});
65+
66+
// Report all misconfigurations at once
67+
if (misconfigurations.length > 0) {
68+
throw new Error(
69+
`Found ${misconfigurations.length} addressCoin misconfigurations:\n` + misconfigurations.join('\n')
70+
);
71+
}
72+
});
73+
74+
it('should validate specific chain-specific tokens', function () {
75+
// Test specific tokens by chain
76+
const testCases = [
77+
// XDC Network tokens
78+
{ token: 'ofcxdc:usdc', addressCoin: 'xdc', chain: 'XDC' },
79+
{ token: 'ofcxdc:lbt', addressCoin: 'xdc', chain: 'XDC' },
80+
{ token: 'ofcxdc:gama', addressCoin: 'xdc', chain: 'XDC' },
81+
{ token: 'ofcxdc:srx', addressCoin: 'xdc', chain: 'XDC' },
82+
{ token: 'ofcxdc:weth', addressCoin: 'xdc', chain: 'XDC' },
83+
// Base Ethereum tokens
84+
{ token: 'ofcbaseeth:spec', addressCoin: 'baseeth', chain: 'Base' },
85+
{ token: 'ofcbaseeth:soon', addressCoin: 'baseeth', chain: 'Base' },
86+
{ token: 'ofcbaseeth:wave', addressCoin: 'baseeth', chain: 'Base' },
87+
{ token: 'ofcbaseeth:tig', addressCoin: 'baseeth', chain: 'Base' },
88+
{ token: 'ofcbaseeth:virtual', addressCoin: 'baseeth', chain: 'Base' },
89+
{ token: 'ofcbaseeth:zora', addressCoin: 'baseeth', chain: 'Base' },
90+
{ token: 'ofcbaseeth:toshi', addressCoin: 'baseeth', chain: 'Base' },
91+
{ token: 'ofcbaseeth:creator', addressCoin: 'baseeth', chain: 'Base' },
92+
{ token: 'ofcbaseeth:avnt', addressCoin: 'baseeth', chain: 'Base' },
93+
{ token: 'ofcbaseeth:mira', addressCoin: 'baseeth', chain: 'Base' },
94+
{ token: 'ofcbaseeth:towns', addressCoin: 'baseeth', chain: 'Base' },
95+
{ token: 'ofcbaseeth:recall', addressCoin: 'baseeth', chain: 'Base' },
96+
{ token: 'ofcbaseeth:brlv', addressCoin: 'baseeth', chain: 'Base' },
97+
{ token: 'ofcbaseeth:wbrly', addressCoin: 'baseeth', chain: 'Base' },
98+
{ token: 'ofcbaseeth:sapien', addressCoin: 'baseeth', chain: 'Base' },
99+
{ token: 'ofcbaseeth:aixbt', addressCoin: 'baseeth', chain: 'Base' },
100+
{ token: 'ofcbaseeth:brett', addressCoin: 'baseeth', chain: 'Base' },
101+
// MON Network tokens
102+
{ token: 'ofcmon:usdc', addressCoin: 'mon', chain: 'MON' },
103+
{ token: 'ofcmon:wmon', addressCoin: 'mon', chain: 'MON' },
104+
// HYPE Network token
105+
{ token: 'ofchype:hwhype', addressCoin: 'hype', chain: 'HYPE' },
106+
// IP (Story Network) token
107+
{ token: 'ofcip:aria', addressCoin: 'ip', chain: 'Story' },
108+
];
109+
110+
const errors: string[] = [];
111+
112+
testCases.forEach(({ token, addressCoin, chain }) => {
113+
const ofcCoin: any = coins.get(token);
114+
if (!ofcCoin) {
115+
errors.push(`${chain} token ${token} not found in statics`);
116+
} else if (ofcCoin.addressCoin !== addressCoin) {
117+
errors.push(
118+
`${chain} token ${token} should have addressCoin='${addressCoin}' but has '${ofcCoin.addressCoin}'`
119+
);
120+
}
121+
});
122+
123+
if (errors.length > 0) {
124+
throw new Error(`Found ${errors.length} configuration errors:\n` + errors.join('\n'));
125+
}
126+
});
127+
128+
it('should validate all tokens have addressCoin property', function () {
129+
// Get all OFC ERC20 tokens (ofcerc20 and tofcerc20 instances)
130+
// These should all have an addressCoin property
131+
const ofcErc20Tokens = coins.filter(
132+
(coin: any) =>
133+
coin.family === 'ofc' && coin.isToken === true && (coin.name.includes(':') || coin.asset.includes(':'))
134+
);
135+
136+
const tokensWithoutAddressCoin: string[] = [];
137+
138+
ofcErc20Tokens.forEach((ofcCoin: any) => {
139+
if (!ofcCoin.addressCoin) {
140+
tokensWithoutAddressCoin.push(`${ofcCoin.name} (asset: ${ofcCoin.asset})`);
141+
}
142+
});
143+
144+
// Report tokens without addressCoin (informational only, not a failure)
145+
tokensWithoutAddressCoin.length.should.be.greaterThanOrEqual(0);
146+
});
147+
});
148+
149+
describe('validate required custody features for all OFC ERC20 tokens', function () {
150+
it('should have required custody features for ofcerc20 and tofcerc20 tokens', function () {
151+
const requiredFeatures = [
152+
CoinFeature.ACCOUNT_MODEL,
153+
CoinFeature.REQUIRES_BIG_NUMBER,
154+
CoinFeature.CUSTODY,
155+
CoinFeature.CUSTODY_BITGO_TRUST,
156+
];
157+
158+
// Get all OFC ERC20 tokens (ofcerc20 and tofcerc20 instances)
159+
// These are identified by having an addressCoin property
160+
const ofcCoins = coins.filter((coin: any) => coin.family === 'ofc' && coin.addressCoin);
161+
162+
const missingFeatures: string[] = [];
163+
164+
ofcCoins.forEach((ofcCoin) => {
165+
requiredFeatures.forEach((feature) => {
166+
if (!ofcCoin.features.includes(feature)) {
167+
missingFeatures.push(`Token ${ofcCoin.name} is missing feature: ${feature}`);
168+
}
169+
});
170+
});
171+
172+
if (missingFeatures.length > 0) {
173+
throw new Error(`Found ${missingFeatures.length} missing features:\n` + missingFeatures.join('\n'));
174+
}
175+
});
176+
});
177+
178+
describe('validate address validation for chain-specific tokens', function () {
179+
it('should validate bg- format addresses for all OFC tokens', function () {
180+
// Get sample OFC tokens from different chains
181+
const testTokens = [
182+
'ofcxdc:usdc',
183+
'ofcbaseeth:spec',
184+
'ofcmon:usdc',
185+
'ofchype:hwhype',
186+
'ofcip:aria',
187+
'ofceth',
188+
'ofcbtc',
189+
];
190+
191+
const errors: string[] = [];
192+
193+
testTokens.forEach((tokenName) => {
194+
const ofcCoin = bitgo.coin(tokenName);
195+
if (ofcCoin) {
196+
const validBgAddress = 'bg-5b2b80eafbdf94d5030bb23f9b56ad64';
197+
const invalidBgAddress = 'bg-5b2b80eafbdf94d5030bb23f9b56ad64nnn';
198+
199+
if (!ofcCoin.isValidAddress(validBgAddress)) {
200+
errors.push(`${tokenName} should accept valid bg- address format`);
201+
}
202+
203+
if (ofcCoin.isValidAddress(invalidBgAddress)) {
204+
errors.push(`${tokenName} should reject invalid bg- address format`);
205+
}
206+
}
207+
});
208+
209+
if (errors.length > 0) {
210+
throw new Error(`Found ${errors.length} address validation errors:\n` + errors.join('\n'));
211+
}
212+
});
213+
});
214+
215+
describe('validate all OFC tokens are properly registered', function () {
216+
it('should be able to instantiate all OFC tokens', function () {
217+
const ofcCoins = coins.filter((coin) => coin.family === 'ofc');
218+
const errors: string[] = [];
219+
220+
ofcCoins.forEach((ofcCoin) => {
221+
try {
222+
const coin = bitgo.coin(ofcCoin.name);
223+
if (!coin) {
224+
errors.push(`Failed to instantiate ${ofcCoin.name}`);
225+
}
226+
} catch (e) {
227+
errors.push(`Error instantiating ${ofcCoin.name}: ${e.message}`);
228+
}
229+
});
230+
231+
if (errors.length > 0) {
232+
throw new Error(`Found ${errors.length} instantiation errors:\n` + errors.join('\n'));
233+
}
234+
});
235+
});
236+
});

modules/statics/src/base.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2918,7 +2918,7 @@ export enum UnderlyingAsset {
29182918
'baseeth:wbrly' = 'baseeth:wbrly',
29192919
'baseeth:recall' = 'baseeth:recall',
29202920
'baseeth:sapien' = 'baseeth:sapien',
2921-
'baseeth:aixbt' = 'baseeht:aixbt',
2921+
'baseeth:aixbt' = 'baseeth:aixbt',
29222922
'baseeth:brett' = 'baseeth:brett',
29232923

29242924
// BaseETH testnet tokens

0 commit comments

Comments
 (0)