Skip to content

Add trade quote utility #60

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

Merged
merged 24 commits into from
Jul 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
05f9027
Add trade quote utility
cgewecke Jun 8, 2021
d85ae3d
0.2.0
cgewecke Jun 8, 2021
eb873b7
Add polygon quote test
cgewecke Jun 8, 2021
3548d1c
Use GasNow instead of EthGasStation
cgewecke Jun 9, 2021
eb01164
Make ZeroEx quoter constants TradeQuote params
cgewecke Jun 9, 2021
4e8a0f1
Re-order files and integrate utils into TradeAPI
cgewecke Jun 9, 2021
d7c7de7
Remove tokenMap dependency / add fetchTradeQuote arg validation
cgewecke Jun 9, 2021
d317dc3
Add gasPrice option to fetchTradeAsync / tradeQuote.generate
cgewecke Jun 9, 2021
9ccac88
Estimate gas via tradeModuleWrapper
cgewecke Jun 9, 2021
45b0b6b
Add TradeAPI#fetchTradeQuote tests
cgewecke Jun 10, 2021
9231a41
0.2.1
cgewecke Jun 10, 2021
42a7b20
Set coin prices to zero if CoinGecko fails
cgewecke Jun 10, 2021
d8a7256
0.2.2
cgewecke Jun 10, 2021
c7c5ac6
Rename tradequote file to tradeQuoter
cgewecke Jun 10, 2021
b5b52f0
Make sure dist is cleared before build
cgewecke Jun 10, 2021
3940d82
0.2.3
cgewecke Jun 10, 2021
8d76606
Fix bug reading from options.slippagePercentage
cgewecke Jun 11, 2021
e4a87aa
0.2.4
cgewecke Jun 11, 2021
2c360f0
Additional tests for TradeAPI utils and public method comments
cgewecke Jun 14, 2021
140edb3
Pass fee percentage to ZeroEx api call / remove Uniswap from excluded…
cgewecke Jul 12, 2021
c0b8941
0.2.5
cgewecke Jul 12, 2021
184406f
Include fee in toTokenAmount adjustment
cgewecke Jul 13, 2021
324f6a4
0.2.6
cgewecke Jul 13, 2021
4c81300
Merge branch 'master' into chris/trade_quote_util
cgewecke Jul 21, 2021
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
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "set.js",
"version": "0.1.2",
"version": "0.2.6",
"description": "A javascript library for interacting with the Set Protocol v2",
"keywords": [
"set.js",
Expand All @@ -16,10 +16,12 @@
"scripts": {
"prod": "yarn run build",
"chain": "bash scripts/init_chain_internal.sh",
"build": "yarn run build-dist && yarn run tslint && tspath -f",
"build": "yarn run clean && yarn run build-dist && yarn run tslint && tspath -f",
"build-ts": "tsc -p tsconfig.json",
"build-dist": "tsc -p tsconfig.dist.json",
"clean": "rm -rf dist",
"test": "jest --runInBand",
"test:verbose": "jest --runInBand --silent=false",
"test:watch": "jest --watch --runInBand",
"tslint": "tslint -c tslint.json -p tsconfig.json",
"precommit": "lint-staged",
Expand Down Expand Up @@ -63,11 +65,14 @@
"@types/jest": "^26.0.5",
"@types/web3": "^1.2.2",
"abi-decoder": "^2.3.0",
"axios": "^0.21.1",
"bignumber.js": "^9.0.0",
"dotenv": "^8.2.0",
"ethereum-types": "^3.2.0",
"ethereumjs-util": "^7.0.3",
"ethers": "^5.0.3",
"graph-results-pager": "^1.0.3",
"js-big-decimal": "^1.3.4",
"jsonschema": "^1.2.6",
"lodash": "^4.17.19",
"truffle": "^5.1.35",
Expand Down
2 changes: 1 addition & 1 deletion src/Set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ class Set {
assertions
);
this.system = new SystemAPI(ethersProvider, config.controllerAddress);
this.trade = new TradeAPI(ethersProvider, config.tradeModuleAddress);
this.trade = new TradeAPI(ethersProvider, config.tradeModuleAddress, config.zeroExApiKey);
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know if people can generate their own API keys for 0x. We may have to supply it for it to be used by anyone else

Choose a reason for hiding this comment

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

The 0x API is configured with a list of API keys which are permitted to access RFQ-T liquidity. For the instance at api.0x.org, the 0x team is maintaining a list of trusted integrators

@asoong based on the statement from their docs it's not possible to request one. If I understand correctly it will still works but I just may not get the best deal available, right?

this.navIssuance = new NavIssuanceAPI(ethersProvider, config.navIssuanceModuleAddress);
this.priceOracle = new PriceOracleAPI(ethersProvider, config.masterOracleAddress);
this.debtIssuance = new DebtIssuanceAPI(ethersProvider, config.debtIssuanceModuleAddress);
Expand Down
151 changes: 149 additions & 2 deletions src/api/TradeAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,23 @@ import { TransactionOverrides } from '@setprotocol/set-protocol-v2/dist/typechai
import { BigNumber } from 'ethers/lib/ethers';

import TradeModuleWrapper from '../wrappers/set-protocol-v2/TradeModuleWrapper';
import SetTokenAPI from './SetTokenAPI';
import Assertions from '../assertions';

import {
TradeQuoter,
CoinGeckoDataService,
GasOracleService
} from './utils';

import {
TradeQuote,
CoinGeckoTokenData,
CoinGeckoTokenMap,
GasOracleSpeed,
CoinGeckoCoinPrices
} from '../types';

/**
* @title TradeAPI
* @author Set Protocol
Expand All @@ -36,14 +51,20 @@ import Assertions from '../assertions';
export default class TradeAPI {
private tradeModuleWrapper: TradeModuleWrapper;
private assert: Assertions;
private provider: Provider;
private tradeQuoter: TradeQuoter;
private coinGecko: CoinGeckoDataService;
private chainId: number;

public constructor(
provider: Provider,
tradeModuleAddress: Address,
assertions?: Assertions
zeroExApiKey?: string,
) {
this.provider = provider;
this.tradeModuleWrapper = new TradeModuleWrapper(provider, tradeModuleAddress);
this.assert = assertions || new Assertions();
this.assert = new Assertions();
this.tradeQuoter = new TradeQuoter(zeroExApiKey);
}

/**
Expand Down Expand Up @@ -113,4 +134,130 @@ export default class TradeAPI {
txOpts
);
}

/**
* Call 0x API to generate a trade quote for two SetToken components.
*
* @param fromToken Address of token being sold
* @param toToken Address of token being bought
* @param fromTokenDecimals Token decimals of token being sold (ex: 18)
* @param toTokenDecimals Token decimals of token being bought (ex: 18)
* @param rawAmount String quantity of token to sell (ex: "0.5")
* @param fromAddress SetToken address which holds the buy / sell components
* @param setToken SetTokenAPI instance
* @param gasPrice (Optional) gasPrice to calculate gas costs with (Default: fetched from GasNow)
* @param slippagePercentage (Optional) maximum slippage, determines min receive quantity. (Default: 2%)
Copy link
Contributor

Choose a reason for hiding this comment

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

Did you grab these defaults from the service?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes... from heroku, here: https://dashboard.heroku.com/apps/set-core-production/settings

Screen Shot 2021-07-12 at 2 48 42 PM

The whole number percentage is then divided by 100 before being passed to Zero Ex here:

(slippagePercentage / 100),

* @param isFirmQuote (Optional) Whether quote request is indicative or firm
* @param feePercentage (Optional) Default: 0
* @param feeRecipient (Optional) Default: 0xD3D555Bb655AcBA9452bfC6D7cEa8cC7b3628C55
* @param excludedSources (Optional) Exchanges to exclude (Default: ['Kyber', 'Eth2Dai', 'Uniswap', 'Mesh'])
*
* @return {Promise<TradeQuote>}
*/
public async fetchTradeQuoteAsync(
fromToken: Address,
toToken: Address,
fromTokenDecimals: number,
toTokenDecimals: number,
rawAmount: string,
fromAddress: Address,
setToken: SetTokenAPI,
gasPrice?: number,
slippagePercentage?: number,
isFirmQuote?: boolean,
feePercentage?: number,
feeRecipient?: Address,
excludedSources?: string[],
): Promise<TradeQuote> {
this.assert.schema.isValidAddress('fromToken', fromToken);
this.assert.schema.isValidAddress('toToken', toToken);
this.assert.schema.isValidAddress('fromAddress', fromAddress);
this.assert.schema.isValidJsNumber('fromTokenDecimals', fromTokenDecimals);
this.assert.schema.isValidJsNumber('toTokenDecimals', toTokenDecimals);
this.assert.schema.isValidString('rawAmount', rawAmount);

const chainId = (await this.provider.getNetwork()).chainId;

return this.tradeQuoter.generate({
fromToken,
toToken,
fromTokenDecimals,
toTokenDecimals,
rawAmount,
fromAddress,
chainId,
tradeModule: this.tradeModuleWrapper,
provider: this.provider,
setToken,
gasPrice,
slippagePercentage,
isFirmQuote,
feePercentage,
feeRecipient,
excludedSources,
});
}

/**
* Fetches a list of tokens and their metadata from CoinGecko. Each entry includes
* the token's address, proper name, decimals, exchange symbol and a logo URI if available.
* For Ethereum, this is a list of tokens tradeable on Uniswap, for Polygon it's a list of
* tokens tradeable on Sushiswap's Polygon exchange. Method is useful for acquiring token decimals
* necessary to generate a trade quote and images for representing available tokens in a UI.
*
* @return List of tradeable tokens for chain platform
*/
public async fetchTokenListAsync(): Promise<CoinGeckoTokenData[]> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Just want to confirm this is not currently being consumed? It was part of the old flow for grabbing the decimals? This might belong in a different utils type of API

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We're using this stuff in the admin UI. This API is helpful I think.

await this.initializeForChain();
return this.coinGecko.fetchTokenList();
}

/**
* Fetches the same info as `fetchTokenList` in the form of a map indexed by address. Method is
* useful if you're cacheing the token list and want quick lookups for a variety of trades.
*
* @return Map of token addresses to token metadata
*/
public async fetchTokenMapAsync(): Promise<CoinGeckoTokenMap> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same

await this.initializeForChain();
return this.coinGecko.fetchTokenMap();
}

