Skip to content

Commit dfdff66

Browse files
feat: mon token support
Ticket: WIN-8562
1 parent 4379062 commit dfdff66

11 files changed

Lines changed: 308 additions & 24 deletions

File tree

modules/bitgo/src/v2/coinFactory.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ import {
8888
Flrp,
8989
FlrToken,
9090
HashToken,
91+
MonToken,
9192
TethLikeCoin,
9293
FiatAED,
9394
FiatEur,
@@ -557,6 +558,10 @@ export function registerCoinConstructors(coinFactory: CoinFactory, coinMap: Coin
557558
coinFactory.register(name, coinConstructor);
558559
});
559560

561+
MonToken.createTokenConstructors().forEach(({ name, coinConstructor }) => {
562+
coinFactory.register(name, coinConstructor);
563+
});
564+
560565
XdcToken.createTokenConstructors().forEach(({ name, coinConstructor }) => {
561566
coinFactory.register(name, coinConstructor);
562567
});
@@ -1081,10 +1086,8 @@ export function getTokenConstructor(tokenConfig: TokenConfig): CoinConstructor |
10811086
case 'tton':
10821087
return JettonToken.createTokenConstructor(tokenConfig as JettonTokenConfig);
10831088
case 'mon':
1084-
case 'tmon': {
1085-
const coinNames = { Mainnet: 'mon', Testnet: 'tmon' };
1086-
return EthLikeErc20Token.createTokenConstructor(tokenConfig as EthLikeTokenConfig, coinNames);
1087-
}
1089+
case 'tmon':
1090+
return MonToken.createTokenConstructor(tokenConfig as EthLikeTokenConfig);
10881091
case 'xdc':
10891092
case 'txdc':
10901093
return XdcToken.createTokenConstructor(tokenConfig as EthLikeTokenConfig);

modules/bitgo/src/v2/coins/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import { Iota } from '@bitgo/sdk-coin-iota';
4545
import { Islm, Tislm } from '@bitgo/sdk-coin-islm';
4646
import { Lnbtc, Tlnbtc } from '@bitgo/sdk-coin-lnbtc';
4747
import { Ltc, Tltc } from '@bitgo/sdk-coin-ltc';
48-
import { Mon, Tmon } from '@bitgo/sdk-coin-mon';
48+
import { Mon, Tmon, MonToken } from '@bitgo/sdk-coin-mon';
4949
import { Oas, Toas } from '@bitgo/sdk-coin-oas';
5050
import { Opeth, Topeth, OpethToken } from '@bitgo/sdk-coin-opeth';
5151
import { Osmo, Tosmo } from '@bitgo/sdk-coin-osmo';
@@ -119,7 +119,7 @@ export { Initia, Tinitia };
119119
export { Iota };
120120
export { Lnbtc, Tlnbtc };
121121
export { Ltc, Tltc };
122-
export { Mon, Tmon };
122+
export { Mon, Tmon, MonToken };
123123
export { Oas, Toas };
124124
export { Opeth, Topeth, OpethToken };
125125
export { Osmo, Tosmo };

