fix: Remove coingecko-api-v3 library and de-dupe fetching utils (#4837)
### Description - Remove coingecko-api-v3 lib which is unnecessary and breaks bundling - Remove `getCoingeckoTokenPrices` function which is redundant with `CoinGeckoTokenPriceGetter` - Simplify `CoinGeckoTokenPriceGetter` and remove need for Mock class Related: https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/4787 See discussion here: https://discord.com/channels/935678348330434570/1304125818817220653 ### Drive-by changes - Fix bundling issue with Storybook in widgets lib ### Backward compatibility No ### Testing Rewrote unit tests and tested Storybooktrevor/disable-rarichain-rpc
parent
40d59a2f47
commit
5f41b11346
@ -0,0 +1,5 @@ |
||||
--- |
||||
'@hyperlane-xyz/sdk': major |
||||
--- |
||||
|
||||
Remove getCoingeckoTokenPrices (use CoinGeckoTokenPriceGetter instead) |
@ -1,51 +1,83 @@ |
||||
import { expect } from 'chai'; |
||||
import sinon from 'sinon'; |
||||
|
||||
import { ethereum, solanamainnet } from '@hyperlane-xyz/registry'; |
||||
|
||||
import { TestChainName, testChainMetadata } from '../consts/testChains.js'; |
||||
import { MockCoinGecko } from '../test/MockCoinGecko.js'; |
||||
|
||||
import { CoinGeckoTokenPriceGetter } from './token-prices.js'; |
||||
|
||||
const MOCK_FETCH_CALLS = true; |
||||
|
||||
describe('TokenPriceGetter', () => { |
||||
let tokenPriceGetter: CoinGeckoTokenPriceGetter; |
||||
let mockCoinGecko: MockCoinGecko; |
||||
const chainA = TestChainName.test1, |
||||
chainB = TestChainName.test2, |
||||
priceA = 1, |
||||
priceB = 5.5; |
||||
before(async () => { |
||||
mockCoinGecko = new MockCoinGecko(); |
||||
// Origin token
|
||||
mockCoinGecko.setTokenPrice(chainA, priceA); |
||||
// Destination token
|
||||
mockCoinGecko.setTokenPrice(chainB, priceB); |
||||
tokenPriceGetter = new CoinGeckoTokenPriceGetter( |
||||
mockCoinGecko, |
||||
testChainMetadata, |
||||
undefined, |
||||
0, |
||||
); |
||||
|
||||
const chainA = TestChainName.test1; |
||||
const chainB = TestChainName.test2; |
||||
const priceA = 2; |
||||
const priceB = 5; |
||||
let stub: sinon.SinonStub; |
||||
|
||||
beforeEach(() => { |
||||
tokenPriceGetter = new CoinGeckoTokenPriceGetter({ |
||||
chainMetadata: { ethereum, solanamainnet, ...testChainMetadata }, |
||||
apiKey: 'test', |
||||
expirySeconds: 10, |
||||
sleepMsBetweenRequests: 10, |
||||
}); |
||||
|
||||
describe('getTokenPrice', () => { |
||||
it('returns a token price', async () => { |
||||
expect(await tokenPriceGetter.getTokenPrice(chainA)).to.equal(priceA); |
||||
if (MOCK_FETCH_CALLS) { |
||||
stub = sinon |
||||
.stub(tokenPriceGetter, 'fetchPriceData') |
||||
.returns(Promise.resolve([priceA, priceB])); |
||||
} |
||||
}); |
||||
|
||||
afterEach(() => { |
||||
if (MOCK_FETCH_CALLS && stub) { |
||||
stub.restore(); |
||||
} |
||||
}); |
||||
|
||||
it('caches a token price', async () => { |
||||
mockCoinGecko.setFail(chainA, true); |
||||
expect(await tokenPriceGetter.getTokenPrice(chainA)).to.equal(priceA); |
||||
mockCoinGecko.setFail(chainA, false); |
||||
describe('getTokenPriceByIds', () => { |
||||
it('returns token prices', async () => { |
||||
// stubbed results
|
||||
expect( |
||||
await tokenPriceGetter.getTokenPriceByIds([ |
||||
ethereum.name, |
||||
solanamainnet.name, |
||||
]), |
||||
).to.eql([priceA, priceB]); |
||||
}); |
||||
}); |
||||
|
||||
describe('getTokenPrice', () => { |
||||
it('returns a token price', async () => { |
||||
// hardcoded result of 1 for testnets
|
||||
expect( |
||||
await tokenPriceGetter.getTokenPrice(TestChainName.test1), |
||||
).to.equal(1); |
||||
// stubbed result for non-testnet
|
||||
expect(await tokenPriceGetter.getTokenPrice(ethereum.name)).to.equal( |
||||
priceA, |
||||
); |
||||
}); |
||||
}); |
||||
|
||||
describe('getTokenExchangeRate', () => { |
||||
it('returns a value consistent with getTokenPrice()', async () => { |
||||
const exchangeRate = await tokenPriceGetter.getTokenExchangeRate( |
||||
chainA, |
||||
chainB, |
||||
); |
||||
// Should equal 1 because testnet prices are always forced to 1
|
||||
expect(exchangeRate).to.equal(1); |
||||
// hardcoded result of 1 for testnets
|
||||
expect( |
||||
await tokenPriceGetter.getTokenExchangeRate(chainA, chainB), |
||||
).to.equal(1); |
||||
|
||||
// stubbed result for non-testnet
|
||||
expect( |
||||
await tokenPriceGetter.getTokenExchangeRate( |
||||
ethereum.name, |
||||
solanamainnet.name, |
||||
), |
||||
).to.equal(priceA / priceB); |
||||
}); |
||||
}); |
||||
}); |
||||
|
@ -1,47 +0,0 @@ |
||||
import { SimplePriceResponse } from 'coingecko-api-v3'; |
||||
|
||||
import type { |
||||
CoinGeckoInterface, |
||||
CoinGeckoResponse, |
||||
CoinGeckoSimplePriceInterface, |
||||
CoinGeckoSimplePriceParams, |
||||
} from '../gas/token-prices.js'; |
||||
import type { ChainName } from '../types.js'; |
||||
|
||||
// A mock CoinGecko intended to be used by tests
|
||||
export class MockCoinGecko implements CoinGeckoInterface { |
||||
// Prices keyed by coingecko id
|
||||
private tokenPrices: Record<string, number>; |
||||
// Whether or not to fail to return a response, keyed by coingecko id
|
||||
private fail: Record<string, boolean>; |
||||
|
||||
constructor() { |
||||
this.tokenPrices = {}; |
||||
this.fail = {}; |
||||
} |
||||
|
||||
price(input: CoinGeckoSimplePriceParams): CoinGeckoResponse { |
||||
const data: SimplePriceResponse = {}; |
||||
for (const id of input.ids) { |
||||
if (this.fail[id]) { |
||||
return Promise.reject(`Failed to fetch price for ${id}`); |
||||
} |
||||
data[id] = { |
||||
usd: this.tokenPrices[id], |
||||
}; |
||||
} |
||||
return Promise.resolve(data); |
||||
} |
||||
|
||||
get simplePrice(): CoinGeckoSimplePriceInterface { |
||||
return this.price; |
||||
} |
||||
|
||||
setTokenPrice(chain: ChainName, price: number): void { |
||||
this.tokenPrices[chain] = price; |
||||
} |
||||
|
||||
setFail(chain: ChainName, fail: boolean): void { |
||||
this.fail[chain] = fail; |
||||
} |
||||
} |
Loading…
Reference in new issue