Skip to content

Commit

Permalink
Merge pull request #342 from hummingbot/harjas27-feat/uniswap_bsc
Browse files Browse the repository at this point in the history
Feat / Add Uniswap on chains BSC, Avalanche, and Celo
  • Loading branch information
nikspz committed Jul 31, 2024
2 parents 6c55d4f + 7e565d4 commit bd5573e
Show file tree
Hide file tree
Showing 24 changed files with 1,053 additions and 143 deletions.
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module.exports = {
'src/chains/binance-smart-chain/binance-smart-chain.ts',
'src/chains/ethereum/ethereum.ts',
'src/chains/avalanche/avalanche.ts',
'src/chains/celo/celo.ts',
'src/chains/avalanche/pangolin/pangolin.ts',
'src/chains/cosmos/cosmos.ts',
'src/chains/near/near.ts',
Expand Down
10 changes: 9 additions & 1 deletion src/chains/avalanche/avalanche.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { TraderjoeConfig } from '../../connectors/traderjoe/traderjoe.config';
import { PangolinConfig } from '../../connectors/pangolin/pangolin.config';
import { OpenoceanConfig } from '../../connectors/openocean/openocean.config';
import { Ethereumish } from '../../services/common-interfaces';
import { UniswapConfig } from '../../connectors/uniswap/uniswap.config';
import { SushiswapConfig } from '../../connectors/sushiswap/sushiswap.config';
import { ConfigManagerV2 } from '../../services/config-manager-v2';
import { EVMController } from '../ethereum/evm.controllers';
Expand Down Expand Up @@ -84,7 +85,14 @@ export class Avalanche extends EthereumBase implements Ethereumish {

getSpender(reqSpender: string): string {
let spender: string;
if (reqSpender === 'pangolin') {
if (reqSpender === 'uniswap') {
spender = UniswapConfig.config.uniswapV3SmartOrderRouterAddress(
'avalanche',
this._chain,
);
} else if (reqSpender === 'uniswapLP') {
spender = UniswapConfig.config.uniswapV3NftManagerAddress('avalanche', this._chain);
} else if (reqSpender === 'pangolin') {
spender = PangolinConfig.config.routerAddress(this._chain);
} else if (reqSpender === 'openocean') {
spender = OpenoceanConfig.config.routerAddress('avalanche', this._chain);
Expand Down
4 changes: 3 additions & 1 deletion src/chains/avalanche/avalanche.validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ export const validateSpender: Validator = mkValidator(

(val) =>
typeof val === 'string' &&
(val === 'pangolin' ||
(val === 'uniswap' ||
val === 'uniswapLP' ||
val === 'pangolin' ||
val === 'traderjoe' ||
val === 'openocean' ||
val === 'sushiswap' ||
Expand Down
8 changes: 8 additions & 0 deletions src/chains/binance-smart-chain/binance-smart-chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { SushiswapConfig } from '../../connectors/sushiswap/sushiswap.config';
import { ConfigManagerV2 } from '../../services/config-manager-v2';
import { OpenoceanConfig } from '../../connectors/openocean/openocean.config';
import { EVMController } from '../ethereum/evm.controllers';
import {UniswapConfig} from "../../connectors/uniswap/uniswap.config";

export class BinanceSmartChain extends EthereumBase implements Ethereumish {
private static _instances: { [name: string]: BinanceSmartChain };
Expand Down Expand Up @@ -113,6 +114,13 @@ export class BinanceSmartChain extends EthereumBase implements Ethereumish {
'binance-smart-chain',
this._chain
);
} else if (reqSpender === 'uniswap') {
spender = UniswapConfig.config.uniswapV3SmartOrderRouterAddress(
'binance-smart-chain',
this._chain
);
} else if (reqSpender === 'uniswapLP') {
spender = UniswapConfig.config.uniswapV3NftManagerAddress('binance-smart-chain', this._chain);
} else {
spender = reqSpender;
}
Expand Down
130 changes: 130 additions & 0 deletions src/chains/celo/celo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
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 getCeloConfig } from '../ethereum/ethereum.config';
import { Provider } from '@ethersproject/abstract-provider';
import { Ethereumish } from '../../services/common-interfaces';
import { ConfigManagerV2 } from '../../services/config-manager-v2';
import { EVMController } from '../ethereum/evm.controllers';
import { UniswapConfig } from '../../connectors/uniswap/uniswap.config';

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

private constructor(network: string) {
const config = getCeloConfig('celo', network);
super(
'celo',
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): Celo {
if (Celo._instances === undefined) {
Celo._instances = {};
}
if (!(network in Celo._instances)) {
Celo._instances[network] = new Celo(network);
}

return Celo._instances[network];
}

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

// getters

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(
'celo',
this._chain,
);
} else if (reqSpender === 'uniswapLP') {
spender = UniswapConfig.config.uniswapV3NftManagerAddress('celo', 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);
}

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

const gasPrice = await this.getGasPrice();
if (gasPrice !== null) {
this._gasPrice = gasPrice;
} else {
logger.info('gasPrice is unexpectedly null.');
}

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

async close() {
await super.close();
if (this._chain in Celo._instances) {
delete Celo._instances[this._chain];
}
}
}
41 changes: 41 additions & 0 deletions src/chains/celo/celo.validators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
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 Celo address (0x followed by 40 hexidecimal characters).';

// given a request, look for a key called spender that is 'uniswap' or an Ethereum address
export const validateSpender: Validator = mkValidator(
'spender',
invalidSpenderError,

(val) =>
typeof val === 'string' &&
(val === 'uniswap' ||
val === 'uniswapLP' ||
isAddress(val))
);

export const validateCeloApproveRequest: RequestValidator =
mkRequestValidator([
validateAddress,
validateSpender,
validateToken,
validateAmount,
validateNonce,
]);

export const validateCeloAllowancesRequest: RequestValidator =
mkRequestValidator([validateAddress, validateSpender, validateTokenSymbols]);
5 changes: 4 additions & 1 deletion src/chains/ethereum/ethereum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ export class Ethereum extends EthereumBase implements Ethereumish {
let spender: string;
if (reqSpender === 'uniswap') {
spender = UniswapConfig.config.uniswapV3SmartOrderRouterAddress(
this.chainName,
this._chain,
);
} else if (reqSpender === 'pancakeswap') {
Expand All @@ -196,7 +197,9 @@ export class Ethereum extends EthereumBase implements Ethereumish {
this._chain,
);
} else if (reqSpender === 'uniswapLP') {
spender = UniswapConfig.config.uniswapV3NftManagerAddress(this._chain);
spender = UniswapConfig.config.uniswapV3NftManagerAddress(
this.chainName,
this._chain);
} else if (reqSpender === 'carbonamm') {
spender = CarbonConfig.config.carbonContractsConfig(
'ethereum',
Expand Down
3 changes: 2 additions & 1 deletion src/chains/polygon/polygon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,11 @@ export class Polygon extends EthereumBase implements Ethereumish {
let spender: string;
if (reqSpender === 'uniswap') {
spender = UniswapConfig.config.uniswapV3SmartOrderRouterAddress(
'polygon',
this._chain
);
} else if (reqSpender === 'uniswapLP') {
spender = UniswapConfig.config.uniswapV3NftManagerAddress(this._chain);
spender = UniswapConfig.config.uniswapV3NftManagerAddress('polygon', this._chain);
} else if (reqSpender === 'quickswap') {
spender = QuickswapConfig.config.routerAddress(this._chain);
} else if (reqSpender === 'sushiswap') {
Expand Down
78 changes: 60 additions & 18 deletions src/connectors/uniswap/uniswap.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ export namespace UniswapConfig {
gasLimitEstimate: number;
ttl: number;
maximumHops: number;
uniswapV3SmartOrderRouterAddress: (network: string) => string;
uniswapV3NftManagerAddress: (network: string) => string;
uniswapV3FactoryAddress: (network: string) => string;
uniswapV3SmartOrderRouterAddress: (chain: string, network: string) => string;
uniswapV3NftManagerAddress: (chain: string, network: string) => string;
uniswapV3FactoryAddress: (chain: string, network: string) => string;
quoterContractAddress: (chain: string, network: string) => string;
tradingTypes: (type: string) => Array<string>;
chainType: string;
availableNetworks: Array<AvailableNetworks>;
useRouter?: boolean;
feeTier?: string;
quoterContractAddress: (network: string) => string;
}

export const config: NetworkConfig = {
Expand All @@ -26,17 +26,37 @@ export namespace UniswapConfig {
),
ttl: ConfigManagerV2.getInstance().get(`uniswap.ttl`),
maximumHops: ConfigManagerV2.getInstance().get(`uniswap.maximumHops`),
uniswapV3SmartOrderRouterAddress: (network: string) =>
uniswapV3SmartOrderRouterAddress: (chain: string, network: string) =>
ConfigManagerV2.getInstance().get(
`uniswap.contractAddresses.${network}.uniswapV3SmartOrderRouterAddress`
'uniswap.contractAddresses.' +
chain +
'.' +
network +
'.uniswapV3SmartOrderRouterAddress'
),
uniswapV3NftManagerAddress: (network: string) =>
uniswapV3NftManagerAddress: (chain: string, network: string) =>
ConfigManagerV2.getInstance().get(
`uniswap.contractAddresses.${network}.uniswapV3NftManagerAddress`
'uniswap.contractAddresses.' +
chain +
'.' +
network +
'.uniswapV3NftManagerAddress'
),
uniswapV3FactoryAddress: (network: string) =>
uniswapV3FactoryAddress: (chain: string, network: string) =>
ConfigManagerV2.getInstance().get(
`uniswap.contractAddresses.${network}.uniswapV3FactoryAddress`
'uniswap.contractAddresses.' +
chain +
'.' +
network +
'.uniswapV3FactoryAddress'
),
quoterContractAddress: (chain: string, network: string) =>
ConfigManagerV2.getInstance().get(
'uniswap.contractAddresses.' +
chain +
'.' +
network +
'.uniswapV3QuoterV2ContractAddress'
),
tradingTypes: (type: string) => {
return type === 'swap' ? ['AMM'] : ['AMM_LP'];
Expand All @@ -46,29 +66,51 @@ export namespace UniswapConfig {
{
chain: 'ethereum',
networks: Object.keys(
ConfigManagerV2.getInstance().get('uniswap.contractAddresses')
ConfigManagerV2.getInstance().get('uniswap.contractAddresses.ethereum')
).filter((network) =>
Object.keys(
ConfigManagerV2.getInstance().get('ethereum.networks')
).includes(network)
),
},
{
chain: 'polygon',
{ chain: 'polygon',
networks: Object.keys(
ConfigManagerV2.getInstance().get('uniswap.contractAddresses')
ConfigManagerV2.getInstance().get('uniswap.contractAddresses.polygon')
).filter((network) =>
Object.keys(
ConfigManagerV2.getInstance().get('polygon.networks')
).includes(network)
),
},
{ chain: 'binance-smart-chain',
networks: Object.keys(
ConfigManagerV2.getInstance().get('uniswap.contractAddresses.binance-smart-chain')
).filter((network) =>
Object.keys(
ConfigManagerV2.getInstance().get('binance-smart-chain.networks')
).includes(network)
),
},
{ chain: 'avalanche',
networks: Object.keys(
ConfigManagerV2.getInstance().get('uniswap.contractAddresses.avalanche')
).filter((network) =>
Object.keys(
ConfigManagerV2.getInstance().get('avalanche.networks')
).includes(network)
),
},
{ chain: 'celo',
networks: Object.keys(
ConfigManagerV2.getInstance().get('uniswap.contractAddresses.celo')
).filter((network) =>
Object.keys(
ConfigManagerV2.getInstance().get('celo.networks')
).includes(network)
),
},
],
useRouter: ConfigManagerV2.getInstance().get(`uniswap.useRouter`),
feeTier: ConfigManagerV2.getInstance().get(`uniswap.feeTier`),
quoterContractAddress: (network: string) =>
ConfigManagerV2.getInstance().get(
`uniswap.contractAddresses.${network}.uniswapV3QuoterV2ContractAddress`
),
};
}
Loading

0 comments on commit bd5573e

Please sign in to comment.