From 8b5414bf3b3fcd72f638951b5a57bf28d235c76a Mon Sep 17 00:00:00 2001 From: rasguy92 Date: Mon, 27 Nov 2023 00:01:06 +0700 Subject: [PATCH] ref: setup drand client --- src/common/FloatingOrbs/index.tsx | 69 ++++++++++-------- src/common/FloatingOrbs/types.ts | 25 +++++-- .../CreateGameCard/CreateCoinFlipGameCard.tsx | 3 +- .../CreateGameCard/CreateDiceGameCard.tsx | 12 ++-- src/components/CreateGameCard/apiSlice.ts | 1 - src/components/CreateGameCard/types.tsx | 3 +- src/hooks/index.ts | 3 +- src/hooks/useTokenUSDValue.ts | 10 +++ src/libs/drand/index.ts | 70 +++++++++++++++++++ src/models/currency.ts | 4 +- src/utils/ethers-helpers.ts | 14 +++- src/utils/index.ts | 7 +- src/utils/price.ts | 24 +++++++ 13 files changed, 193 insertions(+), 52 deletions(-) create mode 100644 src/hooks/useTokenUSDValue.ts create mode 100644 src/libs/drand/index.ts create mode 100644 src/utils/price.ts diff --git a/src/common/FloatingOrbs/index.tsx b/src/common/FloatingOrbs/index.tsx index 7a3b555..2460ab7 100644 --- a/src/common/FloatingOrbs/index.tsx +++ b/src/common/FloatingOrbs/index.tsx @@ -1,36 +1,43 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { FloatingOrbContainer, FloatingOrbBlurContainer } from './types'; +import { useLayoutEffect, useRef } from 'react'; +import { FloatingOrbContainer, FloatingOrbBlurContainer, FloatingOrbBlurContainerWithoutBackdrop, Orb } from './types'; +import { gsap, Sine } from 'gsap'; import "twin.macro" -// interface FloatingOrbsProps {} -export const FloatingOrbs = () => ( - - -
-
-
-
+export const FloatingOrbs: React.FC & React.Attributes> = ({ children, ...props}: React.AllHTMLAttributes & React.Attributes) => { + const orbContainerRef = useRef(null); + + useLayoutEffect(() => { + const context = gsap.context(() => { + const tl = gsap.timeline(); + const duration = 5; + const orbs = orbContainerRef.current.querySelectorAll('.orb'); + tl.repeatDelay(0.25); + tl.fromTo(orbs[0], { scale: 1.1, rotate: '0deg', x: "-=5rem", y: "+=3rem" }, { scale: 1.05 , rotate: '180deg', x: "+=3rem", y: "-=5rem", repeat: -1, yoyo: true, duration, ease: Sine.easeInOut }, 'start') + tl.fromTo(orbs[1], { scale: 1.05, rotate: '0deg', x: "-=3rem", y: "+=3rem" }, { scale: 1.1 , rotate: '180deg', x: "+=3rem", y: "-=3rem", repeat: -1, yoyo: true, duration, ease: Sine.easeInOut }, '<') + tl.fromTo(orbs[2], { scale: 1.2, rotate: '0deg', x: "-=3rem", y: "+=3rem" }, { scale: 1 , rotate: '180deg', x: "+=3rem", y: "-=6rem", repeat: -1, yoyo: true, duration, ease: Sine.easeInOut }, '<') + tl.yoyo(true); + }, orbContainerRef.current); + return () => context.revert(); + }); + + return !children ? ( + + +
+ + +
-
- ) + + ) : + +
+ + + +
+ {children} +
+ } diff --git a/src/common/FloatingOrbs/types.ts b/src/common/FloatingOrbs/types.ts index 0e113d5..ec1dda7 100644 --- a/src/common/FloatingOrbs/types.ts +++ b/src/common/FloatingOrbs/types.ts @@ -1,16 +1,33 @@ import tw, {styled} from 'twin.macro'; export const FloatingOrbContainer = styled.div` - ${tw`absolute top-0 right-0 w-full h-full z-[-20] flex justify-center items-center`} + ${tw`sm:mt-[-5rem] absolute top-0 right-0 w-full h-full z-[-51] flex justify-center items-center`} + overflow: clip; ` export const FloatingOrbBlurContainer = styled.div` content: ''; - display: block; height: 100%; width: 100%; position: absolute; top: 0; left: 0; - z-index: z-[-21]; - ${tw`backdrop-blur-[80px]`} + ${tw`bg-pretu bg-opacity-[0.3]`} + ${tw` z-[-21] backdrop-blur-[80px]`} +` + +export const FloatingOrbBlurContainerWithoutBackdrop = styled.div` + content: ''; + height: 100%; + width: 100%; + position: absolute; + top: 0; + left: 0; + ${tw`z-[-21] bg-pretu bg-opacity-[0.01]`} + -webkit-overflow: clip; +` + +export const Orb = styled.div` + ${tw`pointer-events-none absolute w-[350px] h-[350px] rounded-[100%] blur-[75px]`} + filter: blur(75px); + -webkit-filter: blur(75px); ` \ No newline at end of file diff --git a/src/components/CreateGameCard/CreateCoinFlipGameCard.tsx b/src/components/CreateGameCard/CreateCoinFlipGameCard.tsx index 55aa8c3..c97e9ef 100644 --- a/src/components/CreateGameCard/CreateCoinFlipGameCard.tsx +++ b/src/components/CreateGameCard/CreateCoinFlipGameCard.tsx @@ -167,8 +167,7 @@ export const CreateCoinFlipGameCard = ({ ref={formRef} > - - + {/* badge selection */}
diff --git a/src/components/CreateGameCard/CreateDiceGameCard.tsx b/src/components/CreateGameCard/CreateDiceGameCard.tsx index 2e17801..d573cba 100644 --- a/src/components/CreateGameCard/CreateDiceGameCard.tsx +++ b/src/components/CreateGameCard/CreateDiceGameCard.tsx @@ -33,7 +33,7 @@ import { ArrowDownIcon, StarLoaderIcon } from '../../assets/icons'; import tw from 'twin.macro'; import { useForm } from 'react-hook-form'; import { useBalance } from 'wagmi'; -import { useNavigateTo, useWegaStore, useCreateGameParams } from '../../hooks'; +import { useNavigateTo, useWegaStore, useCreateGameParams, useTokenUSDValue } from '../../hooks'; import { useCreateGameMutation } from '../../containers/App/api'; import { useCreateWagerAndDepositMutation, @@ -69,7 +69,6 @@ export const CreateDiceGameCard = ({ const [currentWagerType] = useState(wagerType); const [currentCurrencyType, setCurrentCurrencyType] = useState(currencyType); const {revealed, triggerRevealAnimation} = useFormReveal(false, formRef, detailsBlock); - const { register, formState: { errors }, watch, handleSubmit, setValue } = useForm({ mode: 'onChange', resolver: joiResolver(createGameSchema('wager')) , @@ -78,6 +77,7 @@ export const CreateDiceGameCard = ({ wager: 1 } }); + const wagerUSDValue = useTokenUSDValue(currentCurrencyType, watch('wager')); // approval for allowance const isWagerApproved = (allowance: number, wagerAmount: number) => allowance >= wagerAmount; @@ -91,7 +91,6 @@ export const CreateDiceGameCard = ({ const { data: userWagerBalance, isLoading: isWagerbalanceLoading } = useBalance({ address: wallet?.address as HexishString, token: tokenAddress, - enabled: false, cacheTime: 60_000, onError(error) { console.log('Error', error) @@ -165,7 +164,7 @@ export const CreateDiceGameCard = ({ onSubmit={handleSubmit(handleCreateGameClick)} ref={formRef} > - + {/* badge selection */} @@ -180,9 +179,10 @@ export const CreateDiceGameCard = ({ name="wager" render={({ message }) => {message} } /> - 00,00 USD + {/* should receive wager amount as input */} + {wagerUSDValue.loading ? 'loading...' : wagerUSDValue.value} USD Balance: { - isWagerbalanceLoading ? "Retrieving balance..." : userWagerBalance ? userWagerBalance?.formatted + ' ' + userWagerBalance?.symbol : 0 + isWagerbalanceLoading ? "Retrieving balance..." : userWagerBalance ? userWagerBalance?.formatted : String(0) } {/* useBalance from wagmi can be used here */}
diff --git a/src/components/CreateGameCard/apiSlice.ts b/src/components/CreateGameCard/apiSlice.ts index 2c8e931..ebff886 100644 --- a/src/components/CreateGameCard/apiSlice.ts +++ b/src/components/CreateGameCard/apiSlice.ts @@ -29,5 +29,4 @@ appApiSlice.enhanceEndpoints({ addTagTypes: ['Games'], endpoints: { createGame: { invalidatesTags: [ { type: 'Games', id: 'LIST' } ]} } }); - export const { useCreateGameMutation } = createGameApiSlice; diff --git a/src/components/CreateGameCard/types.tsx b/src/components/CreateGameCard/types.tsx index 31f3ff6..2355634 100644 --- a/src/components/CreateGameCard/types.tsx +++ b/src/components/CreateGameCard/types.tsx @@ -7,6 +7,7 @@ export const CreateGameCardContainer = styled.div` flex-direction: column; align-items: center; gap: 32px; + ${tw`dark:bg-[#282828] rounded-[10px] border border-[2px] border-[#343434]`} ` export const InputBox = styled.input` @@ -16,9 +17,7 @@ export const InputBox = styled.input` line-height: 68px; ${tw`bg-inherit focus:outline-none`} text-align: center; - -moz-appearance: textfield; - &::-webkit-outer-spin-button, &::-webkit-inner-spin-button { -webkit-appearance: none; diff --git a/src/hooks/index.ts b/src/hooks/index.ts index b709dee..4391492 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -4,4 +4,5 @@ export { useWegaStore } from './useWegaStore'; export { useBlockchainApiHooks } from './useBlockchainApiHooks'; export { useNavigateTo } from './useNavigateTo'; export { useFirebaseData } from './useFirebaseData'; -export { useCreateGameParams } from './useCreateGameParams'; \ No newline at end of file +export { useCreateGameParams } from './useCreateGameParams'; +export { useTokenUSDValue } from './useTokenUSDValue'; \ No newline at end of file diff --git a/src/hooks/useTokenUSDValue.ts b/src/hooks/useTokenUSDValue.ts new file mode 100644 index 0000000..4dd75b5 --- /dev/null +++ b/src/hooks/useTokenUSDValue.ts @@ -0,0 +1,10 @@ +import { cryptoComparePriceApi } from "../utils/price" +import { useAsync } from 'react-use'; +import { AllPossibleCurrencyTypes } from "../models"; +export function useTokenUSDValue(token: AllPossibleCurrencyTypes, amount: number) { + const price = useAsync(async () => { + const price = Number(await cryptoComparePriceApi.fetchUsdPriceOf(token)); + return parseFloat(String(amount * price)).toFixed(2); + }, [token, amount]); + return price; +} diff --git a/src/libs/drand/index.ts b/src/libs/drand/index.ts new file mode 100644 index 0000000..97979f4 --- /dev/null +++ b/src/libs/drand/index.ts @@ -0,0 +1,70 @@ +// import { useLocalStorage } from 'react-use' +import { convertBytesToNumber } from '../../utils' +type DrandMeta = { chainHash: string; round: number; signature: string; randomness: bigint;} +interface IDrandClient { + getRandomNumber(): Promise; + getLastRequestedDrandMeta(): string | null; + // eslint-disable-next-line no-unused-vars + setDrandMeta(drandMeta: DrandMeta): void; +} + +class DrandClient implements IDrandClient { + private metaStorageKey = 'drand_meta' + private baseUrl = 'https://api.drand.sh'; + private fetchSettings = { + headers: { + 'Access-Control-Allow-Origin': '*' + }, + credentials: 'same-origin' as RequestCredentials, + cache: 'no-cache' as RequestCache + } + getLastRequestedDrandMeta() { + return JSON.parse(localStorage.getItem(this.metaStorageKey) as string) || null; + }; + setDrandMeta(drandMeta: DrandMeta){ + localStorage.setItem(this.metaStorageKey, JSON.stringify(drandMeta)); + } + async getRandomNumber(): Promise { + const meta = this.getLastRequestedDrandMeta(); + if(meta){ + const newRound = meta.round++; + const data = await this.fetchDrand(meta.chainHash, newRound); + this.setDrandMeta(data) + return data; + } else { + const data = await this.fetchDrand(meta.chainHash); + this.setDrandMeta(data); + return data; + } + } + + async fetchDrand(lastChainHash: string, lastRound?: number){ + let fulfilled = false; + let result: DrandMeta = { + round: 0, + randomness: 0n, + signature: "", + chainHash: lastChainHash + } + while (!fulfilled) { + const res = await fetch(this.parseFetchUrl(lastChainHash, lastRound), this.fetchSettings); + const resBody = await res.text(); + const { randomness, round, signature } = JSON.parse(resBody); + // set fulfilled to false; + if(randomness || round || signature) { + result = Object.assign(result, { + round, + randomness: convertBytesToNumber(randomness), + signature: signature, + chainHash: lastChainHash, + }) + fulfilled = true; + } + } + return result; + } + parseFetchUrl(chainHash: string, round?: number): string { + return this.baseUrl.concat(`${chainHash}/public/${round ? round : 'latest'}`); + } +} +export const drandClient = new DrandClient(); \ No newline at end of file diff --git a/src/models/currency.ts b/src/models/currency.ts index a0be7c0..dc04aae 100644 --- a/src/models/currency.ts +++ b/src/models/currency.ts @@ -1,7 +1,9 @@ // eslint-disable-next-line no-unused-vars -export enum CurrencyTypesEnum { USDC, USDT }; +export enum CurrencyTypesEnum { USDC, USDT, USD }; export const CurrencyTypes = { [CurrencyTypesEnum.USDC]: 'USDC', [CurrencyTypesEnum.USDT]: 'USDT', + [CurrencyTypesEnum.USD]: 'USD', } as const; + export type AllPossibleCurrencyTypes = typeof CurrencyTypes[keyof typeof CurrencyTypes]; \ No newline at end of file diff --git a/src/utils/ethers-helpers.ts b/src/utils/ethers-helpers.ts index dcd0749..e40f27b 100644 --- a/src/utils/ethers-helpers.ts +++ b/src/utils/ethers-helpers.ts @@ -1,4 +1,10 @@ -import { parseEther, BigNumberish, Interface } from "ethers"; +import { + parseEther, + BigNumberish, + Interface, + toBigInt, + solidityPackedSha256 +} from "ethers"; import { Player } from '../models' export function parseIntFromBigNumber(val: BigNumberish | number) { @@ -32,4 +38,8 @@ export function isGameCreator( return true; } return false; -} \ No newline at end of file +} + +export function convertBytesToNumber(bytes: string): bigint { + return toBigInt(solidityPackedSha256(['bytes'],["0x".concat(bytes)])); + } \ No newline at end of file diff --git a/src/utils/index.ts b/src/utils/index.ts index 276a64d..b6c6484 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -5,7 +5,8 @@ export { miniWalletAddress, toBigIntInWei, parseTopicDataFromEventLog, - interfaceIdFromAbi + interfaceIdFromAbi, + convertBytesToNumber } from './ethers-helpers.ts' export { deployed } from './deployment.ts' export { toastSettings } from './toast.ts' @@ -22,4 +23,6 @@ export { wegaErc20EscrowConfig, wegaGameControllerConfig, wegaRandomizerControllerConfig -} from './abis.ts' \ No newline at end of file +} from './abis.ts' + +export { cryptoComparePriceApi } from './price.ts' \ No newline at end of file diff --git a/src/utils/price.ts b/src/utils/price.ts new file mode 100644 index 0000000..2e94691 --- /dev/null +++ b/src/utils/price.ts @@ -0,0 +1,24 @@ +import { AllPossibleCurrencyTypes } from "../models"; + +interface IPriceAPI { + // eslint-disable-next-line no-unused-vars + fetchUsdPriceOf(token: AllPossibleCurrencyTypes): Promise; +} + +class CryptoComparePriceApi implements IPriceAPI { + private baseUrl: string = 'https://min-api.cryptocompare.com'; + async fetchUsdPriceOf(token: AllPossibleCurrencyTypes) { + try { + const response = await fetch(this.parseUrlForUSDPriceFetch(token)); + const data = await response.json(); + return data.USD; + } catch (e) { + console.log(e); + } + }; + parseUrlForUSDPriceFetch(token: AllPossibleCurrencyTypes): string { + return `${this.baseUrl}/data/price?fsym=${token}&tsyms=USD` + } +} +export const cryptoComparePriceApi = new CryptoComparePriceApi(); +