From e9a5d22a6efbcaadaae31befb36d3cfe2d16eb0a Mon Sep 17 00:00:00 2001 From: st4rgut24 Date: Wed, 20 Dec 2023 14:06:22 -0800 Subject: [PATCH] Add chain fees to price carousel (#658) * add chain fees to price carousel * restore check for valid carousel values * add nym to contributors --------- Co-authored-by: stargut Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com> --- api/resolvers/chainFee.js | 37 +++++++++++++++++++++++++++++++++++++ api/resolvers/index.js | 3 ++- api/ssrApollo.js | 6 ++++++ api/typeDefs/chainFee.js | 7 +++++++ api/typeDefs/index.js | 3 ++- components/chain-fee.js | 29 +++++++++++++++++++++++++++++ components/price.js | 18 ++++++++++++++++-- contributors.txt | 1 + fragments/chainFee.js | 3 +++ pages/_app.js | 11 +++++++---- 10 files changed, 110 insertions(+), 8 deletions(-) create mode 100644 api/resolvers/chainFee.js create mode 100644 api/typeDefs/chainFee.js create mode 100644 components/chain-fee.js create mode 100644 fragments/chainFee.js diff --git a/api/resolvers/chainFee.js b/api/resolvers/chainFee.js new file mode 100644 index 00000000..a3c3728d --- /dev/null +++ b/api/resolvers/chainFee.js @@ -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 fetchChainFeeRate () { + let chainFee = 0 + try { + const fee = await lndService.getChainFeeRate({ lnd }) + chainFee = fee.tokens_per_vbyte + } catch (err) { + console.error('fetchChainFee', err) + } + cache.set('fee', { fee: chainFee, createdAt: Date.now() }) + return chainFee +} + +async function getChainFeeRate () { + if (cache.has('fee')) { + const { fee, createdAt } = cache.get('fee') + const expired = createdAt + expiresIn < Date.now() + if (expired) fetchChainFeeRate().catch(console.error) // update cache + return fee + } else { + fetchChainFeeRate().catch(console.error) + } + return 0 +} + +export default { + Query: { + chainFee: async (parent, opts, ctx) => { + return await getChainFeeRate() + } + } +} diff --git a/api/resolvers/index.js b/api/resolvers/index.js index b341d890..60fc8f34 100644 --- a/api/resolvers/index.js +++ b/api/resolvers/index.js @@ -15,6 +15,7 @@ import price from './price' import { GraphQLJSONObject as JSONObject } from 'graphql-type-json' import admin from './admin' import blockHeight from './blockHeight' +import chainFee from './chainFee' import image from './image' import { GraphQLScalarType, Kind } from 'graphql' import { createIntScalar } from 'graphql-scalar' @@ -53,4 +54,4 @@ const limit = createIntScalar({ }) export default [user, item, message, wallet, lnurl, notifications, invite, sub, - upload, search, growth, rewards, referrals, price, admin, blockHeight, image, { JSONObject }, { Date: date }, { Limit: limit }] + upload, search, growth, rewards, referrals, price, admin, blockHeight, chainFee, image, { JSONObject }, { Date: date }, { Limit: limit }] diff --git a/api/ssrApollo.js b/api/ssrApollo.js index a34b769d..2524fd86 100644 --- a/api/ssrApollo.js +++ b/api/ssrApollo.js @@ -10,6 +10,7 @@ import search from './search' import { ME } from '../fragments/users' import { PRICE } from '../fragments/price' import { BLOCK_HEIGHT } from '../fragments/blockHeight' +import { CHAIN_FEE } from '../fragments/chainFee' import { getServerSession } from 'next-auth/next' import { getAuthOptions } from '../pages/api/auth/[...nextauth]' @@ -94,6 +95,10 @@ export function getGetServerSideProps ( query: BLOCK_HEIGHT, variables: {} }) + const { data: { chainFee } } = await client.query({ + query: CHAIN_FEE, variables: {} + }) + let error = null; let data = null; let props = {} if (query) { try { @@ -125,6 +130,7 @@ export function getGetServerSideProps ( me, price, blockHeight, + chainFee, ssrData: data } } diff --git a/api/typeDefs/chainFee.js b/api/typeDefs/chainFee.js new file mode 100644 index 00000000..0bc444ee --- /dev/null +++ b/api/typeDefs/chainFee.js @@ -0,0 +1,7 @@ +import { gql } from 'graphql-tag' + +export default gql` + extend type Query { + chainFee: Int! + } +` diff --git a/api/typeDefs/index.js b/api/typeDefs/index.js index ef524d4e..11963a9f 100644 --- a/api/typeDefs/index.js +++ b/api/typeDefs/index.js @@ -16,6 +16,7 @@ import referrals from './referrals' import price from './price' import admin from './admin' import blockHeight from './blockHeight' +import chainFee from './chainFee' import image from './image' const common = gql` @@ -37,4 +38,4 @@ const common = gql` ` export default [common, user, item, itemForward, message, wallet, lnurl, notifications, invite, - sub, upload, growth, rewards, referrals, price, admin, blockHeight, image] + sub, upload, growth, rewards, referrals, price, admin, blockHeight, chainFee, image] diff --git a/components/chain-fee.js b/components/chain-fee.js new file mode 100644 index 00000000..f61092dc --- /dev/null +++ b/components/chain-fee.js @@ -0,0 +1,29 @@ +import { createContext, useContext, useMemo } from 'react' +import { useQuery } from '@apollo/client' +import { SSR } from '../lib/constants' +import { CHAIN_FEE } from '../fragments/chainFee' + +export const ChainFeeContext = createContext({ + fee: 0 +}) + +export const useChainFee = () => useContext(ChainFeeContext) + +export const ChainFeeProvider = ({ chainFee, children }) => { + const { data } = useQuery(CHAIN_FEE, { + ...(SSR + ? {} + : { + pollInterval: 30000, + nextFetchPolicy: 'cache-and-network' + }) + }) + const value = useMemo(() => ({ + fee: data?.chainFee ?? chainFee ?? 0 + }), [data, chainFee]) + return ( + + {children} + + ) +} diff --git a/components/price.js b/components/price.js index 1cce362b..f9fa6f2f 100644 --- a/components/price.js +++ b/components/price.js @@ -6,6 +6,7 @@ import { PRICE } from '../fragments/price' import { CURRENCY_SYMBOLS } from '../lib/currency' import { SSR } from '../lib/constants' import { useBlockHeight } from './block-height' +import { useChainFee } from './chain-fee' export const PriceContext = React.createContext({ price: null, @@ -47,13 +48,15 @@ export default function Price ({ className }) { const satSelection = window.localStorage.getItem('asSats') setAsSats(satSelection ?? 'fiat') }, []) + const { price, fiatSymbol } = usePrice() const { height: blockHeight } = useBlockHeight() + const { fee: chainFee } = useChainFee() - if (!price || price < 0 || blockHeight <= 0) return null + if (!price || price < 0 || blockHeight <= 0 || chainFee <= 0) return null // Options: yep, 1btc, blockHeight, undefined - // yep -> 1btc -> blockHeight -> undefined -> yep + // yep -> 1btc -> blockHeight -> chainFee -> undefined -> yep const handleClick = () => { if (asSats === 'yep') { window.localStorage.setItem('asSats', '1btc') @@ -62,6 +65,9 @@ export default function Price ({ className }) { window.localStorage.setItem('asSats', 'blockHeight') setAsSats('blockHeight') } else if (asSats === 'blockHeight') { + window.localStorage.setItem('asSats', 'chainFee') + setAsSats('chainFee') + } else if (asSats === 'chainFee') { window.localStorage.removeItem('asSats') setAsSats('fiat') } else { @@ -96,6 +102,14 @@ export default function Price ({ className }) { ) } + if (asSats === 'chainFee') { + return ( +
+ {chainFee} sat/vB +
+ ) + } + if (asSats === 'fiat') { return (
diff --git a/contributors.txt b/contributors.txt index cbaf5883..51b547e8 100644 --- a/contributors.txt +++ b/contributors.txt @@ -5,3 +5,4 @@ WeAreAllSatoshi rleed bitcoinplebdev benthecarman +stargut diff --git a/fragments/chainFee.js b/fragments/chainFee.js new file mode 100644 index 00000000..3012db07 --- /dev/null +++ b/fragments/chainFee.js @@ -0,0 +1,3 @@ +import { gql } from '@apollo/client' + +export const CHAIN_FEE = gql`{ chainFee }` diff --git a/pages/_app.js b/pages/_app.js index 81b61457..eecc27ad 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -17,6 +17,7 @@ import { SSR } from '../lib/constants' import NProgress from 'nprogress' import 'nprogress/nprogress.css' import { LoggerProvider } from '../components/logger' +import { ChainFeeProvider } from '../components/chain-fee.js' NProgress.configure({ showSpinner: false @@ -75,7 +76,7 @@ export default 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, blockHeight, ...otherProps } = props + const { apollo, ssrData, me, price, blockHeight, chainFee, ...otherProps } = props useEffect(() => { writeQuery(client, apollo, ssrData) }, [client, apollo, ssrData]) @@ -96,9 +97,11 @@ export default function MyApp ({ Component, pageProps: { ...props } }) { - - - + + + + +