diff --git a/package-lock.json b/package-lock.json index 62257683..d322bcb6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,9 +14,14 @@ "@emotion/styled": "^11.11.0", "@fontsource/poppins": "^5.0.8", "@ls-lint/ls-lint": "^2.2.2", + "@noble/hashes": "^1.3.3", "@reduxjs/toolkit": "^1.9.7", + "@scure/base": "^1.1.5", + "@scure/bip32": "^1.3.3", + "@scure/btc-signer": "^1.2.1", "@trivago/prettier-plugin-sort-imports": "^4.2.1", "@types/chrome": "^0.0.248", + "bitcoinjs-lib": "^6.1.5", "concurrently": "^8.2.2", "decimal.js": "^10.4.3", "dotenv": "^16.3.1", @@ -27,6 +32,7 @@ "eslint-plugin-react": "^7.33.2", "ethers": "5.7.2", "formik": "^2.4.5", + "micro-packed": "^0.5.1", "prettier": "^3.0.3", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -2538,6 +2544,28 @@ "ls-lint": "bin/cli.js" } }, + "node_modules/@noble/curves": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz", + "integrity": "sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==", + "dependencies": { + "@noble/hashes": "1.3.3" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "license": "MIT", @@ -2630,6 +2658,41 @@ "darwin" ] }, + "node_modules/@scure/base": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.5.tgz", + "integrity": "sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.3.tgz", + "integrity": "sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ==", + "dependencies": { + "@noble/curves": "~1.3.0", + "@noble/hashes": "~1.3.2", + "@scure/base": "~1.1.4" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/btc-signer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@scure/btc-signer/-/btc-signer-1.2.1.tgz", + "integrity": "sha512-/Zle18/aWhYDBuBeXGDGJTdo0/LKpQhU8ETBJeWABCQkbk0QHCFCinidTiz9hdQFfh0HtasPGq5p6EodVCfEew==", + "dependencies": { + "@noble/curves": "~1.3.0", + "@noble/hashes": "~1.3.3", + "@scure/base": "~1.1.5", + "micro-packed": "~0.5.1" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "license": "MIT" @@ -3443,10 +3506,44 @@ "version": "1.0.2", "license": "MIT" }, + "node_modules/base-x": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", + "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" + }, "node_modules/bech32": { "version": "1.1.4", "license": "MIT" }, + "node_modules/bip174": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.1.1.tgz", + "integrity": "sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/bitcoinjs-lib": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.5.tgz", + "integrity": "sha512-yuf6xs9QX/E8LWE2aMJPNd0IxGofwfuVOiYdNUESkc+2bHHVKjhJd8qewqapeoolh9fihzHGoDCB5Vkr57RZCQ==", + "dependencies": { + "@noble/hashes": "^1.2.0", + "bech32": "^2.0.0", + "bip174": "^2.1.1", + "bs58check": "^3.0.1", + "typeforce": "^1.11.3", + "varuint-bitcoin": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/bitcoinjs-lib/node_modules/bech32": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" + }, "node_modules/bn.js": { "version": "5.2.1", "license": "MIT" @@ -3504,6 +3601,23 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bs58": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", + "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", + "dependencies": { + "base-x": "^4.0.0" + } + }, + "node_modules/bs58check": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-3.0.1.tgz", + "integrity": "sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==", + "dependencies": { + "@noble/hashes": "^1.2.0", + "bs58": "^5.0.0" + } + }, "node_modules/cac": { "version": "6.7.14", "license": "MIT", @@ -5452,6 +5566,17 @@ "node": ">= 8" } }, + "node_modules/micro-packed": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/micro-packed/-/micro-packed-0.5.1.tgz", + "integrity": "sha512-VjBHcsMAVfivjCZPnqAEEkcihPBbNd39KLEMH76ksL3ORKSZE04gkrtsAmXtaTor67PmTO5h0Rq9+j3PA4zNrw==", + "dependencies": { + "@scure/base": "~1.1.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/micromatch": { "version": "4.0.5", "license": "MIT", @@ -6333,6 +6458,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/safe-regex-test": { "version": "1.0.0", "license": "MIT", @@ -6803,6 +6947,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typeforce": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", + "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" + }, "node_modules/typescript": { "version": "5.3.2", "license": "Apache-2.0", @@ -6917,6 +7066,14 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/varuint-bitcoin": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz", + "integrity": "sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==", + "dependencies": { + "safe-buffer": "^5.1.1" + } + }, "node_modules/vite": { "version": "4.5.0", "dev": true, diff --git a/package.json b/package.json index 4e21eddd..605dc358 100644 --- a/package.json +++ b/package.json @@ -24,9 +24,14 @@ "@emotion/styled": "^11.11.0", "@fontsource/poppins": "^5.0.8", "@ls-lint/ls-lint": "^2.2.2", + "@noble/hashes": "^1.3.3", "@reduxjs/toolkit": "^1.9.7", + "@scure/base": "^1.1.5", + "@scure/bip32": "^1.3.3", + "@scure/btc-signer": "^1.2.1", "@trivago/prettier-plugin-sort-imports": "^4.2.1", "@types/chrome": "^0.0.248", + "bitcoinjs-lib": "^6.1.5", "concurrently": "^8.2.2", "decimal.js": "^10.4.3", "dotenv": "^16.3.1", @@ -37,6 +42,7 @@ "eslint-plugin-react": "^7.33.2", "ethers": "5.7.2", "formik": "^2.4.5", + "micro-packed": "^0.5.1", "prettier": "^3.0.3", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/src/app/app.tsx b/src/app/app.tsx index 308339b6..b7c2b576 100644 --- a/src/app/app.tsx +++ b/src/app/app.tsx @@ -2,6 +2,7 @@ import { Route } from 'react-router-dom'; import { AppLayout } from '@components/app.layout'; import { MyVaults } from '@pages/my-vaults/my-vaults'; +import { ProofOfReservePage } from '@pages/proof-of-reserve/proof-of-reserve-page'; import { BalanceContextProvider } from '@providers/balance-context-provider'; import { About } from './pages/about/about'; @@ -18,6 +19,7 @@ export function App(): React.JSX.Element { } /> } /> } /> + } /> diff --git a/src/app/components/mint-unmint/components/lock-screen/lock-screen.tsx b/src/app/components/mint-unmint/components/lock-screen/lock-screen.tsx index 44c974ad..6edac5b3 100644 --- a/src/app/components/mint-unmint/components/lock-screen/lock-screen.tsx +++ b/src/app/components/mint-unmint/components/lock-screen/lock-screen.tsx @@ -1,25 +1,39 @@ -import { useContext, useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useDispatch } from 'react-redux'; -import { Button, VStack } from '@chakra-ui/react'; +import { Button, VStack, useToast } from '@chakra-ui/react'; import { VaultCard } from '@components/vault/vault-card'; +import { UseBitcoinReturnType } from '@hooks/use-bitcoin'; +import { UseEthereumReturnType } from '@hooks/use-ethereum'; +import { UseSignPSBTReturnType } from '@hooks/use-psbt'; import { useVaults } from '@hooks/use-vaults'; +import { BitcoinError } from '@models/error-types'; import { Vault } from '@models/vault'; -import { BlockchainContext } from '@providers/blockchain-context-provider'; import { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; import { LockScreenProtocolFee } from './components/protocol-fee'; interface LockScreenProps { + bitcoinHandler: UseBitcoinReturnType; + ethereumHandler: UseEthereumReturnType; + psbtHandler: UseSignPSBTReturnType; currentStep: [number, string]; } -export function LockScreen({ currentStep }: LockScreenProps): React.JSX.Element { +export function LockScreen({ + currentStep, + bitcoinHandler, + psbtHandler, + ethereumHandler, +}: LockScreenProps): React.JSX.Element { + const toast = useToast(); const dispatch = useDispatch(); + const { readyVaults } = useVaults(); - const blockchainContext = useContext(BlockchainContext); - const bitcoin = blockchainContext?.bitcoin; - const ethereum = blockchainContext?.ethereum; + + const { bitcoinPrice } = bitcoinHandler; + const { getProtocolFee } = ethereumHandler; + const { handleSignFundingTransaction } = psbtHandler; const [isSubmitting, setIsSubmitting] = useState(false); const [protocolFeePercentage, setProtocolFeePercentage] = useState(undefined); @@ -28,21 +42,31 @@ export function LockScreen({ currentStep }: LockScreenProps): React.JSX.Element useEffect(() => { const fetchProtocolFeePercentage = async () => { - const currentProtocolFeePercentage = await ethereum?.getProtocolFee(); + const currentProtocolFeePercentage = await getProtocolFee(); setProtocolFeePercentage(currentProtocolFeePercentage); }; fetchProtocolFeePercentage(); - }, [ethereum]); + }, [getProtocolFee]); async function handleClick(currentVault?: Vault) { if (!currentVault) return; try { setIsSubmitting(true); - await bitcoin?.fetchBitcoinContractOfferAndSendToUserWallet(currentVault); + await handleSignFundingTransaction(currentVault.collateral, currentVault.uuid); + setTimeout(() => { + dispatch(mintUnmintActions.setMintStep([2, currentVault.uuid])); + setIsSubmitting(false); + }, 3000); } catch (error) { setIsSubmitting(false); - throw new Error('Error locking vault'); + toast({ + title: 'Failed to sign transaction', + description: error instanceof BitcoinError ? error.message : '', + status: 'error', + duration: 9000, + isClosable: true, + }); } } @@ -51,7 +75,7 @@ export function LockScreen({ currentStep }: LockScreenProps): React.JSX.Element + + + ); +} diff --git a/src/app/components/mint-unmint/components/mint/mint.tsx b/src/app/components/mint-unmint/components/mint/mint.tsx index 72ed1577..c3459795 100644 --- a/src/app/components/mint-unmint/components/mint/mint.tsx +++ b/src/app/components/mint-unmint/components/mint/mint.tsx @@ -1,9 +1,12 @@ import { useSelector } from 'react-redux'; import { HStack } from '@chakra-ui/react'; +import { useBlockchainContext } from '@hooks/use-blockchain-context'; +import { usePSBT } from '@hooks/use-psbt'; import { RootState } from '@store/index'; import { LockScreen } from '../lock-screen/lock-screen'; +import { SignClosingTransactionScreen } from '../lock-screen/sign-closing-transaction-screen'; import { ProgressTimeline } from '../progress-timeline/progress-timeline'; import { TransactionForm } from '../transaction-form/transaction-form'; import { TransactionSummary } from '../transaction-summary/transaction-summary'; @@ -11,6 +14,9 @@ import { Walkthrough } from '../walkthrough/walkthrough'; import { MintLayout } from './components/mint.layout'; export function Mint(): React.JSX.Element { + const { bitcoin, ethereum } = useBlockchainContext(); + const psbtHandler = usePSBT(bitcoin); + const { mintStep } = useSelector((state: RootState) => state.mintunmint); return ( @@ -19,8 +25,23 @@ export function Mint(): React.JSX.Element { {[0].includes(mintStep[0]) && } - {[1].includes(mintStep[0]) && } - {[2, 3].includes(mintStep[0]) && ( + {[1].includes(mintStep[0]) && ( + + )} + {[2].includes(mintStep[0]) && ( + + )} + {[3, 4].includes(mintStep[0]) && ( )} diff --git a/src/app/components/mint-unmint/components/progress-timeline/components/progress-timeline-step.tsx b/src/app/components/mint-unmint/components/progress-timeline/components/progress-timeline-step.tsx index e5ea120c..92d88422 100644 --- a/src/app/components/mint-unmint/components/progress-timeline/components/progress-timeline-step.tsx +++ b/src/app/components/mint-unmint/components/progress-timeline/components/progress-timeline-step.tsx @@ -1,10 +1,12 @@ -import { Divider, Text } from '@chakra-ui/react'; +import { Divider, Stack, Text } from '@chakra-ui/react'; import { StepIconOne, StepIconThree, StepIconTwo } from '../../../../../../styles/icon'; interface StepProps { + width?: string; currentStep: number; stepIndex: number; + isFirstStep?: boolean; isLastStep?: boolean; title?: string; } @@ -48,15 +50,25 @@ export function StepGraphics({ ); } -export function StepText({ currentStep, stepIndex, title }: StepProps): React.JSX.Element { +export function StepText({ + currentStep, + stepIndex, + width, + title, + isFirstStep = false, + isLastStep = false, +}: StepProps): React.JSX.Element { return ( - = stepIndex ? 'accent.cyan.01' : 'white.01'} - fontSize={'xs'} - fontWeight={currentStep === stepIndex ? 800 : 400} - opacity={currentStep > stepIndex ? '50%' : '100%'} - > - {title} - + + = stepIndex ? 'accent.cyan.01' : 'white.01'} + fontSize={'xs'} + fontWeight={currentStep === stepIndex ? 800 : 400} + opacity={currentStep > stepIndex ? '50%' : '100%'} + > + {title} + + ); } diff --git a/src/app/components/mint-unmint/components/progress-timeline/progress-timeline.tsx b/src/app/components/mint-unmint/components/progress-timeline/progress-timeline.tsx index e648b4b3..d9b5782b 100644 --- a/src/app/components/mint-unmint/components/progress-timeline/progress-timeline.tsx +++ b/src/app/components/mint-unmint/components/progress-timeline/progress-timeline.tsx @@ -31,12 +31,31 @@ export function ProgressTimeline({ - + + - - - + + + + ); diff --git a/src/app/components/mint-unmint/components/protocol-summary-stack/protocol-summary-stack.tsx b/src/app/components/mint-unmint/components/protocol-summary-stack/protocol-summary-stack.tsx index 08137f2a..9d6495b5 100644 --- a/src/app/components/mint-unmint/components/protocol-summary-stack/protocol-summary-stack.tsx +++ b/src/app/components/mint-unmint/components/protocol-summary-stack/protocol-summary-stack.tsx @@ -1,15 +1,16 @@ // import { ProtocolHistory } from "@components/protocol-history/protocol-history"; -import { useContext } from 'react'; - import { Skeleton, Text, VStack } from '@chakra-ui/react'; -import { BlockchainContext } from '@providers/blockchain-context-provider'; +import { useBlockchainContext } from '@hooks/use-blockchain-context'; import { ProtocolSummaryStackLayout } from './components/protocol-summary-stack.layout'; export function ProtocolSummaryStack(): React.JSX.Element { - const blockchainContext = useContext(BlockchainContext); - const totalSupply = blockchainContext?.ethereum.totalSupply; - const bitcoinPrice = blockchainContext?.bitcoin.bitcoinPrice; + const blockchainContext = useBlockchainContext(); + const { ethereum, bitcoin } = blockchainContext; + + const { totalSupply } = ethereum; + const { bitcoinPrice } = bitcoin; + return ( diff --git a/src/app/components/mint-unmint/components/transaction-form/transaction-form.tsx b/src/app/components/mint-unmint/components/transaction-form/transaction-form.tsx index fec9e1e1..4309beae 100644 --- a/src/app/components/mint-unmint/components/transaction-form/transaction-form.tsx +++ b/src/app/components/mint-unmint/components/transaction-form/transaction-form.tsx @@ -1,9 +1,9 @@ -import { useContext, useState } from 'react'; +import { useState } from 'react'; import { Button, FormControl, FormErrorMessage, Text, VStack, useToast } from '@chakra-ui/react'; import { customShiftValue } from '@common/utilities'; +import { useBlockchainContext } from '@hooks/use-blockchain-context'; import { EthereumError } from '@models/error-types'; -import { BlockchainContext } from '@providers/blockchain-context-provider'; import { Form, Formik } from 'formik'; import { TransactionFormInput } from './components/transaction-form-input'; @@ -17,16 +17,18 @@ const initialValues: TransactionFormValues = { amount: 0.001 }; export function TransactionForm(): React.JSX.Element { const toast = useToast(); - const blockchainContext = useContext(BlockchainContext); - const ethereum = blockchainContext?.ethereum; - const bitcoinPrice = blockchainContext?.bitcoin.bitcoinPrice; + const blockchainContext = useBlockchainContext(); + const { ethereum, bitcoin } = blockchainContext; + const { setupVault } = ethereum; + const { bitcoinPrice } = bitcoin; + const [isSubmitting, setIsSubmitting] = useState(false); async function handleSetup(btcDepositAmount: number) { try { setIsSubmitting(true); const shiftedBTCDepositAmount = customShiftValue(btcDepositAmount, 8, false); - await ethereum?.setupVault(shiftedBTCDepositAmount); + await setupVault(shiftedBTCDepositAmount); } catch (error) { setIsSubmitting(false); toast({ diff --git a/src/app/components/mint-unmint/components/walkthrough/walkthrough.tsx b/src/app/components/mint-unmint/components/walkthrough/walkthrough.tsx index 7ab2a4d0..77c748b5 100644 --- a/src/app/components/mint-unmint/components/walkthrough/walkthrough.tsx +++ b/src/app/components/mint-unmint/components/walkthrough/walkthrough.tsx @@ -1,9 +1,7 @@ -import { useContext } from 'react'; - import { Button, HStack, Image, Link, Text } from '@chakra-ui/react'; import { TutorialVideo } from '@components/tutorial-video/tutorial-video'; +import { useBlockchainContext } from '@hooks/use-blockchain-context'; -import { BlockchainContext } from '../../../../providers/blockchain-context-provider'; import { WalkthroughHeader } from './components/walkthrough-header'; import { WalkthroughLayout } from './components/walkthrough.layout'; @@ -13,8 +11,9 @@ interface WalkthroughProps { } export function Walkthrough({ flow, currentStep }: WalkthroughProps): React.JSX.Element { - const blockchainContext = useContext(BlockchainContext); - const ethereum = blockchainContext?.ethereum; + const blockchainContext = useBlockchainContext(); + const { ethereum } = blockchainContext; + const { recommendTokenToMetamask } = ethereum; switch (flow) { case 'mint': @@ -68,6 +67,28 @@ export function Walkthrough({ flow, currentStep }: WalkthroughProps): React.JSX. ); case 2: + return ( + + + + Sign the closing transaction in your{' '} + + Bitcoin Wallet{' '} + + which will be broadcasted once the dlcBTC is redeemed. + + + ); + case 3: return ( add them to your Ethereum Wallet. -