Skip to content

Commit 88a5e7f

Browse files
authored
Support Optimism in Set.js (#96)
* Support Optimism in Set.js * Use Polygon's Coingecko tokenlist instead of constructing a manual mapping * Tweak tests to use new Polygon coingecko endpoint * Use Optimism explorer's API endpoint instead of provider gas price
1 parent 5e086be commit 88a5e7f

File tree

10 files changed

+30
-226
lines changed

10 files changed

+30
-226
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "set.js",
3-
"version": "0.4.4",
3+
"version": "0.4.5",
44
"description": "A javascript library for interacting with the Set Protocol v2",
55
"keywords": [
66
"set.js",

src/api/utils/coingecko.ts

Lines changed: 11 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,14 @@
1616

1717
'use strict';
1818

19-
const pageResults = require('graph-results-pager');
20-
2119
import axios from 'axios';
2220
import Assertions from '../../assertions';
2321

2422
import {
2523
CoinGeckoCoinPrices,
2624
CoinGeckoTokenData,
27-
SushiswapTokenData,
2825
CoinGeckoTokenMap,
2926
CoinPricesParams,
30-
PolygonMappedTokenData
3127
} from '../../types';
3228

3329
/**
@@ -95,14 +91,8 @@ export class CoinGeckoDataService {
9591
async fetchTokenList(): Promise<CoinGeckoTokenData[]> {
9692
if (this.tokenList !== undefined) return this.tokenList;
9793

98-
switch (this.chainId) {
99-
case 1:
100-
this.tokenList = await this.fetchEthereumTokenList();
101-
break;
102-
case 137:
103-
this.tokenList = await this.fetchPolygonTokenList();
104-
break;
105-
}
94+
const url = this.getCoingeckoUrl();
95+
this.tokenList = await this.fetchCoingeckoTokenList(url);
10696
this.tokenMap = this.convertTokenListToAddressMap(this.tokenList);
10797

10898
return this.tokenList!;
@@ -135,107 +125,22 @@ export class CoinGeckoDataService {
135125
private getPlatform(): string {
136126
switch (this.chainId) {
137127
case 1: return 'ethereum';
128+
case 10: return 'optimistic-ethereum';
138129
case 137: return 'polygon-pos';
139130
default: return '';
140131
}
141132
}
142133

143-
private async fetchEthereumTokenList(): Promise<CoinGeckoTokenData[]> {
144-
const url = 'https://tokens.coingecko.com/uniswap/all.json';
145-
const response = await axios.get(url);
146-
return response.data.tokens;
147-
}
148-
149-
private async fetchPolygonTokenList(): Promise<CoinGeckoTokenData[]> {
150-
const coingeckoEthereumTokens = await this.fetchEthereumTokenList();
151-
const polygonMappedTokens = await this.fetchPolygonMappedTokenList();
152-
const sushiPolygonTokenList = await this.fetchSushiPolygonTokenList();
153-
const quickswapPolygonTokenList = await this.fetchQuickswapPolygonTokenList();
154-
155-
for (const token of sushiPolygonTokenList) {
156-
const quickswapToken = quickswapPolygonTokenList.find(t => t.address.toLowerCase() === token.address);
157-
158-
if (quickswapToken) {
159-
token.logoURI = quickswapToken.logoURI;
160-
continue;
161-
}
162-
163-
const ethereumAddress = polygonMappedTokens[token.address];
164-
165-
if (ethereumAddress !== undefined) {
166-
const ethereumToken = coingeckoEthereumTokens.find(t => t.address.toLowerCase() === ethereumAddress);
167-
168-
if (ethereumToken) {
169-
token.logoURI = ethereumToken.logoURI;
170-
}
171-
}
172-
}
173-
174-
return sushiPolygonTokenList;
175-
}
176-
177-
private async fetchSushiPolygonTokenList() {
178-
let tokens: SushiswapTokenData[] = [];
179-
const url = 'https://api.thegraph.com/subgraphs/name/sushiswap/matic-exchange';
180-
const properties = [
181-
'id',
182-
'symbol',
183-
'name',
184-
'decimals',
185-
'volumeUSD',
186-
];
187-
188-
const response = await pageResults({
189-
api: url,
190-
query: {
191-
entity: 'tokens',
192-
properties: properties,
193-
},
194-
});
195-
196-
for (const token of response) {
197-
tokens.push({
198-
chainId: 137,
199-
address: token.id,
200-
symbol: token.symbol,
201-
name: token.name,
202-
decimals: parseInt(token.decimals),
203-
volumeUSD: parseFloat(token.volumeUSD),
204-
});
205-
}
206-
207-
// Sort by volume and filter out untraded tokens
208-
tokens.sort((a, b) => b.volumeUSD - a.volumeUSD);
209-
tokens = tokens.filter(t => t.volumeUSD > 0);
210-
211-
return tokens;
212-
}
213-
214-
private async fetchPolygonMappedTokenList() {
215-
const tokens: PolygonMappedTokenData = {};
216-
const url = 'https://api.thegraph.com/subgraphs/name/maticnetwork/mainnet-root-subgraphs';
217-
const properties = ['id', 'rootToken', 'childToken'];
218-
219-
const response = await pageResults({
220-
api: url,
221-
query: {
222-
entity: 'tokenMappings',
223-
properties: properties,
224-
},
225-
});
226-
227-
for (const tokenMapping of response) {
228-
tokens[tokenMapping.childToken.toLowerCase()] = tokenMapping.rootToken.toLowerCase();
134+
private getCoingeckoUrl(): string {
135+
switch (this.chainId) {
136+
case 1: return 'https://tokens.coingecko.com/uniswap/all.json';
137+
case 10: return 'https://tokens.coingecko.com/optimistic-ethereum/all.json';
138+
case 137: return 'https://tokens.coingecko.com/polygon-pos/all.json';
229139
}
230-
231-
return tokens;
232140
}
233141

234-
private async fetchQuickswapPolygonTokenList(): Promise<CoinGeckoTokenData[]> {
235-
const url = 'https://raw.githubusercontent.com/sameepsi/' +
236-
'quickswap-default-token-list/master/src/tokens/mainnet.json';
237-
238-
const data = (await axios.get(url)).data;
239-
return data;
142+
private async fetchCoingeckoTokenList(url: string): Promise<CoinGeckoTokenData[]> {
143+
const response = await axios.get(url);
144+
return response.data.tokens;
240145
}
241146
}

src/api/utils/gasOracle.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export class GasOracleService {
5555

5656
switch (this.chainId) {
5757
case 1: return this.getEthereumGasPrice(speed);
58+
case 10: return this.getOptimismGasPrice();
5859
case 137: return this.getPolygonGasPrice(speed);
5960

6061
// This case should never run because chainId is validated
@@ -75,6 +76,14 @@ export class GasOracleService {
7576
}
7677
}
7778

79+
private async getOptimismGasPrice(): Promise<number> {
80+
const url = 'https://api-optimistic.etherscan.io/api?module=proxy&action=eth_gasPrice';
81+
const data = (await axios.get(url)).data;
82+
const price = Number(data.result);
83+
84+
return price;
85+
}
86+
7887
private async getPolygonGasPrice(speed: GasOracleSpeed): Promise<number> {
7988
const url = 'https://gasstation-mainnet.matic.network';
8089
const data = (await axios.get(url)).data;

src/api/utils/tradeQuoter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ export class TradeQuoter {
338338
private chainCurrencyAddress(chainId: number): Address {
339339
switch (chainId) {
340340
case 1: return '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; // WETH
341+
case 10: return '0x4200000000000000000000000000000000000006'; // Optimism WETH
341342
case 137: return '0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270'; // WMATIC
342343
default: throw new Error(`chainId: ${chainId} is not supported`);
343344
}

src/api/utils/zeroex.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ export class ZeroExTradeQuoter {
114114
private getHostForChain(chainId: number) {
115115
switch (chainId) {
116116
case 1: return 'https://api.0x.org';
117+
case 10: return 'https://optimism.api.0x.org';
117118
case 137: return 'https://polygon.api.0x.org';
118119
}
119120
}

src/assertions/CommonAssertions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ export class CommonAssertions {
123123
}
124124

125125
public isSupportedChainId(chainId: number) {
126-
const validChainIds = [1, 137];
126+
const validChainIds = [1, 10, 137];
127127

128128
if ( !validChainIds.includes(chainId)) {
129129
throw new Error(`Unsupported chainId: ${chainId}. Must be one of ${validChainIds}`);

src/types/utils.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@ export type CoinGeckoTokenData = {
2121
logoURI?: string,
2222
};
2323

24-
export type SushiswapTokenData = CoinGeckoTokenData & {
25-
volumeUSD: number
26-
};
27-
2824
export type CoinGeckoTokenMap = {
2925
[key: string]: CoinGeckoTokenData
3026
};
@@ -34,10 +30,6 @@ export type CoinPricesParams = {
3430
vsCurrencies: string[]
3531
};
3632

37-
export type PolygonMappedTokenData = {
38-
[key: string]: string,
39-
};
40-
4133
export type QuoteOptions = {
4234
fromToken: Address,
4335
toToken: Address,

test/api/TradeAPI.spec.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616

1717
import axios from 'axios';
18-
const pageResults = require('graph-results-pager');
1918

2019
import { ethers, ContractTransaction } from 'ethers';
2120
import { BigNumber } from 'ethers/lib/ethers';
@@ -57,14 +56,6 @@ axios.get.mockImplementation(val => {
5756
case fixture.coinGeckoTokenRequestPoly: return fixture.coinGeckoTokenResponsePoly;
5857
case fixture.coinGeckoPricesRequestEth: return fixture.coinGeckoPricesResponseEth;
5958
case fixture.coinGeckoPricesRequestPoly: return fixture.coinGeckoPricesResponsePoly;
60-
case fixture.quickswapRequestPoly: return fixture.quickswapResponsePoly;
61-
}
62-
});
63-
64-
pageResults.mockImplementation(val => {
65-
switch (val.api) {
66-
case fixture.sushiSubgraphRequestPoly: return fixture.sushiSubgraphResponsePoly;
67-
case fixture.maticMappingSubgraphRequestPoly: return fixture.maticMappingSubgraphResponsePoly;
6859
}
6960
});
7061

@@ -360,7 +351,7 @@ describe('TradeAPI', () => {
360351

361352
it('should fetch correct token data for network', async() => {
362353
const tokenData = await subject();
363-
await expect(tokenData).to.deep.equal(fixture.fetchTokenListResponsePoly);
354+
await expect(tokenData).to.deep.equal(fixture.coinGeckoTokenResponsePoly.data.tokens);
364355
});
365356
});
366357

@@ -402,7 +393,7 @@ describe('TradeAPI', () => {
402393

403394
describe('when the chain is polygon (137)', () => {
404395
beforeEach(async () => {
405-
subjectChainId = 1;
396+
subjectChainId = 137;
406397
provider.getNetwork = jest.fn(() => Promise.resolve(<unknown>{ chainId: subjectChainId } as Network ));
407398
subjectCoinGecko = new CoinGeckoDataService(subjectChainId);
408399
subjectTokenList = await tradeAPI.fetchTokenListAsync();

test/api/TradeQuoter.spec.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import TradeModuleWrapper from '@src/wrappers/set-protocol-v2/TradeModuleWrapper
2323
import { TradeQuoter, CoinGeckoDataService } from '@src/api/utils';
2424
import { tradeQuoteFixtures as fixture } from '../fixtures/tradeQuote';
2525
import { expect } from '@test/utils/chai';
26-
const pageResults = require('graph-results-pager');
2726

2827
const provider = new ethers.providers.JsonRpcProvider('http://localhost:8545');
2928

@@ -57,14 +56,6 @@ axios.get.mockImplementation(val => {
5756
case fixture.coinGeckoTokenRequestPoly: return fixture.coinGeckoTokenResponsePoly;
5857
case fixture.coinGeckoPricesRequestEth: return fixture.coinGeckoPricesResponseEth;
5958
case fixture.coinGeckoPricesRequestPoly: return fixture.coinGeckoPricesResponsePoly;
60-
case fixture.quickswapRequestPoly: return fixture.quickswapResponsePoly;
61-
}
62-
});
63-
64-
pageResults.mockImplementation(val => {
65-
switch (val.api) {
66-
case fixture.sushiSubgraphRequestPoly: return fixture.sushiSubgraphResponsePoly;
67-
case fixture.maticMappingSubgraphRequestPoly: return fixture.maticMappingSubgraphResponsePoly;
6859
}
6960
});
7061

0 commit comments

Comments
 (0)