Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat/ add ETCswap V3 and V2 connectors #340

Open
wants to merge 17 commits into
base: development
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions .github/workflows/workflow.yml

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ethereum Classic's testnet is Mordor, not Goerli.
https://chainlist.org/chain/63
Recommended Funded Endpoint: https://rpc.mordor.etccooperative.org

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chris-mercer The point of replacing that url is to prevent calls to the default endpoint.
Workflows shouldn't access external services except in special cases.

Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ jobs:
cp -rf src/templates/* conf
sed -i 's|/home/gateway/conf/lists/|conf/lists/|g' ./conf/*.yml
sed -i 's/https:\/\/rpc.ankr.com\/eth_goerli/http:\/\/127.0.0.1:8545\//g' ./conf/ethereum.yml
sed -i 's/https:\/\/etc.rivet.link/http:\/\/127.0.0.1:8545\//g' ./conf/ethereum-classic.yml
- name: Run unit test coverage
if: github.event_name == 'pull_request'
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"test:scripts": "jest -i --verbose ./test-scripts/*.test.ts"
},
"dependencies": {
"@_etcswap/smart-order-router": "^3.15.2",
"@balancer-labs/sdk": "^1.1.5",
"@bancor/carbon-sdk": "^0.0.93-DEV",
"@cosmjs/amino": "^0.32.2",
Expand Down Expand Up @@ -63,7 +64,7 @@
"@types/uuid": "^8.3.4",
"@uniswap/sdk": "3.0.3",
"@uniswap/sdk-core": "^5.3.1",
"@uniswap/smart-order-router": "^3.39.0",
"@uniswap/smart-order-router": "^3.46.1",
"@uniswap/v3-core": "^1.0.1",
"@uniswap/v3-periphery": "^1.1.1",
"@uniswap/v3-sdk": "^3.13.1",
Expand Down
126 changes: 126 additions & 0 deletions src/chains/ethereum-classic/ethereum-classic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
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 getEthereumClassicChainConfig } from '../ethereum/ethereum.config';
import { Provider } from '@ethersproject/abstract-provider';
import { Chain as Ethereumish } from '../../services/common-interfaces';
import { ConfigManagerV2 } from '../../services/config-manager-v2';
import { EVMController } from '../ethereum/evm.controllers';
import { ETCSwapConfig } from '../../connectors/etcswap/etcswap.config';

export class EthereumClassicChain extends EthereumBase implements Ethereumish {
private static _instances: { [name: string]: EthereumClassicChain };
private _chain: string;
private _gasPrice: number;
private _gasPriceRefreshInterval: number | null;
private _nativeTokenSymbol: string;
public controller;

private constructor(network: string) {
const config = getEthereumClassicChainConfig('ethereum-classic', network);
super(
'ethereum-classic',
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;
this._gasPriceRefreshInterval =
config.network.gasPriceRefreshInterval !== undefined
? config.network.gasPriceRefreshInterval
: null;

this.updateGasPrice();
this.controller = EVMController;
}

public static getInstance(network: string): EthereumClassicChain {
if (EthereumClassicChain._instances === undefined) {
EthereumClassicChain._instances = {};
}
if (!(network in EthereumClassicChain._instances)) {
EthereumClassicChain._instances[network] = new EthereumClassicChain(
network,
);
}

return EthereumClassicChain._instances[network];
}

public static getConnectedInstances(): {
[name: string]: EthereumClassicChain;
} {
return EthereumClassicChain._instances;
}

/**
* Automatically update the prevailing gas price on the network from the connected RPC node.
*/
async updateGasPrice(): Promise<void> {
if (this._gasPriceRefreshInterval === null) {
return;
}

const gasPrice: number = (await this.provider.getGasPrice()).toNumber();

this._gasPrice = gasPrice * 1e-9;

setTimeout(
this.updateGasPrice.bind(this),
this._gasPriceRefreshInterval * 1000,
);
}

// getters

public get gasPrice(): number {
return this._gasPrice;
}

public get nativeTokenSymbol(): string {
return this._nativeTokenSymbol;
}

public get chain(): string {
return this._chain;
}

// in place for mocking
public get provider() {
return super.provider;
}

getContract(tokenAddress: string, signerOrProvider?: Wallet | Provider) {
return new Contract(tokenAddress, abi.ERC20Abi, signerOrProvider);
}

getSpender(reqSpender: string): string {
let spender: string;
if (reqSpender === 'etcswapLP') {
spender = ETCSwapConfig.config.etcswapV3NftManagerAddress(this._chain);
} else if (reqSpender === 'etcswap') {
spender = ETCSwapConfig.config.etcswapV3SmartOrderRouterAddress(
this._chain,
);
} else {
spender = reqSpender;
}
return spender;
}

