Skip to content

Commit 7aeac75

Browse files
authored
Merge pull request #60 from SetProtocol/chris/trade_quote_util
Add trade quote utility
2 parents 75b5376 + 4c81300 commit 7aeac75

22 files changed

+2232
-6
lines changed

package.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "set.js",
3-
"version": "0.1.2",
3+
"version": "0.2.6",
44
"description": "A javascript library for interacting with the Set Protocol v2",
55
"keywords": [
66
"set.js",
@@ -16,10 +16,12 @@
1616
"scripts": {
1717
"prod": "yarn run build",
1818
"chain": "bash scripts/init_chain_internal.sh",
19-
"build": "yarn run build-dist && yarn run tslint && tspath -f",
19+
"build": "yarn run clean && yarn run build-dist && yarn run tslint && tspath -f",
2020
"build-ts": "tsc -p tsconfig.json",
2121
"build-dist": "tsc -p tsconfig.dist.json",
22+
"clean": "rm -rf dist",
2223
"test": "jest --runInBand",
24+
"test:verbose": "jest --runInBand --silent=false",
2325
"test:watch": "jest --watch --runInBand",
2426
"tslint": "tslint -c tslint.json -p tsconfig.json",
2527
"precommit": "lint-staged",
@@ -63,11 +65,14 @@
6365
"@types/jest": "^26.0.5",
6466
"@types/web3": "^1.2.2",
6567
"abi-decoder": "^2.3.0",
68+
"axios": "^0.21.1",
6669
"bignumber.js": "^9.0.0",
6770
"dotenv": "^8.2.0",
6871
"ethereum-types": "^3.2.0",
6972
"ethereumjs-util": "^7.0.3",
7073
"ethers": "^5.0.3",
74+
"graph-results-pager": "^1.0.3",
75+
"js-big-decimal": "^1.3.4",
7176
"jsonschema": "^1.2.6",
7277
"lodash": "^4.17.19",
7378
"truffle": "^5.1.35",

src/Set.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ class Set {
123123
assertions
124124
);
125125
this.system = new SystemAPI(ethersProvider, config.controllerAddress);
126-
this.trade = new TradeAPI(ethersProvider, config.tradeModuleAddress);
126+
this.trade = new TradeAPI(ethersProvider, config.tradeModuleAddress, config.zeroExApiKey);
127127
this.navIssuance = new NavIssuanceAPI(ethersProvider, config.navIssuanceModuleAddress);
128128
this.priceOracle = new PriceOracleAPI(ethersProvider, config.masterOracleAddress);
129129
this.debtIssuance = new DebtIssuanceAPI(ethersProvider, config.debtIssuanceModuleAddress);

src/api/TradeAPI.ts

Lines changed: 149 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,23 @@ import { TransactionOverrides } from '@setprotocol/set-protocol-v2/dist/typechai
2323
import { BigNumber } from 'ethers/lib/ethers';
2424

2525
import TradeModuleWrapper from '../wrappers/set-protocol-v2/TradeModuleWrapper';
26+
import SetTokenAPI from './SetTokenAPI';
2627
import Assertions from '../assertions';
2728

29+
import {
30+
TradeQuoter,
31+
CoinGeckoDataService,
32+
GasOracleService
33+
} from './utils';
34+
35+
import {
36+
TradeQuote,
37+
CoinGeckoTokenData,
38+
CoinGeckoTokenMap,
39+
GasOracleSpeed,
40+
CoinGeckoCoinPrices
41+
} from '../types';
42+
2843
/**
2944
* @title TradeAPI
3045
* @author Set Protocol
@@ -36,14 +51,20 @@ import Assertions from '../assertions';
3651
export default class TradeAPI {
3752
private tradeModuleWrapper: TradeModuleWrapper;
3853
private assert: Assertions;
54+
private provider: Provider;
55+
private tradeQuoter: TradeQuoter;
56+
private coinGecko: CoinGeckoDataService;
57+
private chainId: number;
3958

4059
public constructor(
4160
provider: Provider,
4261
tradeModuleAddress: Address,
43-
assertions?: Assertions
62+
zeroExApiKey?: string,
4463
) {
64+
this.provider = provider;
4565
this.tradeModuleWrapper = new TradeModuleWrapper(provider, tradeModuleAddress);
46-
this.assert = assertions || new Assertions();
66+
this.assert = new Assertions();
67+
this.tradeQuoter = new TradeQuoter(zeroExApiKey);
4768
}
4869

4970
/**
@@ -113,4 +134,130 @@ export default class TradeAPI {
113134
txOpts
114135
);
115136
}
137+
138+
/**
139+
* Call 0x API to generate a trade quote for two SetToken components.
140+
*
141+
* @param fromToken Address of token being sold
142+
* @param toToken Address of token being bought
143+
* @param fromTokenDecimals Token decimals of token being sold (ex: 18)
144+
* @param toTokenDecimals Token decimals of token being bought (ex: 18)
145+
* @param rawAmount String quantity of token to sell (ex: "0.5")
146+
* @param fromAddress SetToken address which holds the buy / sell components
147+
* @param setToken SetTokenAPI instance
148+
* @param gasPrice (Optional) gasPrice to calculate gas costs with (Default: fetched from GasNow)
149+
* @param slippagePercentage (Optional) maximum slippage, determines min receive quantity. (Default: 2%)
150+
* @param isFirmQuote (Optional) Whether quote request is indicative or firm
151+
* @param feePercentage (Optional) Default: 0
152+
* @param feeRecipient (Optional) Default: 0xD3D555Bb655AcBA9452bfC6D7cEa8cC7b3628C55
153+
* @param excludedSources (Optional) Exchanges to exclude (Default: ['Kyber', 'Eth2Dai', 'Uniswap', 'Mesh'])
154+
*
155+
* @return {Promise<TradeQuote>}
156+
*/
157+
public async fetchTradeQuoteAsync(
158+
fromToken: Address,
159+
toToken: Address,
160+
fromTokenDecimals: number,
161+
toTokenDecimals: number,
162+
rawAmount: string,
163+
fromAddress: Address,
164+
setToken: SetTokenAPI,
165+
gasPrice?: number,
166+
slippagePercentage?: number,
167+
isFirmQuote?: boolean,
168+
feePercentage?: number,
169+
feeRecipient?: Address,
170+
excludedSources?: string[],
171+
): Promise<TradeQuote> {
172+
this.assert.schema.isValidAddress('fromToken', fromToken);
173+
this.assert.schema.isValidAddress('toToken', toToken);
174+
this.assert.schema.isValidAddress('fromAddress', fromAddress);
175+
this.assert.schema.isValidJsNumber('fromTokenDecimals', fromTokenDecimals);
176+
this.assert.schema.isValidJsNumber('toTokenDecimals', toTokenDecimals);
177+
this.assert.schema.isValidString('rawAmount', rawAmount);
178+
179+
const chainId = (await this.provider.getNetwork()).chainId;
180+
181+
return this.tradeQuoter.generate({
182+
fromToken,
183+
toToken,
184+
fromTokenDecimals,
185+
toTokenDecimals,
186+
rawAmount,
187+
fromAddress,
188+
chainId,
189+
tradeModule: this.tradeModuleWrapper,
190+
provider: this.provider,
191+
setToken,
192+
gasPrice,
193+
slippagePercentage,
194+
isFirmQuote,
195+
feePercentage,
196+
feeRecipient,
197+
excludedSources,
198+
});
199+
}
200+
201+
/**
202+
* Fetches a list of tokens and their metadata from CoinGecko. Each entry includes
203+
* the token's address, proper name, decimals, exchange symbol and a logo URI if available.
204+
* For Ethereum, this is a list of tokens tradeable on Uniswap, for Polygon it's a list of
205+
* tokens tradeable on Sushiswap's Polygon exchange. Method is useful for acquiring token decimals
206+
* necessary to generate a trade quote and images for representing available tokens in a UI.
207+
*
208+
* @return List of tradeable tokens for chain platform
209+
*/
210+
public async fetchTokenListAsync(): Promise<CoinGeckoTokenData[]> {
211+
await this.initializeForChain();
212+
return this.coinGecko.fetchTokenList();
213+
}
214+
215+
/**
216+
* Fetches the same info as `fetchTokenList` in the form of a map indexed by address. Method is
217+
* useful if you're cacheing the token list and want quick lookups for a variety of trades.
218+
*
219+
* @return Map of token addresses to token metadata
220+
*/
221+
public async fetchTokenMapAsync(): Promise<CoinGeckoTokenMap> {
222+
await this.initializeForChain();
223+
return this.coinGecko.fetchTokenMap();
224+
}
225+
226+
/**
227+
* Fetches a list of prices vs currencies for the specified inputs from CoinGecko
228+
*
229+
* @param contractAddresses String array of contract addresses
230+
* @param vsCurrencies String array of currency codes (see CoinGecko api for a complete list)
231+
*
232+
* @return List of prices vs currencies
233+
*/
234+
public async fetchCoinPricesAsync(
235+
contractAddresses: string[],
236+
vsCurrencies: string[]
237+
): Promise<CoinGeckoCoinPrices> {
238+
await this.initializeForChain();
239+
return this.coinGecko.fetchCoinPrices({contractAddresses, vsCurrencies});
240+
}
241+
242+
/**
243+
* Fetches the recommended gas price for a specified execution speed.
244+
*
245+
* @param speed (Optional) string value: "average" | "fast" | "fastest" (Default: fast)
246+
*
247+
* @return Number: gas price
248+
*/
249+
public async fetchGasPriceAsync(speed?: GasOracleSpeed): Promise<number> {
250+
await this.initializeForChain();
251+
const oracle = new GasOracleService(this.chainId);
252+
return oracle.fetchGasPrice(speed);
253+
}
254+
255+
256+
private async initializeForChain() {
257+
if (this.coinGecko === undefined) {
258+
const network = await this.provider.getNetwork();
259+
this.chainId = network.chainId;
260+
this.coinGecko = new CoinGeckoDataService(network.chainId);
261+
}
262+
}
116263
}

src/api/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ import TradeAPI from './TradeAPI';
88
import NavIssuanceAPI from './NavIssuanceAPI';
99
import PriceOracleAPI from './PriceOracleAPI';
1010
import DebtIssuanceAPI from './DebtIssuanceAPI';
11+
import {
12+
TradeQuoter,
13+
CoinGeckoDataService,
14+
GasOracleService
15+
} from './utils';
1116

1217
export {
1318
BlockchainAPI,
@@ -20,4 +25,7 @@ export {
2025
NavIssuanceAPI,
2126
PriceOracleAPI,
2227
DebtIssuanceAPI,
28+
TradeQuoter,
29+
CoinGeckoDataService,
30+
GasOracleService
2331
};

0 commit comments

Comments
 (0)