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 { 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 }] | ||||
|  | ||||
| @ -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 | ||||
|       } | ||||
|     } | ||||
|  | ||||
							
								
								
									
										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 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] | ||||
|  | ||||
							
								
								
									
										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 { 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
									
								
							
							
						
						
									
										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 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> | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user