modules/bitgo/test/browser/browser.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ describe('Coins', () => {
5858
EthLikeErc721Token: 1,
5959
HashToken: 1,
6060
FlrToken: 1,
61+
MonToken: 1,
6162
XdcToken: 1,
6263
JettonToken: 1,
6364
};

modules/sdk-coin-mon/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './lib';
22
export * from './mon';
33
export * from './tmon';
44
export * from './register';
5+
export * from './monToken';
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { coins, EthLikeTokenConfig } from '@bitgo/statics';
2+
import { BitGoBase, CoinConstructor, common, MPCAlgorithm, NamedCoinConstructor } from '@bitgo/sdk-core';
3+
import { CoinNames, EthLikeToken, recoveryBlockchainExplorerQuery } from '@bitgo/abstract-eth';
4+
5+
import { TransactionBuilder } from './lib';
6+
7+
export { EthLikeTokenConfig };
8+
9+
export class MonToken extends EthLikeToken {
10+
public readonly tokenConfig: EthLikeTokenConfig;
11+
static coinNames: CoinNames = {
12+
Mainnet: 'mon',
13+
Testnet: 'tmon',
14+
};
15+
constructor(bitgo: BitGoBase, tokenConfig: EthLikeTokenConfig) {
16+
super(bitgo, tokenConfig, MonToken.coinNames);
17+
}
18+
static createTokenConstructor(config: EthLikeTokenConfig): CoinConstructor {
19+
return super.createTokenConstructor(config, MonToken.coinNames);
20+
}
21+
22+
static createTokenConstructors(): NamedCoinConstructor[] {
23+
return super.createTokenConstructors(MonToken.coinNames);
24+
}
25+
26+
protected getTransactionBuilder(): TransactionBuilder {
27+
return new TransactionBuilder(coins.get(this.getBaseChain()));
28+
}
29+
30+
/** @inheritDoc **/
31+
getMPCAlgorithm(): MPCAlgorithm {
32+
return 'ecdsa';
33+
}
34+
35+
/** @inheritDoc */
36+
supportsTss(): boolean {
37+
return true;
38+
}
39+
40+
/**
41+
* Make a query to Mon explorer for information such as balance, token balance, solidity calls
42+
* @param {Object} query key-value pairs of parameters to append after /api
43+
* @param {string} apiKey optional API key to use instead of the one from the environment
44+
* @returns {Promise<Object>} response from Mon explorer
45+
*/
46+
async recoveryBlockchainExplorerQuery(
47+
query: Record<string, string>,
48+
apiKey?: string
49+
): Promise<Record<string, unknown>> {
50+
const apiToken = apiKey || common.Environments[this.bitgo.getEnv()].monExplorerApiToken;
51+
const explorerUrl = common.Environments[this.bitgo.getEnv()].monExplorerBaseUrl;
52+
return await recoveryBlockchainExplorerQuery(query, explorerUrl as string, apiToken);
53+
}
54+
55+
getFullName(): string {
56+
return 'Mon Token';
57+
}
58+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import { BitGoBase } from '@bitgo/sdk-core';
22
import { Mon } from './mon';
33
import { Tmon } from './tmon';
4+
import { MonToken } from './monToken';
45

56
export const register = (sdk: BitGoBase): void => {
67
sdk.register('mon', Mon.createInstance);
78
sdk.register('tmon', Tmon.createInstance);
9+
MonToken.createTokenConstructors().forEach(({ name, coinConstructor }) => {
10+
sdk.register(name, coinConstructor);
11+
});
812
};
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import 'should';
2+
import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test';
3+
import { BitGoAPI } from '@bitgo/sdk-api';
4+
5+
import { register, MonToken } from '../../src';
6+
7+
describe('Mon Token:', function () {
8+
let bitgo: TestBitGoAPI;
9+
let monTokenCoin;
10+
const tokenName = 'mon:usdc';
11+
12+
before(function () {
13+
bitgo = TestBitGo.decorate(BitGoAPI, { env: 'prod' });
14+
register(bitgo);
15+
bitgo.initializeTestVars();
16+
monTokenCoin = bitgo.coin(tokenName);
17+
});
18+
19+
it('should return constants', function () {
20+
monTokenCoin.getChain().should.equal('mon:usdc');
21+
monTokenCoin.getBaseChain().should.equal('mon');
22+
monTokenCoin.getFullName().should.equal('Mon Token');
23+
monTokenCoin.getBaseFactor().should.equal(1e6);
24+
monTokenCoin.type.should.equal(tokenName);
25+
monTokenCoin.name.should.equal('Monad USDC');
26+
monTokenCoin.coin.should.equal('mon');
27+
monTokenCoin.network.should.equal('Mainnet');
28+
monTokenCoin.decimalPlaces.should.equal(6);
29+
});
30+
31+
describe('Token Registration and TransactionBuilder', function () {
32+
const mainnetTokens = ['mon:usdc', 'mon:wmon'];
33+
34+
describe('Mainnet tokens', function () {
35+
mainnetTokens.forEach((tokenName) => {
36+
it(`${tokenName} should be registered as MonToken`, function () {
37+
const token = bitgo.coin(tokenName);
38+
token.should.be.instanceOf(MonToken);
39+
});
40+
41+
it(`${tokenName} should create TransactionBuilder without error`, function () {
42+
const token = bitgo.coin(tokenName) as MonToken;
43+
// @ts-expect-error - accessing protected method for testing
44+
(() => token.getTransactionBuilder()).should.not.throw();
45+
});
46+
47+
it(`${tokenName} should use Mon-specific TransactionBuilder`, function () {
48+
const token = bitgo.coin(tokenName) as MonToken;
49+
// @ts-expect-error - accessing protected method for testing
50+
const builder = token.getTransactionBuilder();
51+
builder.should.have.property('_common');
52+
builder.constructor.name.should.equal('TransactionBuilder');
53+
});
54+
55+
it(`${tokenName} should not throw "Cannot use common sdk module" error`, function () {
56+
const token = bitgo.coin(tokenName) as MonToken;
57+
let errorThrown = false;
58+
let errorMessage = '';
59+
60+
try {
61+
// @ts-expect-error - accessing protected method for testing
62+
const builder = token.getTransactionBuilder();
63+
// Try to use the builder to ensure it's fully functional
64+
// @ts-expect-error - type expects TransactionType enum
65+
builder.type('Send');
66+
} catch (e) {
67+
errorThrown = true;
68+
errorMessage = (e as Error).message;
69+
}
70+
71+
errorThrown.should.equal(false);
72+
errorMessage.should.not.match(/Cannot use common sdk module/);
73+
});
74+
75+
it(`${tokenName} should build transaction successfully`, async function () {
76+
const token = bitgo.coin(tokenName) as MonToken;
77+
// @ts-expect-error - accessing protected method for testing
78+
const builder = token.getTransactionBuilder();
79+
80+
// Set up a basic transfer transaction
81+
// @ts-expect-error - type expects TransactionType enum
82+
builder.type('Send');
83+
builder.fee({
84+
fee: '10000000000',
85+
gasLimit: '100000',
86+
});
87+
builder.counter(1);
88+
builder.contract(token.tokenContractAddress);
89+
90+
// Verify the builder is correctly configured
91+
builder.should.have.property('_type', 'Send');
92+
builder.should.have.property('_fee');
93+
builder.should.have.property('_counter', 1);
94+
});
95+
});
96+
});
97+
98+
it('should verify all Mon tokens use MonToken class, not EthLikeErc20Token', function () {
99+
mainnetTokens.forEach((tokenName) => {
100+
const token = bitgo.coin(tokenName);
101+
token.should.be.instanceOf(MonToken);
102+
token.constructor.name.should.equal('MonToken');
103+
token.constructor.name.should.not.equal('EthLikeErc20Token');
104+
});
105+
});
106+
});
107+
});

modules/statics/src/account.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,16 @@ export class XdcERC20Token extends ContractAddressDefinedToken {
540540
}
541541
}
542542

543+
/**
544+
* The Mon network supports tokens
545+
* Mon Tokens are ERC20 tokens
546+
*/
547+
export class MonERC20Token extends ContractAddressDefinedToken {
548+
constructor(options: Erc20ConstructorOptions) {
549+
super(options);
550+
}
551+
}
552+
543553
/**
544554
* The Xrp network supports tokens
545555
* Xrp tokens are identified by their issuer address
@@ -2956,6 +2966,96 @@ export function txdcErc20(
29562966
);
29572967
}
29582968

2969+
/**
2970+
* Factory function for MonErc20 token instances.
2971+
*
2972+
* @param id uuid v4
2973+
* @param name unique identifier of the token
2974+
* @param fullName Complete human-readable name of the token
2975+
* @param decimalPlaces Number of decimal places this token supports (divisibility exponent)
2976+
* @param contractAddress Contract address of this token
2977+
* @param asset Asset which this coin represents. This is the same for both mainnet and testnet variants of a coin.
2978+
* @param prefix? Optional token prefix. Defaults to empty string
2979+
* @param suffix? Optional token suffix. Defaults to token name.
2980+
* @param network? Optional token network. Defaults to Mon mainnet network.
2981+
* @param features? Features of this coin. Defaults to the DEFAULT_FEATURES defined in `AccountCoin`
2982+
* @param primaryKeyCurve The elliptic curve for this chain/token
2983+
*/
2984+
export function monErc20(
2985+
id: string,
2986+
name: string,
2987+
fullName: string,
2988+
decimalPlaces: number,
2989+
contractAddress: string,
2990+
asset: UnderlyingAsset,
2991+
features: CoinFeature[] = [...AccountCoin.DEFAULT_FEATURES, CoinFeature.EIP1559],
2992+
prefix = '',
2993+
suffix: string = name.toUpperCase(),
2994+
network: AccountNetwork = Networks.main.mon,
2995+
primaryKeyCurve: KeyCurve = KeyCurve.Secp256k1
2996+
) {
2997+
return Object.freeze(
2998+
new MonERC20Token({
2999+
id,
3000+
name,
3001+
fullName,
3002+
network,
3003+
contractAddress,
3004+
prefix,
3005+
suffix,
3006+
features,
3007+
decimalPlaces,
3008+
asset,
3009+
isToken: true,
3010+
primaryKeyCurve,
3011+
baseUnit: BaseUnit.ETH,
3012+
})
3013+
);
3014+
}
3015+
3016+
/**
3017+
* Factory function for Mon testnet MonErc20 token instances.
3018+
*
3019+
* @param id uuid v4
3020+
* @param name unique identifier of the token
3021+
* @param fullName Complete human-readable name of the token
3022+
* @param decimalPlaces Number of decimal places this token supports (divisibility exponent)
3023+
* @param contractAddress Contract address of this token
3024+
* @param asset Asset which this coin represents. This is the same for both mainnet and testnet variants of a coin.
3025+
* @param prefix? Optional token prefix. Defaults to empty string
3026+
* @param suffix? Optional token suffix. Defaults to token name.
3027+
* @param network? Optional token network. Defaults to the Mon test network.
3028+
* @param features? Features of this coin. Defaults to the DEFAULT_FEATURES defined in `AccountCoin`
3029+
* @param primaryKeyCurve The elliptic curve for this chain/token
3030+
*/
3031+
export function tmonErc20(
3032+
id: string,
3033+
name: string,
3034+
fullName: string,
3035+
decimalPlaces: number,
3036+
contractAddress: string,
3037+
asset: UnderlyingAsset,
3038+
features: CoinFeature[] = AccountCoin.DEFAULT_FEATURES,
3039+
prefix = '',
3040+
suffix: string = name.toUpperCase(),
3041+
network: AccountNetwork = Networks.test.mon,
3042+
primaryKeyCurve: KeyCurve = KeyCurve.Secp256k1
3043+
) {
3044+
return monErc20(
3045+
id,
3046+
name,
3047+
fullName,
3048+
decimalPlaces,
3049+
contractAddress,
3050+
asset,
3051+
features,
3052+
prefix,
3053+
suffix,
3054+
network,
3055+
primaryKeyCurve
3056+
);
3057+
}
3058+
29593059
/**
29603060
* Factory function for xrp token instances.
29613061
*

0 commit comments

Comments
 (0)