From 5d7c97c5ce9da294f21089449dc8bb62e13ec2c5 Mon Sep 17 00:00:00 2001 From: Daniel Liu Date: Tue, 21 Mar 2023 15:12:05 +0800 Subject: [PATCH] refactor: support xdc chain --- src/chains/xdc/xdc.ts | 85 +++++++++++++++++++++++ src/chains/xdc/xdc.validators.ts | 36 ++++++++++ src/chains/xdc/xdc_tokens_apothem.json | 47 +++++++++++++ src/network/network.controllers.ts | 10 +++ src/services/connection-manager.ts | 2 + src/services/wallet/wallet.controllers.ts | 3 + src/services/wallet/wallet.validators.ts | 6 ++ src/templates/root.yml | 4 ++ src/templates/xdc.yml | 17 +++++ 9 files changed, 210 insertions(+) create mode 100644 src/chains/xdc/xdc.ts create mode 100644 src/chains/xdc/xdc.validators.ts create mode 100644 src/chains/xdc/xdc_tokens_apothem.json create mode 100644 src/templates/xdc.yml diff --git a/src/chains/xdc/xdc.ts b/src/chains/xdc/xdc.ts new file mode 100644 index 0000000000..bd4234e16b --- /dev/null +++ b/src/chains/xdc/xdc.ts @@ -0,0 +1,85 @@ +import abi from '../ethereum/ethereum.abi.json'; +import { logger } from '../../services/logger'; +import { Contract, Transaction, Wallet } from 'ethers'; +import { EthereumBase } from '../ethereum/ethereum-base'; +import { getEthereumConfig as getPolygonConfig } from '../ethereum/ethereum.config'; +import { Provider } from '@ethersproject/abstract-provider'; +import { UniswapConfig } from '../../connectors/uniswap/uniswap.config'; +import { Ethereumish } from '../../services/common-interfaces'; +import { ConfigManagerV2 } from '../../services/config-manager-v2'; + +export class Xdc extends EthereumBase implements Ethereumish { + private static _instances: { [name: string]: Xdc }; + private _gasPrice: number; + private _nativeTokenSymbol: string; + private _chain: string; + + private constructor(network: string) { + const config = getPolygonConfig('xdc', network); + super( + 'xdc', + config.network.chainID, + config.network.nodeURL, + config.network.tokenListSource, + config.network.tokenListType, + config.manualGasPrice, + config.gasLimitTransaction, + ConfigManagerV2.getInstance().get('server.nonceDbPath'), + ConfigManagerV2.getInstance().get('server.transactionDbPath') + ); + this._chain = config.network.name; + this._nativeTokenSymbol = config.nativeCurrencySymbol; + this._gasPrice = config.manualGasPrice; + } + + public static getInstance(network: string): Xdc { + if (Xdc._instances === undefined) { + Xdc._instances = {}; + } + if (!(network in Xdc._instances)) { + Xdc._instances[network] = new Xdc(network); + } + + return Xdc._instances[network]; + } + + public static getConnectedInstances(): { [name: string]: Xdc } { + return Xdc._instances; + } + + public get gasPrice(): number { + return this._gasPrice; + } + + public get nativeTokenSymbol(): string { + return this._nativeTokenSymbol; + } + + public get chain(): string { + return this._chain; + } + + getContract(tokenAddress: string, signerOrProvider?: Wallet | Provider) { + return new Contract(tokenAddress, abi.ERC20Abi, signerOrProvider); + } + + getSpender(reqSpender: string): string { + let spender: string; + if (reqSpender === 'uniswap') { + spender = UniswapConfig.config.uniswapV3SmartOrderRouterAddress( + this._chain + ); + } else { + spender = reqSpender; + } + return spender; + } + + // cancel transaction + async cancelTx(wallet: Wallet, nonce: number): Promise { + logger.info( + 'Canceling any existing transaction(s) with nonce number ' + nonce + '.' + ); + return super.cancelTxWithGasPrice(wallet, nonce, this._gasPrice * 2); + } +} diff --git a/src/chains/xdc/xdc.validators.ts b/src/chains/xdc/xdc.validators.ts new file mode 100644 index 0000000000..957be623ac --- /dev/null +++ b/src/chains/xdc/xdc.validators.ts @@ -0,0 +1,36 @@ +import { + mkRequestValidator, + mkValidator, + RequestValidator, + Validator, + validateAmount, + validateToken, + validateTokenSymbols, +} from '../../services/validators'; +import { + isAddress, + validateNonce, + validateAddress, +} from '../ethereum/ethereum.validators'; + +export const invalidSpenderError: string = + 'The spender param is not a valid xdc address (0x followed by 40 hexidecimal characters).'; + +// given a request, look for a key called spender that is 'uniswap', 'sushi' or an Ethereum address +export const validateSpender: Validator = mkValidator( + 'spender', + invalidSpenderError, + (val) => typeof val === 'string' && (val === 'uniswap' || isAddress(val)) +); + +export const validateXdcApproveRequest: RequestValidator = + mkRequestValidator([ + validateAddress, + validateSpender, + validateToken, + validateAmount, + validateNonce, + ]); + +export const validateXdcAllowancesRequest: RequestValidator = + mkRequestValidator([validateAddress, validateSpender, validateTokenSymbols]); diff --git a/src/chains/xdc/xdc_tokens_apothem.json b/src/chains/xdc/xdc_tokens_apothem.json new file mode 100644 index 0000000000..9ebd8b80d4 --- /dev/null +++ b/src/chains/xdc/xdc_tokens_apothem.json @@ -0,0 +1,47 @@ +{ + "name": "apothem", + "tokens": [ + { + "symbol": "WXDC", + "chainId": 51, + "address": "0x2a5c77b016Df1b3b0AE4E79a68F8adF64Ee741ba", + "decimals": 18 + }, + { + "symbol": "WBTC2", + "chainId": 51, + "address": "0x01B0500f82EF188D0410a46f2E8940133E213e83", + "decimals": 8 + }, + { + "symbol": "YFI2", + "chainId": 51, + "address": "0x22e4Eb82FF59c53B275aDEacd4EE4Bc47fc4f16d", + "decimals": 18 + }, + { + "symbol": "MKR2", + "chainId": 51, + "address": "0x258E445fEf3F41429e38ee124DA63aBfb08edc70", + "decimals": 18 + }, + { + "symbol": "AAVE2", + "chainId": 51, + "address": "0x3042207876c47D3c206df99b3279d97813B34Ea1", + "decimals": 18 + }, + { + "symbol": "UNI2", + "chainId": 51, + "address": "0xD9e33607d06cBB1Fef59488b9969426b10F310B8", + "decimals": 18 + }, + { + "symbol": "USDC2", + "chainId": 51, + "address": "0xF83B9Dc502A3F76c042b4043B6C1B5eBBE574389", + "decimals": 6 + } + ] +} \ No newline at end of file diff --git a/src/network/network.controllers.ts b/src/network/network.controllers.ts index 9fa074f8e0..face2b2c42 100644 --- a/src/network/network.controllers.ts +++ b/src/network/network.controllers.ts @@ -9,6 +9,7 @@ import { BinanceSmartChain } from '../chains/binance-smart-chain/binance-smart-c import { Ethereum } from '../chains/ethereum/ethereum'; import { Harmony } from '../chains/harmony/harmony'; import { Polygon } from '../chains/polygon/polygon'; +import { Xdc } from '../chains/xdc/xdc'; import { TokenInfo } from '../chains/ethereum/ethereum-base'; import { HttpException, @@ -42,6 +43,8 @@ export async function getStatus( connections.push(Ethereum.getInstance(req.network as string)); } else if (req.chain === 'polygon') { connections.push(Polygon.getInstance(req.network as string)); + } else if (req.chain === 'xdc') { + connections.push(Xdc.getInstance(req.network as string)); } else if (req.chain === 'near') { connections.push(Near.getInstance(req.network as string)); } else if (req.chain === 'cronos') { @@ -74,6 +77,11 @@ export async function getStatus( polygonConnections ? Object.values(polygonConnections) : [] ); + const xdcConnections = Xdc.getConnectedInstances(); + connections = connections.concat( + xdcConnections ? Object.values(xdcConnections) : [] + ); + const cronosConnections = Cronos.getConnectedInstances(); connections = connections.concat( cronosConnections ? Object.values(cronosConnections) : [] @@ -131,6 +139,8 @@ export async function getTokens(req: TokensRequest): Promise { connection = Ethereum.getInstance(req.network); } else if (req.chain === 'polygon') { connection = Polygon.getInstance(req.network); + } else if (req.chain === 'xdc') { + connection = Xdc.getInstance(req.network); } else if (req.chain === 'near') { connection = Near.getInstance(req.network); } else if (req.chain === 'cronos') { diff --git a/src/services/connection-manager.ts b/src/services/connection-manager.ts index 526adf5a50..b9ae7d2090 100644 --- a/src/services/connection-manager.ts +++ b/src/services/connection-manager.ts @@ -4,6 +4,7 @@ import { Ethereum } from '../chains/ethereum/ethereum'; import { BinanceSmartChain } from '../chains/binance-smart-chain/binance-smart-chain'; import { Harmony } from '../chains/harmony/harmony'; import { Polygon } from '../chains/polygon/polygon'; +import { Xdc } from '../chains/xdc/xdc'; import { MadMeerkat } from '../connectors/mad_meerkat/mad_meerkat'; import { Openocean } from '../connectors/openocean/openocean'; import { Pangolin } from '../connectors/pangolin/pangolin'; @@ -46,6 +47,7 @@ export async function getChain( else if (chain === 'avalanche') chainInstance = Avalanche.getInstance(network); else if (chain === 'polygon') chainInstance = Polygon.getInstance(network); + else if (chain === 'xdc') chainInstance = Xdc.getInstance(network); else if (chain === 'harmony') chainInstance = Harmony.getInstance(network); else if (chain === 'near') chainInstance = Near.getInstance(network); else if (chain === 'binance-smart-chain') diff --git a/src/services/wallet/wallet.controllers.ts b/src/services/wallet/wallet.controllers.ts index 0bc276db55..73feeb802a 100644 --- a/src/services/wallet/wallet.controllers.ts +++ b/src/services/wallet/wallet.controllers.ts @@ -4,6 +4,7 @@ import { BinanceSmartChain } from '../../chains/binance-smart-chain/binance-smar import { Cronos } from '../../chains/cronos/cronos'; import { Ethereum } from '../../chains/ethereum/ethereum'; import { Polygon } from '../../chains/polygon/polygon'; +import { Xdc } from '../../chains/xdc/xdc'; import { Cosmos } from '../../chains/cosmos/cosmos'; import { Harmony } from '../../chains/harmony/harmony'; @@ -57,6 +58,8 @@ export async function addWallet( connection = Cronos.getInstance(req.network); } else if (req.chain === 'polygon') { connection = Polygon.getInstance(req.network); + } else if (req.chain === 'xdc') { + connection = Xdc.getInstance(req.network); } else if (req.chain === 'cosmos') { connection = Cosmos.getInstance(req.network); } else if (req.chain === 'near') { diff --git a/src/services/wallet/wallet.validators.ts b/src/services/wallet/wallet.validators.ts index 10f9492824..a854de270e 100644 --- a/src/services/wallet/wallet.validators.ts +++ b/src/services/wallet/wallet.validators.ts @@ -77,6 +77,11 @@ export const validatePrivateKey: Validator = mkSelectingValidator( invalidEthPrivateKeyError, (val) => typeof val === 'string' && isEthPrivateKey(val) ), + xdc: mkValidator( + 'privateKey', + invalidEthPrivateKeyError, + (val) => typeof val === 'string' && isEthPrivateKey(val) + ), 'binance-smart-chain': mkValidator( 'privateKey', invalidEthPrivateKeyError, @@ -101,6 +106,7 @@ export const validateChain: Validator = mkValidator( (val === 'ethereum' || val === 'avalanche' || val === 'polygon' || + val === 'xdc' || val == 'near' || val === 'harmony' || val === 'cronos' || diff --git a/src/templates/root.yml b/src/templates/root.yml index 706812c05e..49c622bf08 100644 --- a/src/templates/root.yml +++ b/src/templates/root.yml @@ -20,6 +20,10 @@ configurations: configurationPath: polygon.yml schemaPath: ethereum-schema.json + $namespace xdc: + configurationPath: xdc.yml + schemaPath: ethereum-schema.json + $namespace defira: configurationPath: defira.yml schemaPath: defira-schema.json diff --git a/src/templates/xdc.yml b/src/templates/xdc.yml new file mode 100644 index 0000000000..b85c15f84e --- /dev/null +++ b/src/templates/xdc.yml @@ -0,0 +1,17 @@ +# list the xdc networks available to gateway +networks: + xinfin: + chainID: 50 + nodeURL: https://erpc.xinfin.network + tokenListType: 'URL' + tokenListSource: 'https://raw.githubusercontent.com/pro100skm/xdc-token-list/master/mainnet.tokenlist.json' + nativeCurrencySymbol: 'XDC' + apothem: + chainID: 51 + nodeURL: https://erpc.apothem.network + tokenListType: 'FILE' + tokenListSource: 'src/chains/xdc/xdc_tokens_apothem.json' + nativeCurrencySymbol: 'XDC' + +manualGasPrice: 1 +gasLimitTransaction: 3000000