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:
		
							parent
							
								
									94fbabcdf9
								
							
						
					
					
						commit
						16a6f93708
					
				
							
								
								
									
										37
									
								
								api/resolvers/blockHeight.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								api/resolvers/blockHeight.js
									
									
									
									
									
										Normal 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() | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -14,6 +14,7 @@ import referrals from './referrals' | |||||||
| import price from './price' | import price from './price' | ||||||
| import { GraphQLJSONObject as JSONObject } from 'graphql-type-json' | import { GraphQLJSONObject as JSONObject } from 'graphql-type-json' | ||||||
| import admin from './admin' | import admin from './admin' | ||||||
|  | import blockHeight from './blockHeight' | ||||||
| import { GraphQLScalarType, Kind } from 'graphql' | import { GraphQLScalarType, Kind } from 'graphql' | ||||||
| 
 | 
 | ||||||
| const date = new GraphQLScalarType({ | const date = new GraphQLScalarType({ | ||||||
| @ -44,4 +45,4 @@ const date = new GraphQLScalarType({ | |||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| export default [user, item, message, wallet, lnurl, notifications, invite, sub, | 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 }] | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ import lnd from './lnd' | |||||||
| import search from './search' | import search from './search' | ||||||
| import { ME } from '../fragments/users' | import { ME } from '../fragments/users' | ||||||
| import { PRICE } from '../fragments/price' | import { PRICE } from '../fragments/price' | ||||||
|  | import { BLOCK_HEIGHT } from '../fragments/blockHeight' | ||||||
| import { getServerSession } from 'next-auth/next' | import { getServerSession } from 'next-auth/next' | ||||||
| import { getAuthOptions } from '../pages/api/auth/[...nextauth]' | import { getAuthOptions } from '../pages/api/auth/[...nextauth]' | ||||||
| 
 | 
 | ||||||
| @ -92,6 +93,10 @@ export function getGetServerSideProps ( | |||||||
|       query: PRICE, variables: { fiatCurrency: me?.fiatCurrency } |       query: PRICE, variables: { fiatCurrency: me?.fiatCurrency } | ||||||
|     }) |     }) | ||||||
| 
 | 
 | ||||||
|  |     const { data: { blockHeight } } = await client.query({ | ||||||
|  |       query: BLOCK_HEIGHT, variables: {} | ||||||
|  |     }) | ||||||
|  | 
 | ||||||
|     let error = null; let data = null; let props = {} |     let error = null; let data = null; let props = {} | ||||||
|     if (query) { |     if (query) { | ||||||
|       try { |       try { | ||||||
| @ -122,6 +127,7 @@ export function getGetServerSideProps ( | |||||||
|         ...props, |         ...props, | ||||||
|         me, |         me, | ||||||
|         price, |         price, | ||||||
|  |         blockHeight, | ||||||
|         ssrData: data |         ssrData: data | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  | |||||||
							
								
								
									
										7
									
								
								api/typeDefs/blockHeight.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								api/typeDefs/blockHeight.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | import { gql } from 'graphql-tag' | ||||||
|  | 
 | ||||||
|  | export default gql` | ||||||
|  |   extend type Query { | ||||||
|  |     blockHeight: Int! | ||||||
|  |   } | ||||||
|  | ` | ||||||
| @ -15,6 +15,7 @@ import rewards from './rewards' | |||||||
| import referrals from './referrals' | import referrals from './referrals' | ||||||
| import price from './price' | import price from './price' | ||||||
| import admin from './admin' | import admin from './admin' | ||||||
|  | import blockHeight from './blockHeight' | ||||||
| 
 | 
 | ||||||
| const common = gql` | const common = gql` | ||||||
|   type Query { |   type Query { | ||||||
| @ -34,4 +35,4 @@ const common = gql` | |||||||
| ` | ` | ||||||
| 
 | 
 | ||||||
| export default [common, user, item, itemForward, message, wallet, lnurl, notifications, invite, | 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] | ||||||
|  | |||||||
							
								
								
									
										29
									
								
								components/block-height.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								components/block-height.js
									
									
									
									
									
										Normal 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> | ||||||
|  |   ) | ||||||
|  | } | ||||||
| @ -5,6 +5,7 @@ import { useMe } from './me' | |||||||
| import { PRICE } from '../fragments/price' | import { PRICE } from '../fragments/price' | ||||||
| import { CURRENCY_SYMBOLS } from '../lib/currency' | import { CURRENCY_SYMBOLS } from '../lib/currency' | ||||||
| import { SSR } from '../lib/constants' | import { SSR } from '../lib/constants' | ||||||
|  | import { useBlockHeight } from './block-height' | ||||||
| 
 | 
 | ||||||
| export const PriceContext = React.createContext({ | export const PriceContext = React.createContext({ | ||||||
|   price: null, |   price: null, | ||||||
| @ -46,14 +47,20 @@ export default function Price ({ className }) { | |||||||
|     setAsSats(window.localStorage.getItem('asSats')) |     setAsSats(window.localStorage.getItem('asSats')) | ||||||
|   }, []) |   }, []) | ||||||
|   const { price, fiatSymbol } = usePrice() |   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 = () => { |   const handleClick = () => { | ||||||
|     if (asSats === 'yep') { |     if (asSats === 'yep') { | ||||||
|       window.localStorage.setItem('asSats', '1btc') |       window.localStorage.setItem('asSats', '1btc') | ||||||
|       setAsSats('1btc') |       setAsSats('1btc') | ||||||
|     } else if (asSats === '1btc') { |     } else if (asSats === '1btc') { | ||||||
|  |       window.localStorage.setItem('asSats', 'blockHeight') | ||||||
|  |       setAsSats('blockHeight') | ||||||
|  |     } else if (asSats === 'blockHeight') { | ||||||
|       window.localStorage.removeItem('asSats') |       window.localStorage.removeItem('asSats') | ||||||
|       setAsSats(undefined) |       setAsSats(undefined) | ||||||
|     } else { |     } 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 ( |   return ( | ||||||
|     <div className={compClassName} onClick={handleClick} variant='link'> |     <div className={compClassName} onClick={handleClick} variant='link'> | ||||||
|       {fiatSymbol + fixedDecimal(price, 0)} |       {fiatSymbol + fixedDecimal(price, 0)} | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								fragments/blockHeight.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								fragments/blockHeight.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | import { gql } from '@apollo/client' | ||||||
|  | 
 | ||||||
|  | export const BLOCK_HEIGHT = gql`{ blockHeight }` | ||||||
| @ -4,6 +4,7 @@ import { MeProvider } from '../components/me' | |||||||
| import PlausibleProvider from 'next-plausible' | import PlausibleProvider from 'next-plausible' | ||||||
| import getApolloClient from '../lib/apollo' | import getApolloClient from '../lib/apollo' | ||||||
| import { PriceProvider } from '../components/price' | import { PriceProvider } from '../components/price' | ||||||
|  | import { BlockHeightProvider } from '../components/block-height' | ||||||
| import Head from 'next/head' | import Head from 'next/head' | ||||||
| import { useRouter } from 'next/dist/client/router' | import { useRouter } from 'next/dist/client/router' | ||||||
| import { useEffect } from 'react' | 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 |     If we are on the client, we populate the apollo cache with the | ||||||
|     ssr data |     ssr data | ||||||
|   */ |   */ | ||||||
|   const { apollo, ssrData, me, price, ...otherProps } = props |   const { apollo, ssrData, me, price, blockHeight, ...otherProps } = props | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     writeQuery(client, apollo, ssrData) |     writeQuery(client, apollo, ssrData) | ||||||
|   }, [client, apollo, ssrData]) |   }, [client, apollo, ssrData]) | ||||||
| @ -92,7 +93,9 @@ function MyApp ({ Component, pageProps: { ...props } }) { | |||||||
|                   <LightningProvider> |                   <LightningProvider> | ||||||
|                     <ToastProvider> |                     <ToastProvider> | ||||||
|                       <ShowModalProvider> |                       <ShowModalProvider> | ||||||
|                         <Component ssrData={ssrData} {...otherProps} /> |                         <BlockHeightProvider blockHeight={blockHeight}> | ||||||
|  |                           <Component ssrData={ssrData} {...otherProps} /> | ||||||
|  |                         </BlockHeightProvider> | ||||||
|                       </ShowModalProvider> |                       </ShowModalProvider> | ||||||
|                     </ToastProvider> |                     </ToastProvider> | ||||||
|                   </LightningProvider> |                   </LightningProvider> | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user