Add block height to price carousel (#484)

* Add block height to price carousel

source block height from mempool.space API
https://mempool.space/docs/api/rest#get-block-tip-height

* Add block height to SSR, clean up fragment query

* Cache block height for 1 minute, not 30 seconds

use `numWithUnits` for block height label

* Replace mempool.space API with LND API call

---------

Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
This commit is contained in:
SatsAllDay 2023-09-12 11:56:02 -04:00 committed by GitHub
parent 94fbabcdf9
commit 16a6f93708
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 107 additions and 5 deletions

View File

@ -0,0 +1,37 @@
import lndService from 'ln-service'
import lnd from '../lnd'
const cache = new Map()
const expiresIn = 1000 * 30 // 30 seconds in milliseconds
async function fetchBlockHeight () {
let blockHeight = 0
try {
const height = await lndService.getHeight({ lnd })
blockHeight = height.current_block_height
} catch (err) {
console.error('fetchBlockHeight', err)
}
cache.set('block', { height: blockHeight, createdAt: Date.now() })
return blockHeight
}
async function getBlockHeight () {
if (cache.has('block')) {
const { height, createdAt } = cache.get('block')
const expired = createdAt + expiresIn < Date.now()
if (expired) fetchBlockHeight().catch(console.error) // update cache
return height // serve stale block height (this on the SSR critical path)
} else {
fetchBlockHeight().catch(console.error)
}
return 0
}
export default {
Query: {
blockHeight: async (parent, opts, ctx) => {
return await getBlockHeight()
}
}
}

View File

@ -14,6 +14,7 @@ import referrals from './referrals'
import price from './price'
import { GraphQLJSONObject as JSONObject } from 'graphql-type-json'
import admin from './admin'
import blockHeight from './blockHeight'
import { GraphQLScalarType, Kind } from 'graphql'
const date = new GraphQLScalarType({
@ -44,4 +45,4 @@ const date = new GraphQLScalarType({
})
export default [user, item, message, wallet, lnurl, notifications, invite, sub,
upload, search, growth, rewards, referrals, price, admin, { JSONObject }, { Date: date }]
upload, search, growth, rewards, referrals, price, admin, blockHeight, { JSONObject }, { Date: date }]

View File

@ -9,6 +9,7 @@ import lnd from './lnd'
import search from './search'
import { ME } from '../fragments/users'
import { PRICE } from '../fragments/price'
import { BLOCK_HEIGHT } from '../fragments/blockHeight'
import { getServerSession } from 'next-auth/next'
import { getAuthOptions } from '../pages/api/auth/[...nextauth]'
@ -92,6 +93,10 @@ export function getGetServerSideProps (
query: PRICE, variables: { fiatCurrency: me?.fiatCurrency }
})
const { data: { blockHeight } } = await client.query({
query: BLOCK_HEIGHT, variables: {}
})
let error = null; let data = null; let props = {}
if (query) {
try {
@ -122,6 +127,7 @@ export function getGetServerSideProps (
...props,
me,
price,
blockHeight,
ssrData: data
}
}

View File

@ -0,0 +1,7 @@
import { gql } from 'graphql-tag'
export default gql`
extend type Query {
blockHeight: Int!
}
`

View File

@ -15,6 +15,7 @@ import rewards from './rewards'
import referrals from './referrals'
import price from './price'
import admin from './admin'
import blockHeight from './blockHeight'
const common = gql`
type Query {
@ -34,4 +35,4 @@ const common = gql`
`
export default [common, user, item, itemForward, message, wallet, lnurl, notifications, invite,
sub, upload, growth, rewards, referrals, price, admin]
sub, upload, growth, rewards, referrals, price, admin, blockHeight]

View File

@ -0,0 +1,29 @@
import { createContext, useContext } from 'react'
import { useQuery } from '@apollo/client'
import { SSR } from '../lib/constants'
import { BLOCK_HEIGHT } from '../fragments/blockHeight'
export const BlockHeightContext = createContext({
height: 0
})
export const useBlockHeight = () => useContext(BlockHeightContext)
export const BlockHeightProvider = ({ blockHeight, children }) => {
const { data } = useQuery(BLOCK_HEIGHT, {
...(SSR
? {}
: {
pollInterval: 30000,
nextFetchPolicy: 'cache-and-network'
})
})
const value = {
height: data?.blockHeight ?? blockHeight ?? 0
}
return (
<BlockHeightContext.Provider value={value}>
{children}
</BlockHeightContext.Provider>
)
}

View File

@ -5,6 +5,7 @@ import { useMe } from './me'
import { PRICE } from '../fragments/price'
import { CURRENCY_SYMBOLS } from '../lib/currency'
import { SSR } from '../lib/constants'
import { useBlockHeight } from './block-height'
export const PriceContext = React.createContext({
price: null,
@ -46,14 +47,20 @@ export default function Price ({ className }) {
setAsSats(window.localStorage.getItem('asSats'))
}, [])
const { price, fiatSymbol } = usePrice()
const { height: blockHeight } = useBlockHeight()
if (!price || price < 0) return null
if (!price || price < 0 || blockHeight <= 0) return null
// Options: yep, 1btc, blockHeight, undefined
// yep -> 1btc -> blockHeight -> undefined -> yep
const handleClick = () => {
if (asSats === 'yep') {
window.localStorage.setItem('asSats', '1btc')
setAsSats('1btc')
} else if (asSats === '1btc') {
window.localStorage.setItem('asSats', 'blockHeight')
setAsSats('blockHeight')
} else if (asSats === 'blockHeight') {
window.localStorage.removeItem('asSats')
setAsSats(undefined)
} else {
@ -80,6 +87,14 @@ export default function Price ({ className }) {
)
}
if (asSats === 'blockHeight') {
return (
<div className={compClassName} onClick={handleClick} variant='link'>
{`block ${blockHeight}`}
</div>
)
}
return (
<div className={compClassName} onClick={handleClick} variant='link'>
{fiatSymbol + fixedDecimal(price, 0)}

3
fragments/blockHeight.js Normal file
View File

@ -0,0 +1,3 @@
import { gql } from '@apollo/client'
export const BLOCK_HEIGHT = gql`{ blockHeight }`

View File

@ -4,6 +4,7 @@ import { MeProvider } from '../components/me'
import PlausibleProvider from 'next-plausible'
import getApolloClient from '../lib/apollo'
import { PriceProvider } from '../components/price'
import { BlockHeightProvider } from '../components/block-height'
import Head from 'next/head'
import { useRouter } from 'next/dist/client/router'
import { useEffect } from 'react'
@ -73,7 +74,7 @@ function MyApp ({ Component, pageProps: { ...props } }) {
If we are on the client, we populate the apollo cache with the
ssr data
*/
const { apollo, ssrData, me, price, ...otherProps } = props
const { apollo, ssrData, me, price, blockHeight, ...otherProps } = props
useEffect(() => {
writeQuery(client, apollo, ssrData)
}, [client, apollo, ssrData])
@ -92,7 +93,9 @@ function MyApp ({ Component, pageProps: { ...props } }) {
<LightningProvider>
<ToastProvider>
<ShowModalProvider>
<Component ssrData={ssrData} {...otherProps} />
<BlockHeightProvider blockHeight={blockHeight}>
<Component ssrData={ssrData} {...otherProps} />
</BlockHeightProvider>
</ShowModalProvider>
</ToastProvider>
</LightningProvider>