diff --git a/modules/bitgo/src/v2/coinFactory.ts b/modules/bitgo/src/v2/coinFactory.ts index 8a0d1ff9fb..0057393436 100644 --- a/modules/bitgo/src/v2/coinFactory.ts +++ b/modules/bitgo/src/v2/coinFactory.ts @@ -215,6 +215,7 @@ import { World, WorldToken, Xdc, + XdcToken, Xlm, Xrp, XrpToken, @@ -556,6 +557,10 @@ export function registerCoinConstructors(coinFactory: CoinFactory, coinMap: Coin coinFactory.register(name, coinConstructor); }); + XdcToken.createTokenConstructors().forEach(({ name, coinConstructor }) => { + coinFactory.register(name, coinConstructor); + }); + // Generic ERC20 token registration for coins with SUPPORTS_ERC20 feature coins .filter((coin) => coin.features.includes(CoinFeature.SUPPORTS_ERC20) && !coin.isToken) @@ -1081,10 +1086,8 @@ export function getTokenConstructor(tokenConfig: TokenConfig): CoinConstructor | return EthLikeErc20Token.createTokenConstructor(tokenConfig as EthLikeTokenConfig, coinNames); } case 'xdc': - case 'txdc': { - const coinNames = { Mainnet: 'xdc', Testnet: 'txdc' }; - return EthLikeErc20Token.createTokenConstructor(tokenConfig as EthLikeTokenConfig, coinNames); - } + case 'txdc': + return XdcToken.createTokenConstructor(tokenConfig as EthLikeTokenConfig); default: return undefined; } diff --git a/modules/bitgo/src/v2/coins/index.ts b/modules/bitgo/src/v2/coins/index.ts index 2b187175cf..c51dc4b761 100644 --- a/modules/bitgo/src/v2/coins/index.ts +++ b/modules/bitgo/src/v2/coins/index.ts @@ -68,7 +68,7 @@ import { StellarToken, Txlm, Xlm } from '@bitgo/sdk-coin-xlm'; import { Vet, Tvet, VetToken } from '@bitgo/sdk-coin-vet'; import { Wemix, Twemix } from '@bitgo/sdk-coin-wemix'; import { World, Tworld, WorldToken } from '@bitgo/sdk-coin-world'; -import { Xdc, Txdc } from '@bitgo/sdk-coin-xdc'; +import { Xdc, Txdc, XdcToken } from '@bitgo/sdk-coin-xdc'; import { Txrp, Xrp, XrpToken } from '@bitgo/sdk-coin-xrp'; import { Txtz, Xtz } from '@bitgo/sdk-coin-xtz'; import { Tzec, Zec } from '@bitgo/sdk-coin-zec'; @@ -142,7 +142,7 @@ export { Injective, Tinjective }; export { Islm, Tislm }; export { Trx, Ttrx }; export { Vet, Tvet, VetToken }; -export { Xdc, Txdc }; +export { Xdc, Txdc, XdcToken }; export { StellarToken, Txlm, Xlm }; export { Txrp, Xrp, XrpToken }; export { Txtz, Xtz }; diff --git a/modules/bitgo/test/browser/browser.spec.ts b/modules/bitgo/test/browser/browser.spec.ts index dc89721eb4..e9becb2471 100644 --- a/modules/bitgo/test/browser/browser.spec.ts +++ b/modules/bitgo/test/browser/browser.spec.ts @@ -58,6 +58,7 @@ describe('Coins', () => { EthLikeErc721Token: 1, HashToken: 1, FlrToken: 1, + XdcToken: 1, JettonToken: 1, }; Object.keys(BitGoJS.Coin) diff --git a/modules/sdk-coin-xdc/package.json b/modules/sdk-coin-xdc/package.json index 120a3288b1..3fd5464cd9 100644 --- a/modules/sdk-coin-xdc/package.json +++ b/modules/sdk-coin-xdc/package.json @@ -43,7 +43,6 @@ "@bitgo/abstract-eth": "^24.19.4", "@bitgo/sdk-core": "^36.25.0", "@bitgo/statics": "^58.19.0", - "@bitgo/sdk-coin-evm": "^1.11.0", "@ethereumjs/common": "^2.6.5", "@ethereumjs/tx": "^3.3.0" }, diff --git a/modules/sdk-coin-xdc/src/index.ts b/modules/sdk-coin-xdc/src/index.ts index b6cc7ef4d0..d13de13ae3 100644 --- a/modules/sdk-coin-xdc/src/index.ts +++ b/modules/sdk-coin-xdc/src/index.ts @@ -2,3 +2,4 @@ export * from './lib'; export * from './xdc'; export * from './txdc'; export * from './register'; +export * from './xdcToken'; diff --git a/modules/sdk-coin-xdc/src/register.ts b/modules/sdk-coin-xdc/src/register.ts index c5c5de0bdc..4c7fbf2c6d 100644 --- a/modules/sdk-coin-xdc/src/register.ts +++ b/modules/sdk-coin-xdc/src/register.ts @@ -1,15 +1,12 @@ import { BitGoBase } from '@bitgo/sdk-core'; -import { EthLikeErc20Token } from '@bitgo/sdk-coin-evm'; import { Xdc } from './xdc'; import { Txdc } from './txdc'; +import { XdcToken } from './xdcToken'; export const register = (sdk: BitGoBase): void => { sdk.register('xdc', Xdc.createInstance); sdk.register('txdc', Txdc.createInstance); - EthLikeErc20Token.createTokenConstructors({ - Mainnet: 'xdc', - Testnet: 'txdc', - }).forEach(({ name, coinConstructor }) => { + XdcToken.createTokenConstructors().forEach(({ name, coinConstructor }) => { sdk.register(name, coinConstructor); }); }; diff --git a/modules/sdk-coin-xdc/src/xdcToken.ts b/modules/sdk-coin-xdc/src/xdcToken.ts new file mode 100644 index 0000000000..3e6d39c7df --- /dev/null +++ b/modules/sdk-coin-xdc/src/xdcToken.ts @@ -0,0 +1,55 @@ +/** + * @prettier + */ +import { EthLikeTokenConfig, coins } from '@bitgo/statics'; +import { BitGoBase, CoinConstructor, NamedCoinConstructor, common, MPCAlgorithm } from '@bitgo/sdk-core'; +import { CoinNames, EthLikeToken, recoveryBlockchainExplorerQuery } from '@bitgo/abstract-eth'; + +import { TransactionBuilder } from './lib'; +export { EthLikeTokenConfig }; + +export class XdcToken extends EthLikeToken { + public readonly tokenConfig: EthLikeTokenConfig; + static coinNames: CoinNames = { + Mainnet: 'xdc', + Testnet: 'txdc', + }; + constructor(bitgo: BitGoBase, tokenConfig: EthLikeTokenConfig) { + super(bitgo, tokenConfig, XdcToken.coinNames); + } + static createTokenConstructor(config: EthLikeTokenConfig): CoinConstructor { + return super.createTokenConstructor(config, XdcToken.coinNames); + } + + static createTokenConstructors(): NamedCoinConstructor[] { + return super.createTokenConstructors(XdcToken.coinNames); + } + + protected getTransactionBuilder(): TransactionBuilder { + return new TransactionBuilder(coins.get(this.getBaseChain())); + } + + /** + * Make a query to XDC Etherscan for information such as balance, token balance, solidity calls + * @param {Object} query key-value pairs of parameters to append after /api + * @returns {Promise} response from XDC Etherscan + */ + async recoveryBlockchainExplorerQuery(query: Record): Promise> { + const apiToken = common.Environments[this.bitgo.getEnv()].xdcExplorerApiToken; + const explorerUrl = common.Environments[this.bitgo.getEnv()].xdcExplorerBaseUrl; + return await recoveryBlockchainExplorerQuery(query, explorerUrl as string, apiToken); + } + + getFullName(): string { + return 'XDC Token'; + } + + supportsTss(): boolean { + return true; + } + + /** @inheritDoc */ + getMPCAlgorithm(): MPCAlgorithm { + return 'ecdsa'; + } +} diff --git a/modules/sdk-coin-xdc/test/unit/xdcToken.ts b/modules/sdk-coin-xdc/test/unit/xdcToken.ts index ec80f226fa..7bbd9883ad 100644 --- a/modules/sdk-coin-xdc/test/unit/xdcToken.ts +++ b/modules/sdk-coin-xdc/test/unit/xdcToken.ts @@ -19,7 +19,7 @@ describe('XDC Token:', function () { it('should return constants', function () { xdcTokenCoin.getChain().should.equal('xdc:usdc'); xdcTokenCoin.getBaseChain().should.equal('xdc'); - xdcTokenCoin.getFullName().should.equal('ERC20 Token'); + xdcTokenCoin.getFullName().should.equal('XDC Token'); xdcTokenCoin.getBaseFactor().should.equal(1e6); xdcTokenCoin.type.should.equal(tokenName); xdcTokenCoin.name.should.equal('USD Coin'); diff --git a/modules/statics/src/account.ts b/modules/statics/src/account.ts index e9e56fb374..c3d01dc402 100644 --- a/modules/statics/src/account.ts +++ b/modules/statics/src/account.ts @@ -530,6 +530,16 @@ export class FlrERC20Token extends ContractAddressDefinedToken { } } +/** + * The XDC network supports tokens + * XDC Tokens are ERC20 tokens + */ +export class XdcERC20Token extends ContractAddressDefinedToken { + constructor(options: Erc20ConstructorOptions) { + super(options); + } +} + /** * The Xrp network supports tokens * Xrp tokens are identified by their issuer address @@ -2856,6 +2866,96 @@ export function tflrErc20( ); } +/** + * Factory function for XdcErc20 token instances. + * + * @param id uuid v4 + * @param name unique identifier of the token + * @param fullName Complete human-readable name of the token + * @param decimalPlaces Number of decimal places this token supports (divisibility exponent) + * @param contractAddress Contract address of this token + * @param asset Asset which this coin represents. This is the same for both mainnet and testnet variants of a coin. + * @param prefix? Optional token prefix. Defaults to empty string + * @param suffix? Optional token suffix. Defaults to token name. + * @param network? Optional token network. Defaults to XDC mainnet network. + * @param features? Features of this coin. Defaults to the DEFAULT_FEATURES defined in `AccountCoin` + * @param primaryKeyCurve The elliptic curve for this chain/token + */ +export function xdcErc20( + id: string, + name: string, + fullName: string, + decimalPlaces: number, + contractAddress: string, + asset: UnderlyingAsset, + features: CoinFeature[] = [...AccountCoin.DEFAULT_FEATURES, CoinFeature.EIP1559], + prefix = '', + suffix: string = name.toUpperCase(), + network: AccountNetwork = Networks.main.xdc, + primaryKeyCurve: KeyCurve = KeyCurve.Secp256k1 +) { + return Object.freeze( + new XdcERC20Token({ + id, + name, + fullName, + network, + contractAddress, + prefix, + suffix, + features, + decimalPlaces, + asset, + isToken: true, + primaryKeyCurve, + baseUnit: BaseUnit.ETH, + }) + ); +} + +/** + * Factory function for XDC testnet XdcErc20 token instances. + * + * @param id uuid v4 + * @param name unique identifier of the token + * @param fullName Complete human-readable name of the token + * @param decimalPlaces Number of decimal places this token supports (divisibility exponent) + * @param contractAddress Contract address of this token + * @param asset Asset which this coin represents. This is the same for both mainnet and testnet variants of a coin. + * @param prefix? Optional token prefix. Defaults to empty string + * @param suffix? Optional token suffix. Defaults to token name. + * @param network? Optional token network. Defaults to the XDC test network. + * @param features? Features of this coin. Defaults to the DEFAULT_FEATURES defined in `AccountCoin` + * @param primaryKeyCurve The elliptic curve for this chain/token + */ +export function txdcErc20( + id: string, + name: string, + fullName: string, + decimalPlaces: number, + contractAddress: string, + asset: UnderlyingAsset, + features: CoinFeature[] = AccountCoin.DEFAULT_FEATURES, + prefix = '', + suffix: string = name.toUpperCase(), + network: AccountNetwork = Networks.test.xdc, + primaryKeyCurve: KeyCurve = KeyCurve.Secp256k1 +) { + return xdcErc20( + id, + name, + fullName, + decimalPlaces, + contractAddress, + asset, + features, + prefix, + suffix, + network, + primaryKeyCurve + ); +} + /** * Factory function for xrp token instances. * diff --git a/modules/statics/src/allCoinsAndTokens.ts b/modules/statics/src/allCoinsAndTokens.ts index 70f743f24b..659bc70d7e 100644 --- a/modules/statics/src/allCoinsAndTokens.ts +++ b/modules/statics/src/allCoinsAndTokens.ts @@ -16,6 +16,7 @@ import { erc721Token, fiat, flrErc20, + xdcErc20, gasTankAccount, hederaCoin, hederaToken, @@ -34,6 +35,7 @@ import { terc1155, terc721, tflrErc20, + txdcErc20, topethErc20, tronToken, tstellarToken, @@ -2980,61 +2982,55 @@ export const allCoinsAndTokens = [ ), // XDC mainnet tokens - erc20Token( + xdcErc20( 'b820932d-5772-49ae-a055-a59760f3e4cf', 'xdc:usdc', 'USD Coin', 6, '0xfa2958cb79b0491cc627c1557f441ef849ca8eb1', - UnderlyingAsset['xdc:usdc'], - Networks.main.xdc + UnderlyingAsset['xdc:usdc'] ), - erc20Token( + xdcErc20( '8914a1bd-1495-46df-84da-445c6d49edb2', 'xdc:lbt', 'Law Block Token', 18, '0x05940b2df33d6371201e7ae099ced4c363855dfe', - UnderlyingAsset['xdc:lbt'], - Networks.main.xdc + UnderlyingAsset['xdc:lbt'] ), - erc20Token( + xdcErc20( 'f03302de-b06b-4ddc-94a2-ad7e89896725', 'xdc:gama', 'Gama Token', 18, '0x3a170c7c987f55c84f28733bfa27962d8cdd5d3b', - UnderlyingAsset['xdc:gama'], - Networks.main.xdc + UnderlyingAsset['xdc:gama'] ), - erc20Token( + xdcErc20( 'bdf602ea-3a6c-407a-8afd-33d6c04a8bc3', 'xdc:srx', 'STORX', 18, '0x5d5f074837f5d4618b3916ba74de1bf9662a3fed', - UnderlyingAsset['xdc:srx'], - Networks.main.xdc + UnderlyingAsset['xdc:srx'] ), - erc20Token( + xdcErc20( 'd42c9497-0987-497d-97f2-8b19c539e350', 'xdc:weth', 'Wrapped Ether', 18, '0xa7348290de5cf01772479c48d50dec791c3fc212', - UnderlyingAsset['xdc:weth'], - Networks.main.xdc + UnderlyingAsset['xdc:weth'] ), // XDC testnet tokens - erc20Token( + txdcErc20( '5fc561e7-5fb5-4702-9860-9f48d90f1d45', 'txdc:tmt', 'XDC Network TMT', 6, '0xb283ec8dad644effc5c4c50bb7bb21442ac3c2db', - UnderlyingAsset['txdc:tmt'], - Networks.test.xdc + UnderlyingAsset['txdc:tmt'] ), // MON mainnet tokens diff --git a/modules/statics/src/coinFeatures.ts b/modules/statics/src/coinFeatures.ts index f1f5950aef..9d58181847 100644 --- a/modules/statics/src/coinFeatures.ts +++ b/modules/statics/src/coinFeatures.ts @@ -692,11 +692,7 @@ export const VET_TOKEN_FEATURES = VET_FEATURES.filter((feature) => feature !== C export const EVM_NON_EIP1559_FEATURES = [...EVM_FEATURES.filter((feature) => feature !== CoinFeature.EIP1559)]; -export const XDC_FEATURES = [ - ...EVM_NON_EIP1559_FEATURES, - CoinFeature.ERC20_BULK_TRANSACTION, - CoinFeature.SUPPORTS_ERC20, -]; +export const XDC_FEATURES = [...EVM_NON_EIP1559_FEATURES, CoinFeature.ERC20_BULK_TRANSACTION]; export const SGB_FEATURES = [...EVM_FEATURES, CoinFeature.ERC20_BULK_TRANSACTION]; diff --git a/modules/statics/src/tokenConfig.ts b/modules/statics/src/tokenConfig.ts index b0209da608..2c69a9e3c8 100644 --- a/modules/statics/src/tokenConfig.ts +++ b/modules/statics/src/tokenConfig.ts @@ -16,6 +16,7 @@ import { EthLikeERC20Token, EthLikeERC721Token, FlrERC20Token, + XdcERC20Token, HederaToken, Nep141Token, OpethERC20Token, @@ -612,25 +613,6 @@ const getFormattedMonadTokens = (customCoinMap = coins) => return acc; }, []); -function getXdcTokenConfig(coin: EthLikeERC20Token): EthLikeTokenConfig { - return { - type: coin.name, - coin: coin.network.type === NetworkType.MAINNET ? 'xdc' : 'txdc', - network: coin.network.type === NetworkType.MAINNET ? 'Mainnet' : 'Testnet', - name: coin.fullName, - tokenContractAddress: coin.contractAddress.toString().toLowerCase(), - decimalPlaces: coin.decimalPlaces, - }; -} - -const getFormattedXdcTokens = (customCoinMap = coins) => - customCoinMap.reduce((acc: EthLikeTokenConfig[], coin) => { - if (coin instanceof EthLikeERC20Token && (coin.name.includes('xdc:') || coin.name.includes('txdc:'))) { - acc.push(getXdcTokenConfig(coin)); - } - return acc; - }, []); - function getFlowTokenConfig(coin: EthLikeERC20Token): EthLikeTokenConfig { return { type: coin.name, @@ -757,6 +739,24 @@ const getFormattedFlrTokens = (customCoinMap = coins) => return acc; }, []); +function getXdcTokenConfig(coin: XdcERC20Token): EthLikeTokenConfig { + return { + type: coin.name, + coin: coin.network.type === NetworkType.MAINNET ? 'xdc' : 'txdc', + network: coin.network.type === NetworkType.MAINNET ? 'Mainnet' : 'Testnet', + name: coin.fullName, + tokenContractAddress: coin.contractAddress.toString().toLowerCase(), + decimalPlaces: coin.decimalPlaces, + }; +} +const getFormattedXdcTokens = (customCoinMap = coins) => + customCoinMap.reduce((acc: EthLikeTokenConfig[], coin) => { + if (coin instanceof XdcERC20Token) { + acc.push(getXdcTokenConfig(coin)); + } + return acc; + }, []); + function getSolTokenConfig(coin: SolCoin): SolTokenConfig { return { type: coin.name, @@ -1467,6 +1467,8 @@ export function getFormattedTokenConfigForCoin(coin: Readonly): TokenC return getJettonTokenConfig(coin); } else if (coin instanceof FlrERC20Token) { return getFlrTokenConfig(coin); + } else if (coin instanceof XdcERC20Token) { + return getXdcTokenConfig(coin); } else if (coin instanceof EthLikeERC20Token) { return getEthLikeTokenConfig(coin); } else if (coin instanceof EthLikeERC721Token) {