Skip to content

Commit

Permalink
Merge branch 'dev' into sp/trm-labs-integration
Browse files Browse the repository at this point in the history
# Conflicts:
#	pages/owner/[address]/index.tsx
  • Loading branch information
piekczyk committed Aug 10, 2022
2 parents fa6233b + 9031556 commit 8397949
Show file tree
Hide file tree
Showing 22 changed files with 604 additions and 443 deletions.
83 changes: 20 additions & 63 deletions blockchain/prices.test.ts
Expand Up @@ -9,99 +9,56 @@ import { getStateUnpacker } from '../helpers/testHelpers'
import { createOraclePriceData$, createTokenPriceInUSD$, OraclePriceData } from './prices'

describe('createTokenPriceInUSD$', () => {
function coinbaseOrderBook$() {
return of({
bids: [['1']] as [string][],
asks: [['2']] as [string][],
})
}

const coinPaprikaTicker$ = of({ 'steth-lido-staked-ether': new BigNumber('31605.56989258439') })

function coinGeckoTicker$() {
return of(new BigNumber('1947.78'))
}
const tokenTickers$ = of({
'mkr-usd': new BigNumber('929.26'),
'steth-lido-staked-ether': new BigNumber('1462.87'),
'wrapped-steth': new BigNumber('1573.93'),
})

it('maps token price from coinbase', () => {
const tokenPrice$ = createTokenPriceInUSD$(
of(null),
coinbaseOrderBook$,
coinPaprikaTicker$,
coinGeckoTicker$,
['MKR'],
)
it('maps token price to coinbase ticker', () => {
const tokenPrice$ = createTokenPriceInUSD$(of(null), tokenTickers$, ['MKR'])

const tokenPrice = getStateUnpacker(tokenPrice$)

expect(tokenPrice().MKR.toString()).eq('1.5')
expect(tokenPrice().MKR.toString()).eq('929.26')
})

it('maps token price from coinpaprika', () => {
const tokenPrice$ = createTokenPriceInUSD$(
of(null),
coinbaseOrderBook$,
coinPaprikaTicker$,
coinGeckoTicker$,
['STETH'],
)
it('maps token price to coin paprika ticker', () => {
const tokenPrice$ = createTokenPriceInUSD$(of(null), tokenTickers$, ['STETH'])

const tokenPrice = getStateUnpacker(tokenPrice$)

expect(tokenPrice().STETH.toString()).eq('31605.56989258439')
expect(tokenPrice().STETH.toString()).eq('1462.87')
})

it('maps token price from coingecko', () => {
const tokenPrice$ = createTokenPriceInUSD$(
of(null),
coinbaseOrderBook$,
coinPaprikaTicker$,
coinGeckoTicker$,
['WSTETH'],
)
it('maps token price to coingecko ticker', () => {
const tokenPrice$ = createTokenPriceInUSD$(of(null), tokenTickers$, ['WSTETH'])

const tokenPrice = getStateUnpacker(tokenPrice$)

expect(tokenPrice().WSTETH.toString()).eq('1947.78')
expect(tokenPrice().WSTETH.toString()).eq('1573.93')
})

it('handles concurrent token price requests', () => {
const tokenPrice$ = createTokenPriceInUSD$(
of(null),
coinbaseOrderBook$,
coinPaprikaTicker$,
coinGeckoTicker$,
['MKR', 'STETH'],
)
const tokenPrice$ = createTokenPriceInUSD$(of(null), tokenTickers$, ['MKR', 'STETH'])

const tokenPrice = getStateUnpacker(tokenPrice$)

expect(tokenPrice().MKR.toString()).eq('1.5')
expect(tokenPrice().STETH.toString()).eq('31605.56989258439')
expect(tokenPrice().MKR.toString()).eq('929.26')
expect(tokenPrice().STETH.toString()).eq('1462.87')
})

describe('mapping unknown quantities to undefined', () => {
describe('maps unknown quantities to undefined', () => {
it('handles token with no ticker configured', () => {
const tokenPrice$ = createTokenPriceInUSD$(
of(null),
coinbaseOrderBook$,
coinPaprikaTicker$,
coinGeckoTicker$,
['BAT'],
)
const tokenPrice$ = createTokenPriceInUSD$(of(null), tokenTickers$, ['BAT'])

const tokenPrice = getStateUnpacker(tokenPrice$)

expect(tokenPrice().BAT).is.undefined
})

it('handles no response from service', () => {
const tokenPrice$ = createTokenPriceInUSD$(
of(null),
() => throwError('some error'),
throwError('some error'),
coinGeckoTicker$,
['MKR'],
)
const tokenPrice$ = createTokenPriceInUSD$(of(null), throwError('some error'), ['MKR'])

const tokenPrice = getStateUnpacker(tokenPrice$)

Expand Down
103 changes: 28 additions & 75 deletions blockchain/prices.ts
Expand Up @@ -3,20 +3,12 @@ import { Context } from 'blockchain/network'
import { zero } from 'helpers/zero'
import { isEqual } from 'lodash'
import { bindNodeCallback, combineLatest, forkJoin, Observable, of, timer } from 'rxjs'
import { ajax, AjaxResponse } from 'rxjs/ajax'
import {
catchError,
distinctUntilChanged,
first,
map,
shareReplay,
switchMap,
tap,
} from 'rxjs/operators'
import { ajax } from 'rxjs/ajax'
import { distinctUntilChanged, first, map, shareReplay, switchMap, tap } from 'rxjs/operators'

import { getToken } from './tokensMetadata'

export interface Ticker {
export interface Tickers {
[label: string]: BigNumber
}

Expand Down Expand Up @@ -82,25 +74,7 @@ export function createGasPrice$(
)
}

type CoinbaseOrderBook = {
bids: [string][]
asks: [string][]
}

export function coinbaseOrderBook$(ticker: string): Observable<AjaxResponse['response']> {
return ajax({
url: `https://api.pro.coinbase.com/products/${ticker}/book`,
method: 'GET',
headers: {
Accept: 'application/json',
},
}).pipe(
map(({ response }) => response),
shareReplay(1),
)
}

export const coinPaprikaTicker$: Observable<Ticker> = timer(0, 1000 * 60).pipe(
export const tokenPrices$: Observable<Tickers> = timer(0, 1000 * 60).pipe(
switchMap(() =>
ajax({
url: `${window.location.origin}/api/tokensPrices`,
Expand All @@ -114,61 +88,40 @@ export const coinPaprikaTicker$: Observable<Ticker> = timer(0, 1000 * 60).pipe(
shareReplay(1),
)

export function coinGeckoTicker$(ticker: string): Observable<BigNumber> {
return ajax({
url: `https://api.coingecko.com/api/v3/simple/price?ids=${ticker}&vs_currencies=usd`,
method: 'GET',
headers: {
Accept: 'application/json',
},
}).pipe(
map(({ response }) => new BigNumber(response[ticker].usd)),
shareReplay(1),
)
function getPrice(tickers: Tickers, tickerServiceLabels: Array<string | undefined>) {
for (const label of tickerServiceLabels) {
if (label && tickers[label]) {
return tickers[label]
}
}

throw new Error(`No price data for given token`)
}

export function createTokenPriceInUSD$(
every10Seconds$: Observable<any>,
coinbaseOrderBook$: (ticker: string) => Observable<CoinbaseOrderBook>,
coinpaprikaTicker$: Observable<Ticker>,
coinGeckoTicker$: (ticker: string) => Observable<BigNumber>,
tokenTicker$: Observable<Tickers>,
tokens: Array<string>,
): Observable<Ticker> {
return combineLatest(every10Seconds$, coinpaprikaTicker$).pipe(
switchMap(([, ticker]) =>
): Observable<Tickers> {
return combineLatest(every10Seconds$, tokenTicker$).pipe(
switchMap(([_, tickers]) =>
forkJoin(
tokens.map((token) => {
const { coinbaseTicker, coinpaprikaTicker, coinGeckoTicker } = getToken(token)
if (coinbaseTicker) {
return coinbaseOrderBook$(coinbaseTicker).pipe(
map((response) => {
const bid = new BigNumber(response.bids[0][0])
const ask = new BigNumber(response.asks[0][0])
return {
[token]: bid.plus(ask).div(2),
}
}),
catchError((error) => {
console.log(error)
return of({})
}),
)
} else if (coinpaprikaTicker) {
try {
const { coinpaprikaTicker, coinbaseTicker, coinGeckoTicker } = getToken(token)

const tokenPrice = getPrice(tickers, [
coinbaseTicker,
coinpaprikaTicker,
coinGeckoTicker,
])

return of({
[token]: ticker[coinpaprikaTicker],
[token]: new BigNumber(tokenPrice),
})
} else if (coinGeckoTicker) {
return coinGeckoTicker$(coinGeckoTicker).pipe(
map((price) => ({
[token]: price,
})),
catchError((error) => {
console.log(error)
return of({})
}),
)
} else {
} catch (err) {
console.log(`could not find price for ${token} - no ticker configured`)

return of({})
}
}),
Expand Down
14 changes: 2 additions & 12 deletions components/AppContext.ts
Expand Up @@ -44,13 +44,11 @@ import { createGetRegistryCdps$ } from 'blockchain/getRegistryCdps'
import { createIlkData$, createIlkDataList$, createIlksSupportedOnNetwork$ } from 'blockchain/ilks'
import { createInstiVault$, InstiVault } from 'blockchain/instiVault'
import {
coinbaseOrderBook$,
coinGeckoTicker$,
coinPaprikaTicker$,
createGasPrice$,
createOraclePriceData$,
createTokenPriceInUSD$,
GasPriceParams,
tokenPrices$,
} from 'blockchain/prices'
import {
createAccountBalance$,
Expand Down Expand Up @@ -492,16 +490,8 @@ export function setupAppContext() {
const txHelpers$: TxHelpers$ = createTxHelpers$(connectedContext$, send, gasPrice$)
const transactionManager$ = createTransactionManager(transactions$)

const coninbasePrices$ = memoize(coinbaseOrderBook$)
const coinGeckoPrices$ = memoize(coinGeckoTicker$)

const tokenPriceUSD$ = memoize(
curry(createTokenPriceInUSD$)(
every10Seconds$,
coninbasePrices$,
coinPaprikaTicker$,
coinGeckoPrices$,
),
curry(createTokenPriceInUSD$)(every10Seconds$, tokenPrices$),
(tokens: string[]) => tokens.sort().join(','),
)

Expand Down
8 changes: 5 additions & 3 deletions components/Table.tsx
@@ -1,7 +1,7 @@
import { Direction } from 'helpers/form'
import { useTranslation } from 'next-i18next'
import React, { HTMLProps, memo, ReactNode } from 'react'
import { Box, Button, Container, SxStyleProp } from 'theme-ui'
import { Box, Button, Container, SxStyleProp, Text } from 'theme-ui'

import { ChevronUpDown } from './ChevronUpDown'

Expand Down Expand Up @@ -224,11 +224,13 @@ export function TableSortHeader<K extends string>({
color: 'neutral80',
...sx,
}}
variant="boldParagraph3"
variant="tableHeader"
onClick={() => filters.change({ kind: 'sortBy', sortBy })}
>
<Box sx={{ whiteSpace: 'nowrap', color: isSelected ? 'primary100' : 'neutral80' }}>
{children}
<Text variant="boldParagraph3" color="inherit">
{children}
</Text>
</Box>
<Box>
<ChevronUpDown
Expand Down

0 comments on commit 8397949

Please sign in to comment.