/**
* Fetches a list of prices vs currencies for the specified inputs from CoinGecko
*
* @param contractAddresses String array of contract addresses
* @param vsCurrencies String array of currency codes (see CoinGecko api for a complete list)
*
* @return List of prices vs currencies
*/
public async fetchCoinPricesAsync(
Copy link
Contributor

Choose a reason for hiding this comment

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

I think most of these below belong in a different API, perhaps ERC20 API or something of the sort

contractAddresses: string[],
vsCurrencies: string[]
): Promise<CoinGeckoCoinPrices> {
await this.initializeForChain();
return this.coinGecko.fetchCoinPrices({contractAddresses, vsCurrencies});
}

/**
* Fetches the recommended gas price for a specified execution speed.
*
* @param speed (Optional) string value: "average" | "fast" | "fastest" (Default: fast)
*
* @return Number: gas price
*/
public async fetchGasPriceAsync(speed?: GasOracleSpeed): Promise<number> {
await this.initializeForChain();
const oracle = new GasOracleService(this.chainId);
return oracle.fetchGasPrice(speed);
}


private async initializeForChain() {
if (this.coinGecko === undefined) {
const network = await this.provider.getNetwork();
this.chainId = network.chainId;
this.coinGecko = new CoinGeckoDataService(network.chainId);
}
}
}
8 changes: 8 additions & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import TradeAPI from './TradeAPI';
import NavIssuanceAPI from './NavIssuanceAPI';
import PriceOracleAPI from './PriceOracleAPI';
import DebtIssuanceAPI from './DebtIssuanceAPI';
import {
TradeQuoter,
CoinGeckoDataService,
GasOracleService
} from './utils';

export {
BlockchainAPI,
Expand All @@ -20,4 +25,7 @@ export {
NavIssuanceAPI,
PriceOracleAPI,
DebtIssuanceAPI,
TradeQuoter,
CoinGeckoDataService,
GasOracleService
};
Loading