Skip to content

Commit

Permalink
refactor: create globiance connector
Browse files Browse the repository at this point in the history
  • Loading branch information
gzliudan committed Mar 22, 2023
1 parent cf24876 commit 2cb7867
Show file tree
Hide file tree
Showing 15 changed files with 1,323 additions and 2 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"express": "^4.17.1",
"express-winston": "^4.1.0",
"fs-extra": "^10.0.0",
"globiance-sdk": "^1.0.0-beta",
"js-yaml": "^4.1.0",
"level": "^8.0.0",
"lodash": "^4.17.21",
Expand Down
2 changes: 2 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { MadMeerkatConfig } from './connectors/mad_meerkat/mad_meerkat.config';
import { PangolinConfig } from './connectors/pangolin/pangolin.config';
import { QuickswapConfig } from './connectors/quickswap/quickswap.config';
import { XsswapConfig } from './connectors/xsswap/xsswap.config';
import { GlobianceConfig } from './connectors/globiance/globiance.config';
import { TraderjoeConfig } from './connectors/traderjoe/traderjoe.config';
import { UniswapConfig } from './connectors/uniswap/uniswap.config';
import { OpenoceanConfig } from './connectors/openocean/openocean.config';
Expand Down Expand Up @@ -85,6 +86,7 @@ gatewayApp.get(
pangolin: PangolinConfig.config.availableNetworks,
quickswap: QuickswapConfig.config.availableNetworks,
xsswap: XsswapConfig.config.availableNetworks,
globiance: GlobianceConfig.config.availableNetworks,
sushiswap: SushiswapConfig.config.availableNetworks,
openocean: OpenoceanConfig.config.availableNetworks,
traderjoe: TraderjoeConfig.config.availableNetworks,
Expand Down
1 change: 1 addition & 0 deletions src/chains/ethereum/ethereum.validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const validateSpender: Validator = mkValidator(
val === 'openocean' ||
val === 'quickswap' ||
val === 'xsswap' ||
val === 'globiance' ||
val === 'defikingdoms' ||
val === 'defira' ||
val === 'mad_meerkat' ||
Expand Down
3 changes: 3 additions & 0 deletions src/chains/xdc/xdc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { EthereumBase } from '../ethereum/ethereum-base';
import { getEthereumConfig as getPolygonConfig } from '../ethereum/ethereum.config';
import { Provider } from '@ethersproject/abstract-provider';
import { XsswapConfig } from '../../connectors/xsswap/xsswap.config';
import { GlobianceConfig } from '../../connectors/globiance/globiance.config';
import { Ethereumish } from '../../services/common-interfaces';
import { ConfigManagerV2 } from '../../services/config-manager-v2';

Expand Down Expand Up @@ -67,6 +68,8 @@ export class Xdc extends EthereumBase implements Ethereumish {
let spender: string;
if (reqSpender === 'xsswap') {
spender = XsswapConfig.config.routerAddress(this._chain);
} else if (reqSpender === 'globiance') {
spender = GlobianceConfig.config.routerAddress(this._chain);
} else {
spender = reqSpender;
}
Expand Down
4 changes: 2 additions & 2 deletions src/chains/xdc/xdc.validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ import {
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 'xsswap' or an Ethereum address
// given a request, look for a key called spender that is 'xsswap' or 'globiance' or an Ethereum address
export const validateSpender: Validator = mkValidator(
'spender',
invalidSpenderError,
(val) => typeof val === 'string' && (val === 'xsswap' || isAddress(val))
(val) => typeof val === 'string' && (val === 'xsswap' || val === 'globiance' || isAddress(val))
);

export const validateXdcApproveRequest: RequestValidator =
Expand Down
6 changes: 6 additions & 0 deletions src/connectors/connectors.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { PangolinConfig } from './pangolin/pangolin.config';
import { PerpConfig } from './perp/perp.config';
import { QuickswapConfig } from './quickswap/quickswap.config';
import { XsswapConfig } from './xsswap/xsswap.config';
import { GlobianceConfig } from './globiance/globiance.config';
import { SushiswapConfig } from './sushiswap/sushiswap.config';
import { TraderjoeConfig } from './traderjoe/traderjoe.config';
import { UniswapConfig } from './uniswap/uniswap.config';
Expand Down Expand Up @@ -56,6 +57,11 @@ export namespace ConnectorsRoutes {
trading_type: XsswapConfig.config.tradingTypes,
available_networks: XsswapConfig.config.availableNetworks,
},
{
name: 'globiance',
trading_type: GlobianceConfig.config.tradingTypes,
available_networks: GlobianceConfig.config.availableNetworks,
},
{
name: 'perp',
trading_type: PerpConfig.config.tradingTypes('perp'),
Expand Down
22 changes: 22 additions & 0 deletions src/connectors/globiance/globiance.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ConfigManagerV2 } from '../../services/config-manager-v2';
import { AvailableNetworks } from '../../services/config-manager-types';

export namespace GlobianceConfig {
export interface NetworkConfig {
allowedSlippage: string;
gasLimitEstimate: number;
ttl: number;
routerAddress: (network: string) => string;
tradingTypes: Array<string>;
availableNetworks: Array<AvailableNetworks>;
}

export const config: NetworkConfig = {
allowedSlippage: ConfigManagerV2.getInstance().get('globiance.allowedSlippage'),
gasLimitEstimate: ConfigManagerV2.getInstance().get('globiance.gasLimitEstimate'),
ttl: ConfigManagerV2.getInstance().get('globiance.ttl'),
routerAddress: (network: string) => ConfigManagerV2.getInstance().get('globiance.contractAddresses.' + network + '.routerAddress'),
tradingTypes: ['EVM_AMM'],
availableNetworks: [{ chain: 'xdc', networks: ['xinfin', 'apothem'] }],
};
}
234 changes: 234 additions & 0 deletions src/connectors/globiance/globiance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import { percentRegexp } from '../../services/config-manager-v2';
import { UniswapishPriceError } from '../../services/error-handler';
import {
BigNumber,
Contract,
ContractInterface,
Transaction,
Wallet,
} from 'ethers';
import { isFractionString } from '../../services/validators';
import { GlobianceConfig } from './globiance.config';
import routerAbi from './globiance_v2_router_abi.json';
import {
Fetcher,
Percent,
Router,
Token,
TokenAmount,
Trade,
Pair
} from 'globiance-sdk';
import { logger } from '../../services/logger';
import { Xdc } from '../../chains/xdc/xdc';
import { ExpectedTrade, Uniswapish } from '../../services/common-interfaces';

export class Globiance implements Uniswapish {
private static _instances: { [name: string]: Globiance };
private xdc: Xdc;
private _router: string;
private _routerAbi: ContractInterface;
private _gasLimitEstimate: number;
private _ttl: number;
private chainId;
private tokenList: Record<string, Token> = {};
private _ready: boolean = false;

private constructor(network: string) {
const config = GlobianceConfig.config;
this.xdc = Xdc.getInstance(network);
this.chainId = this.xdc.chainId;
this._router = config.routerAddress(network);
this._ttl = config.ttl;
this._routerAbi = routerAbi.abi;
this._gasLimitEstimate = config.gasLimitEstimate;
}

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

return Globiance._instances[chain + network];
}

/**
* Given a token's address, return the connector's native representation of
* the token.
*
* @param address Token address
*/
public getTokenByAddress(address: string): Token {
return this.tokenList[address];
}

public async init() {
if (!this.xdc.ready()) {
await this.xdc.init();
}
for (const token of this.xdc.storedTokenList) {
this.tokenList[token.address] = new Token(this.chainId, token.address, token.decimals, token.symbol, token.name);
}
this._ready = true;
}

public ready(): boolean {
return this._ready;
}

/**
* Router address.
*/
public get router(): string {
return this._router;
}

/**
* Router smart contract ABI.
*/
public get routerAbi(): ContractInterface {
return this._routerAbi;
}

/**
* Default gas limit used to estimate cost for swap transactions.
*/
public get gasLimitEstimate(): number {
return this._gasLimitEstimate;
}

/**
* Default time-to-live for swap transactions, in seconds.
*/
public get ttl(): number {
return this._ttl;
}

/**
* Gets the allowed slippage percent from the optional parameter or the value
* in the configuration.
*
* @param allowedSlippageStr (Optional) should be of the form '1/10'.
*/
public getAllowedSlippage(allowedSlippageStr?: string): Percent {
if (allowedSlippageStr != null && isFractionString(allowedSlippageStr)) {
const fractionSplit = allowedSlippageStr.split('/');
return new Percent(fractionSplit[0], fractionSplit[1]);
}

const allowedSlippage = GlobianceConfig.config.allowedSlippage;
const nd = allowedSlippage.match(percentRegexp);
if (nd) return new Percent(nd[1], nd[2]);
throw new Error('Encountered a malformed percent string in the config for ALLOWED_SLIPPAGE.');
}

/**
* Given the amount of `baseToken` to put into a transaction, calculate the
* amount of `quoteToken` that can be expected from the transaction.
*
* This is typically used for calculating token sell prices.
*
* @param baseToken Token input for the transaction
* @param quoteToken Output from the transaction
* @param amount Amount of `baseToken` to put into the transaction
*/
async estimateSellTrade(baseToken: Token, quoteToken: Token, amount: BigNumber, allowedSlippage?: string): Promise<ExpectedTrade> {
const nativeTokenAmount: TokenAmount = new TokenAmount(baseToken, amount.toString());
logger.info(`Fetching pair data for ${baseToken.address}-${quoteToken.address}.`);
const pair: Pair = await Fetcher.fetchPairData(baseToken, quoteToken, this.xdc.provider);
const trades: Trade[] = Trade.bestTradeExactIn([pair], nativeTokenAmount, quoteToken, { maxHops: 1 });
if (!trades || trades.length === 0) {
throw new UniswapishPriceError(`priceSwapIn: no trade pair found for ${baseToken} to ${quoteToken}.`);
}
logger.info(`Best trade for ${baseToken.address}-${quoteToken.address}: ${trades[0]}`);
const expectedAmount = trades[0].minimumAmountOut(this.getAllowedSlippage(allowedSlippage));
return { trade: trades[0], expectedAmount };
}

/**
* Given the amount of `baseToken` desired to acquire from a transaction,
* calculate the amount of `quoteToken` needed for the transaction.
*
* This is typically used for calculating token buy prices.
*
* @param quoteToken Token input for the transaction
* @param baseToken Token output from the transaction
* @param amount Amount of `baseToken` desired from the transaction
*/
async estimateBuyTrade(quoteToken: Token, baseToken: Token, amount: BigNumber, allowedSlippage?: string): Promise<ExpectedTrade> {
const nativeTokenAmount: TokenAmount = new TokenAmount(baseToken, amount.toString());
logger.info(`Fetching pair data for ${quoteToken.address}-${baseToken.address}.`);
const pair: Pair = await Fetcher.fetchPairData(quoteToken, baseToken, this.xdc.provider);
const trades: Trade[] = Trade.bestTradeExactOut([pair], quoteToken, nativeTokenAmount, { maxHops: 1 });
if (!trades || trades.length === 0) {
throw new UniswapishPriceError(`priceSwapOut: no trade pair found for ${quoteToken.address} to ${baseToken.address}.`);
}
logger.info(`Best trade for ${quoteToken.address}-${baseToken.address}: ${trades[0]}`);

const expectedAmount = trades[0].maximumAmountIn(this.getAllowedSlippage(allowedSlippage));
return { trade: trades[0], expectedAmount };
}

/**
* Given a wallet and a Uniswap-ish trade, try to execute it on blockchain.
*
* @param wallet Wallet
* @param trade Expected trade
* @param gasPrice Base gas price, for pre-EIP1559 transactions
* @param globianceRouter smart contract address
* @param ttl How long the swap is valid before expiry, in seconds
* @param abi Router contract ABI
* @param gasLimit Gas limit
* @param nonce (Optional) EVM transaction nonce
* @param maxFeePerGas (Optional) Maximum total fee per gas you want to pay
* @param maxPriorityFeePerGas (Optional) Maximum tip per gas you want to pay
*/
async executeTrade(
wallet: Wallet,
trade: Trade,
gasPrice: number,
globianceRouter: string,
ttl: number,
abi: ContractInterface,
gasLimit: number,
nonce?: number,
maxFeePerGas?: BigNumber,
maxPriorityFeePerGas?: BigNumber,
allowedSlippage?: string
): Promise<Transaction> {
const result = Router.swapCallParameters(trade, {
ttl,
recipient: wallet.address,
allowedSlippage: this.getAllowedSlippage(allowedSlippage),
});

const contract = new Contract(globianceRouter, abi, wallet);
if (!nonce) {
nonce = await this.xdc.nonceManager.getNextNonce(wallet.address);
}
let tx;
if (maxFeePerGas || maxPriorityFeePerGas) {
tx = await contract[result.methodName](...result.args, {
gasLimit: gasLimit.toFixed(0),
value: result.value,
nonce: nonce,
maxFeePerGas,
maxPriorityFeePerGas,
});
} else {
tx = await contract[result.methodName](...result.args, {
gasPrice: (gasPrice * 1e9).toFixed(0),
gasLimit: gasLimit.toFixed(0),
value: result.value,
nonce: nonce,
});
}

logger.info(tx);
await this.xdc.nonceManager.commitNonce(wallet.address, nonce);
return tx;
}
}
Loading

0 comments on commit 2cb7867

Please sign in to comment.