// cancel transaction
async cancelTx(wallet: Wallet, nonce: number): Promise<Transaction> {
logger.info(
'Canceling any existing transaction(s) with nonce number ' + nonce + '.',
);
return super.cancelTxWithGasPrice(wallet, nonce, this._gasPrice * 2);
}
}
2 changes: 2 additions & 0 deletions src/chains/ethereum/ethereum.validators.ts

This comment was marked as resolved.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have followed existing naming convention as the Uniswap connectors.
Any deviation may require modifications on the Hummingbot client.

Copy link

@chris-mercer chris-mercer Jul 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay thanks for clarification! great, i want it to follow the schema already present in the codebase

Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ export const validateSpender: Validator = mkValidator(
val === 'curve' ||
val === 'carbonamm' ||
val === 'balancer' ||
val === 'etcswapLP' ||
val === 'etcswap' ||
isAddress(val))
);

Expand Down
14 changes: 14 additions & 0 deletions src/connectors/connectors.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { PlentyConfig } from './plenty/plenty.config';
import { OsmosisConfig } from '../chains/osmosis/osmosis.config';
import { CarbonConfig } from './carbon/carbon.config';
import { BalancerConfig } from './balancer/balancer.config';
import { ETCSwapConfig } from './etcswap/etcswap.config';

export namespace ConnectorsRoutes {
export const router = Router();
Expand Down Expand Up @@ -140,6 +141,19 @@ export namespace ConnectorsRoutes {
chain_type: BalancerConfig.config.chainType,
available_networks: BalancerConfig.config.availableNetworks,
},
{
name: 'etcswap',
trading_type: ETCSwapConfig.config.tradingTypes('swap'),
chain_type: ETCSwapConfig.config.chainType,
available_networks: ETCSwapConfig.config.availableNetworks,
},
{
name: 'etcswapLP',
trading_type: ETCSwapConfig.config.tradingTypes('LP'),
chain_type: ETCSwapConfig.config.chainType,
available_networks: ETCSwapConfig.config.availableNetworks,
additional_spenders: ['etcswap'],
},
],
});
})
Expand Down
58 changes: 58 additions & 0 deletions src/connectors/etcswap/etcswap.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { AvailableNetworks } from '../../services/config-manager-types';
import { ConfigManagerV2 } from '../../services/config-manager-v2';
export namespace ETCSwapConfig {
export interface NetworkConfig {
allowedSlippage: string;
gasLimitEstimate: number;
ttl: number;
maximumHops: number;
etcswapV3SmartOrderRouterAddress: (network: string) => string;
etcswapV3NftManagerAddress: (network: string) => string;
etcswapV3FactoryAddress: (network: string) => string;
quoterContractAddress: (network: string) => string;
tradingTypes: (type: string) => Array<string>;
chainType: string;
availableNetworks: Array<AvailableNetworks>;
useRouter?: boolean;
feeTier?: string;
}

export const config: NetworkConfig = {
allowedSlippage: ConfigManagerV2.getInstance().get(
`etcswap.allowedSlippage`
),
gasLimitEstimate: ConfigManagerV2.getInstance().get(
`etcswap.gasLimitEstimate`
),
ttl: ConfigManagerV2.getInstance().get(`etcswap.ttl`),
maximumHops: ConfigManagerV2.getInstance().get(`etcswap.maximumHops`),
etcswapV3SmartOrderRouterAddress: (network: string) =>
ConfigManagerV2.getInstance().get(
`etcswap.contractAddresses.${network}.etcswapV3SmartOrderRouterAddress`,
),
etcswapV3NftManagerAddress: (network: string) =>
ConfigManagerV2.getInstance().get(
`etcswap.contractAddresses.${network}.etcswapV3NftManagerAddress`,
),
etcswapV3FactoryAddress: (network: string) =>
ConfigManagerV2.getInstance().get(
`etcswap.contractAddresses.${network}.etcswapV3FactoryAddress`
),
quoterContractAddress: (network: string) =>
ConfigManagerV2.getInstance().get(
`etcswap.contractAddresses.${network}.etcswapV3QuoterV2ContractAddress`
),
tradingTypes: (type: string) => {
return type === 'swap' ? ['AMM'] : ['AMM_LP'];
},
chainType: 'EVM',
availableNetworks: [
{
chain: 'ethereum-classic',
networks: ['mainnet']
},
],
useRouter: ConfigManagerV2.getInstance().get(`etcswap.useRouter`),
feeTier: ConfigManagerV2.getInstance().get(`etcswap.feeTier`),
};
}
Loading
Loading