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 { expect } from 'chai'; |
||||||
|
import sinon from 'sinon'; |
||||||
|
|
||||||
|
import { ethereum, solanamainnet } from '@hyperlane-xyz/registry'; |
||||||
|
|
||||||
import { TestChainName, testChainMetadata } from '../consts/testChains.js'; |
import { TestChainName, testChainMetadata } from '../consts/testChains.js'; |
||||||
import { MockCoinGecko } from '../test/MockCoinGecko.js'; |
|
||||||
|
|
||||||
import { CoinGeckoTokenPriceGetter } from './token-prices.js'; |
import { CoinGeckoTokenPriceGetter } from './token-prices.js'; |
||||||
|
|
||||||
|
const MOCK_FETCH_CALLS = true; |
||||||
|
|
||||||
describe('TokenPriceGetter', () => { |
describe('TokenPriceGetter', () => { |
||||||
let tokenPriceGetter: CoinGeckoTokenPriceGetter; |
let tokenPriceGetter: CoinGeckoTokenPriceGetter; |
||||||
let mockCoinGecko: MockCoinGecko; |
|
||||||
const chainA = TestChainName.test1, |
const chainA = TestChainName.test1; |
||||||
chainB = TestChainName.test2, |
const chainB = TestChainName.test2; |
||||||
priceA = 1, |
const priceA = 2; |
||||||
priceB = 5.5; |
const priceB = 5; |
||||||
before(async () => { |
let stub: sinon.SinonStub; |
||||||
mockCoinGecko = new MockCoinGecko(); |
|
||||||
// Origin token
|
beforeEach(() => { |
||||||
mockCoinGecko.setTokenPrice(chainA, priceA); |
tokenPriceGetter = new CoinGeckoTokenPriceGetter({ |
||||||
// Destination token
|
chainMetadata: { ethereum, solanamainnet, ...testChainMetadata }, |
||||||
mockCoinGecko.setTokenPrice(chainB, priceB); |
apiKey: 'test', |
||||||
tokenPriceGetter = new CoinGeckoTokenPriceGetter( |
expirySeconds: 10, |
||||||
mockCoinGecko, |
sleepMsBetweenRequests: 10, |
||||||
testChainMetadata, |
}); |
||||||
undefined, |
|
||||||
0, |
if (MOCK_FETCH_CALLS) { |
||||||
); |
stub = sinon |
||||||
|
.stub(tokenPriceGetter, 'fetchPriceData') |
||||||
|
.returns(Promise.resolve([priceA, priceB])); |
||||||
|
} |
||||||
}); |
}); |
||||||
|
|
||||||
describe('getTokenPrice', () => { |
afterEach(() => { |
||||||
it('returns a token price', async () => { |
if (MOCK_FETCH_CALLS && stub) { |
||||||
expect(await tokenPriceGetter.getTokenPrice(chainA)).to.equal(priceA); |
stub.restore(); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
describe('getTokenPriceByIds', () => { |
||||||
|
it('returns token prices', async () => { |
||||||
|
// stubbed results
|
||||||
|
expect( |
||||||
|
await tokenPriceGetter.getTokenPriceByIds([ |
||||||
|
ethereum.name, |
||||||
|
solanamainnet.name, |
||||||
|
]), |
||||||
|
).to.eql([priceA, priceB]); |
||||||
}); |
}); |
||||||
|
}); |
||||||
|
|
||||||
it('caches a token price', async () => { |
describe('getTokenPrice', () => { |
||||||
mockCoinGecko.setFail(chainA, true); |
it('returns a token price', async () => { |
||||||
expect(await tokenPriceGetter.getTokenPrice(chainA)).to.equal(priceA); |
// hardcoded result of 1 for testnets
|
||||||
mockCoinGecko.setFail(chainA, false); |
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', () => { |
describe('getTokenExchangeRate', () => { |
||||||
it('returns a value consistent with getTokenPrice()', async () => { |
it('returns a value consistent with getTokenPrice()', async () => { |
||||||
const exchangeRate = await tokenPriceGetter.getTokenExchangeRate( |
// hardcoded result of 1 for testnets
|
||||||
chainA, |
expect( |
||||||
chainB, |
await tokenPriceGetter.getTokenExchangeRate(chainA, chainB), |
||||||
); |
).to.equal(1); |
||||||
// Should equal 1 because testnet prices are always forced to 1
|
|
||||||
expect(exchangeRate).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