-
Notifications
You must be signed in to change notification settings - Fork 11
LW-12681 Coingecko token price API #1867
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
Conversation
Allure Report
processReports: ✅ test report for 3174dc33
|
fcb3a0b
to
8e077d9
Compare
8e077d9
to
f52263f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great Job!
() => ({ | ||
getTokenPrice: (assetId: Wallet.Cardano.AssetId): TokenPrice | undefined => { | ||
const tokenPrice = tokenPrices?.tokens?.get(assetId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const tokenPrice = tokenPrices?.tokens?.get(assetId); | |
const tokenPrice = tokenPrices.tokens.get(assetId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
e101d0c removes the second question mark; removing the first one as well makes the extension to not load.
const tokenPrice = tokenPrices?.tokens?.get(assetId); | ||
// Actually track the price only for token in Cardano mainnet, otherwise just do nothing | ||
const trackPrice = () => | ||
store && store.currentChain && store.currentChain.networkId === Wallet.Cardano.NetworkId.Mainnet |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
store && store.currentChain && store.currentChain.networkId === Wallet.Cardano.NetworkId.Mainnet | |
store.currentChain.networkId === Wallet.Cardano.NetworkId.Mainnet |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressing this comment I did a refactoring which changes the layout of the same check.
Anyway (as far as I understood) we still need to check currentChain
to be defined as it could be not in case the wallet is in bitcoin mode.
// Actually track the price only for token in Cardano mainnet, otherwise just do nothing | ||
const trackPrice = () => | ||
store && store.currentChain && store.currentChain.networkId === Wallet.Cardano.NetworkId.Mainnet | ||
? trackCardanoTokenPrice(assetId).catch(console.error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use a logger instead of the console in all the occurences.
import { logger } from '@lace/common';
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
e101d0c should solve this
: // eslint-disable-next-line @typescript-eslint/no-empty-function | ||
() => {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A nitpick. Feel free to ignore.
: // eslint-disable-next-line @typescript-eslint/no-empty-function | |
() => {}; | |
: () => void 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressing this comment I did a little refactoring which makes this comment no longer required.
apps/browser-extension-wallet/src/lib/scripts/background/services/cardanoTokenPrices.ts
Show resolved
Hide resolved
import { config } from '@src/config'; | ||
|
||
/** Shortcut for the token prices subject type. */ | ||
type TokenPriceSubject = Subject<{ tokens: TokenPrices } & Status>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type TokenPriceSubject = Subject<{ tokens: TokenPrices } & Status>; | |
type TokenPriceSubject = BehaviorSubject<{ tokens: TokenPrices } & Status>; |
This way you don't need the local priceData variable because you can access the last emited value: priceSubject$.value
;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just an interface: the Observable
used in the implementation is (ATM) actually a BehaviorSubject
.
In case in the future we need to change the actual implementation, using the ancestor class Subject
reduces the chances we need to change this interface type as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I was not clear enough. I meant:
// have a local BehaviorSubject
const price$ = new BehaviorSubject<{ tokens: TokenPrices } & Status>();
...
const updatePriceData = (assetId: string, data?: PriceData) => {
const now = Date.now();
const nextPriceData = {
...price$.value, // access current value from the subject
[assetId]: data ? [now, data] : [now],
};
emitPrices(nextPriceData);
...
const initCardanoTokenPrices = () => {
storage.local
.get(CACHE_KEY)
.then((data) => {
const initialPriceData = data[CACHE_KEY] || {};
emitPrices(initialPriceData);
fetchPrices();
return price$; // expose local subject
};
Then it could be assigned to coinPrices.tokenSubject$ in utilityService.ts
const coinPrices: CoinPrices = {
tokenPrices$: initCardanoTokenPrices(),
};
But again, this is just a suggestion. I am fine with either way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice suggestion! Thank you.
5b33fc6 should solve this.
updatePriceData(assetId, cachedData); | ||
|
||
try { | ||
const url = `https://coingecko.live-mainnet.eks.lw.iog.io/api/v3/onchain/networks/cardano/tokens/${assetId}/pools`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider providing this URL via env variable
const url = `https://coingecko.live-mainnet.eks.lw.iog.io/api/v3/onchain/networks/cardano/tokens/${assetId}/pools`; | |
const url = `${process.env.TOKEN_PRICES_URL}/${assetId}/pools`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
e101d0c should solve this
apps/browser-extension-wallet/src/lib/scripts/background/services/cardanoTokenPrices.ts
Show resolved
Hide resolved
setTimeout(fetchPrices, TOKEN_PRICE_CHECK_INTERVAL); | ||
}; | ||
|
||
export const initCardanoTokenPrices = (tokenPrices$: TokenPriceSubject) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider returning the local priceSubject$
instead of passing it from outside. This way the coinPrices.tokenPrices$
will get the proper types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is any locally defined priceSubject$
, the only one defined is tokenSubject$
in utilityService.ts
and it is defined as property of const coinPrices
. The local variable priceSubject$
is just a reference to coinPrices.tokenSubject$
from utilityService.ts
.
The reasons why I chosen this approach is to isolate the token price stuff in a dedicated file rather than implementing everything inside utilityService.ts
which is already more than 300 lines.
We can evaluate importing the content of cardanoTokenPrices.ts
into utilityService.ts
, that would significantly simplify this point and some other; probably the type discussed in this comment will be completely removed as well.
const trackPrice = () => | ||
store && store.currentChain && store.currentChain.networkId === Wallet.Cardano.NetworkId.Mainnet | ||
? trackCardanoTokenPrice(assetId).catch(console.error) | ||
: // eslint-disable-next-line @typescript-eslint/no-empty-function | ||
() => {}; | ||
|
||
// If the price for this token was never fetched, wee need to track it | ||
if (!tokenPrice) { | ||
trackPrice(); | ||
|
||
return undefined; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a suggestion. I think it would be more readable written as below. Currently you return an empty function or a promise - not sure why empty function instead of e.g. undefined
(this empty/noop function is not getting called later).
edit: Part of this check will be even simpler networkId === Wallet.Cardano.NetworkId.Mainnet
when my previous comment is applied
const trackPrice = () => | |
store && store.currentChain && store.currentChain.networkId === Wallet.Cardano.NetworkId.Mainnet | |
? trackCardanoTokenPrice(assetId).catch(console.error) | |
: // eslint-disable-next-line @typescript-eslint/no-empty-function | |
() => {}; | |
// If the price for this token was never fetched, wee need to track it | |
if (!tokenPrice) { | |
trackPrice(); | |
return undefined; | |
} | |
// If the price for this token was never fetched, wee need to track it | |
// but only for token in Cardano mainnet | |
if (!tokenPrice && store?.currentChain?.networkId === Wallet.Cardano.NetworkId.Mainnet) { | |
trackCardanoTokenPrice(assetId).catch(console.error) | |
return undefined; | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function trackPrice
is called twice, next call is a few lines later.
I chosen this approach to reduce the code repetition as much as possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know you call trackPrice
, but not the function it returns here in line 60, so why do you need to return a function instead of e.g. undefined
? Also this will be greatly simplified with #1867 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mhh that is not correct.
trackPrice
is a function which has trackCardanoTokenPrice(assetId).catch(console.error)
as body if the wallet is in Cardano mainnet
or an empty body in other networks: i.e. the function returned in line 60 is called (when the wallet is in preprod
, preview
or any bitcoin network)
Edit: please ignore this message, it is completely wrong
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for insisting @przemyslaw-wlodek !
My previous message is probably a bias due to some previous implementations I did...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
e101d0c should solve this
const tokenPrices = useObservable(coinPrices.tokenPrices$); | ||
const adaPrices = useObservable(coinPrices.adaPrices$); | ||
const bitcoinPrices = useObservable(coinPrices.bitcoinPrices$); | ||
const store = useWalletStore(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please consider using a selector. You're passing whole store as a dependency for useMemo
which will constantly recompute.
const store = useWalletStore(); | |
const networkId = useWalletStore(state => state.currentChain?.networkId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
e101d0c should solve this
price: isAdaCurrency ? 1 : adaPrices?.prices?.[fiatCurrency.code.toLowerCase() as ADAPricesKeys], | ||
priceVariationPercentage24h: | ||
adaPrices?.prices?.[`${fiatCurrency.code.toLowerCase()}_24h_change` as ADAPricesKeys] || 0 | ||
}), | ||
[adaPrices?.prices, fiatCurrency.code, isAdaCurrency] | ||
[adaPrices?.prices, fiatCurrency.code, isAdaCurrency, tokenPrices?.tokens, trackCardanoTokenPrice, store] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As described above, please pass just networkId
to dependencies (instead of store
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
e101d0c should solve this
const [lastFetchTime, cachedData] = priceData[assetId] || []; | ||
|
||
// If recently fetched, do nothing | ||
if (typeof lastFetchTime === 'number' && lastFetchTime > Date.now() - TOKEN_PRICE_CHECK_INTERVAL) return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a suggestion:
const [lastFetchTime, cachedData] = priceData[assetId] || []; | |
// If recently fetched, do nothing | |
if (typeof lastFetchTime === 'number' && lastFetchTime > Date.now() - TOKEN_PRICE_CHECK_INTERVAL) return; | |
const [lastFetchTime, cachedData] = priceData[assetId] || [0]; | |
// If recently fetched, do nothing | |
if (lastFetchTime > Date.now() - TOKEN_PRICE_CHECK_INTERVAL) return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
e101d0c should solve this
updatePriceData(assetId, cachedData); | ||
|
||
try { | ||
const url = `https://coingecko.live-mainnet.eks.lw.iog.io/api/v3/onchain/networks/cardano/tokens/${assetId}/pools`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest moving core part of the URL to some constant + a function that accepts assetId
and returns full URL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressing this comment I did a little refactoring which could make this comment no longer required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GJ! 🚀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GJ!
5b33fc6
to
3174dc3
Compare
|
Checklist
Proposed solution
Explain how does this PR solves the problem stated in JIRA ticket.
You can also enumerate different alternatives considered while approaching this task.
Testing
Describe here, how the new implementation can be tested.
Provide link or briefly describe User Acceptance Criteria/Tests that need to be met
Screenshots
Attach screenshots here if implementation involves some UI changes