diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..cc8d72b8 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "trailingComma": "none", + "tabWidth": 4 +} \ No newline at end of file diff --git a/README.md b/README.md index 58478c4c..81d95be3 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ VeWorld WalletKit is a typescript library that facilitates seamless interaction ## Why ? -- Allow easy interaction with all wallets. -- Currently, connex only plays nice with Sync / Sync2 -- Enable a better UX for users +- Allow easy interaction with all wallets. +- Currently, connex only plays nice with Sync / Sync2 +- Enable a better UX for users ## Key features @@ -14,19 +14,19 @@ Key Features a.k.a scope 1. wallet connectivity - key components that handle interaction with veworld and sync 2 + key components that handle interaction with veworld and sync 2 2. customizable UI - ability to totally customize the UI of components + ability to totally customize the UI of components 3. User Experience - Consistent experience with Ethereum and other chains + Consistent experience with Ethereum and other chains 4. Developer friendly - Easy to adopt with proper documentation. + Easy to adopt with proper documentation. ## Setting up the project (Common flow) @@ -70,4 +70,4 @@ yarn build ### Future Work -- Create a Modal/ Library in React / Vue / Angular +- Create a Modal/ Library in React / Vue / Angular diff --git a/apps/sample-react-app/hardhat.config.ts b/apps/sample-react-app/hardhat.config.ts index 6c392940..1208d13b 100644 --- a/apps/sample-react-app/hardhat.config.ts +++ b/apps/sample-react-app/hardhat.config.ts @@ -1,11 +1,11 @@ -import { HardhatUserConfig } from "hardhat/config"; -import "@nomicfoundation/hardhat-toolbox"; +import { HardhatUserConfig } from 'hardhat/config'; +import '@nomicfoundation/hardhat-toolbox'; const config: HardhatUserConfig = { - solidity: "0.8.19", - typechain: { - outDir: "src/hardhat", - }, + solidity: '0.8.19', + typechain: { + outDir: 'src/hardhat' + } }; export default config; diff --git a/apps/sample-react-app/package.json b/apps/sample-react-app/package.json index a62a7fb8..ffb1515a 100644 --- a/apps/sample-react-app/package.json +++ b/apps/sample-react-app/package.json @@ -1,88 +1,102 @@ { - "name": "sample-app-react", - "version": "0.0.0", - "private": true, - "homepage": ".", - "main": "src/index.js", - "scripts": { - "build": "yarn compile && react-app-rewired build", - "clean": "rm -rf build node_modules .turbo cache artifacts src/hardhat", - "compile": "yarn hardhat compile", - "dev": "react-app-rewired start", - "eject": "react-app-rewired eject", - "postinstall": "yarn compile", - "lint": "eslint src --ext .js,.jsx,.ts,.tsx", - "start": "HTTPS=true react-app-rewired start", - "test": "echo 'Not yet testing'" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ] - }, - "dependencies": { - "@chakra-ui/react": "^2.8.1", - "@emotion/react": "^11.11.1", - "@emotion/styled": "^11.11.0", - "@heroicons/react": "^2.0.18", - "@vechain/connex": "2.1.0", - "@vechain/hardhat-vechain": "^0.1.4", - "@vechain/hardhat-web3": "^0.1.4", - "@vechain/picasso": "^2.1.1", - "@vechain/web3-providers-connex": "^1.1.2", - "buffer": "^6.0.3", - "crypto-browserify": "^3.12.0", - "ethers": "^6.8.0", - "framer-motion": "3.10.6", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "process": "^0.11.10", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-router-dom": "^6.4.2", - "react-scripts": "5.0.1", - "react-vendor": "*", - "stream-browserify": "^3.0.0", - "stream-http": "^3.2.0", - "thor-devkit": "^2.0.9", - "wallet-connect": "*", - "web-vitals": "^2.1.2" - }, - "devDependencies": { - "@nomicfoundation/hardhat-toolbox": "^3.0.0", - "@testing-library/jest-dom": "^5.16.1", - "@testing-library/react": "^12.1.2", - "@testing-library/user-event": "^13.5.0", - "@types/jest": "^27.0.3", - "@types/node": "^18.17.2", - "@types/react": "^18.2.28", - "@types/react-dom": "^18.2.13", - "@types/testing-library__jest-dom": "^5.14.5", - "copy-webpack-plugin": "^11.0.0", - "customize-cra": "^1.0.0", - "eslint-config-custom": "*", - "filemanager-webpack-plugin": "^8.0.0", - "hardhat": "^2.18.2", - "mini-css-extract-plugin": "^2.7.6", - "react-app-rewired": "^2.2.1", - "ts-import-plugin": "^3.0.0", - "ts-loader": "^9.5.0", - "ts-node": "^10.9.1", - "tsconfig": "*", - "typescript": "4.9.5", - "webpack": "^5.88.2" - } + "name": "sample-app-react", + "version": "0.0.0", + "private": true, + "homepage": ".", + "main": "src/index.js", + "scripts": { + "build": "yarn compile && react-app-rewired build", + "clean": "rm -rf build node_modules .turbo cache artifacts src/hardhat", + "compile": "yarn hardhat compile", + "dev": "react-app-rewired start", + "eject": "react-app-rewired eject", + "postinstall": "yarn compile", + "lint": "eslint src --ext .js,.jsx,.ts,.tsx", + "start": "HTTPS=true react-app-rewired start", + "test": "echo 'Not yet testing'" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "dependencies": { + "@chakra-ui/react": "^2.8.1", + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", + "@heroicons/react": "^2.0.18", + "@vechain/connex": "2.1.0", + "@vechain/hardhat-vechain": "^0.1.4", + "@vechain/hardhat-web3": "^0.1.4", + "@vechain/picasso": "^2.1.1", + "@vechain/react-wallet-kit": "*", + "@vechain/wallet-connect": "*", + "@vechain/wallet-kit": "*", + "@vechain/web3-providers-connex": "^1.1.2", + "buffer": "^6.0.3", + "crypto-browserify": "^3.12.0", + "framer-motion": "3.10.6", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "process": "^0.11.10", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.4.2", + "react-scripts": "5.0.1", + "stream-browserify": "^3.0.0", + "stream-http": "^3.2.0", + "thor-devkit": "^2.0.9", + "url": "^0.11.3", + "web-vitals": "^2.1.2" + }, + "devDependencies": { + "@nomicfoundation/hardhat-chai-matchers": "^2.0.0", + "@nomicfoundation/hardhat-ethers": "^3.0.0", + "@nomicfoundation/hardhat-network-helpers": "^1.0.0", + "@nomicfoundation/hardhat-toolbox": "^3.0.0", + "@nomicfoundation/hardhat-verify": "^1.0.0", + "@testing-library/jest-dom": "^5.16.1", + "@testing-library/react": "^12.1.2", + "@testing-library/user-event": "^13.5.0", + "@typechain/ethers-v6": "^0.4.0", + "@typechain/hardhat": "^8.0.0", + "@types/chai": "^4.2.0", + "@types/jest": "^27.0.3", + "@types/mocha": ">=9.1.0", + "@types/node": "^18.17.2", + "@types/react": "^18.2.28", + "@types/react-dom": "^18.2.13", + "@types/testing-library__jest-dom": "^5.14.5", + "chai": "^4.2.0", + "copy-webpack-plugin": "^11.0.0", + "customize-cra": "^1.0.0", + "eslint-config-custom": "*", + "ethers": "^6.4.0", + "filemanager-webpack-plugin": "^8.0.0", + "hardhat": "^2.18.2", + "hardhat-gas-reporter": "^1.0.8", + "mini-css-extract-plugin": "^2.7.6", + "react-app-rewired": "^2.2.1", + "solidity-coverage": "^0.8.1", + "ts-import-plugin": "^3.0.0", + "ts-loader": "^9.5.0", + "ts-node": "^10.9.1", + "tsconfig": "*", + "typechain": "^8.2.0", + "typescript": "4.9.5", + "webpack": "^5.88.2" + } } diff --git a/apps/sample-react-app/src/App.tsx b/apps/sample-react-app/src/App.tsx index dd8ca265..5771e94b 100644 --- a/apps/sample-react-app/src/App.tsx +++ b/apps/sample-react-app/src/App.tsx @@ -1,50 +1,50 @@ -import type { JSX } from "react"; -import React from "react"; -import type { Options } from "@vechain/connex"; -import type { WalletConnectOptions } from "wallet-connect"; -import { ConnexProvider } from "react-vendor"; -import { BrowserRouter, Route, Routes } from "react-router-dom"; -import { ChakraProvider } from "@chakra-ui/react"; -import { NavBar, StyledContainer } from "./Components/layout"; -import { Homepage } from "./Screens/Homepage"; -import { Fonts, theme } from "./Styles"; +import type { JSX } from 'react'; +import React from 'react'; +import type { Options } from '@vechain/connex'; +import type { WalletConnectOptions } from '@vechain/wallet-connect'; +import { ConnexProvider } from '@vechain/react-wallet-kit'; +import { BrowserRouter, Route, Routes } from 'react-router-dom'; +import { ChakraProvider } from '@chakra-ui/react'; +import { NavBar, StyledContainer } from './Components/layout'; +import { Homepage } from './Screens/Homepage'; +import { Fonts, theme } from './Styles'; -const nodeOptions: Omit = { - node: "https://testnet.vechain.org/", - network: "test", +const nodeOptions: Omit = { + node: 'https://testnet.vechain.org/', + network: 'test' }; const walletConnectOptions: WalletConnectOptions = { - projectId: "8dfc5cac972ee656e6edeb8309ab30ec", - metadata: { - name: "Sample VeChain dApp", - description: "A sample VeChain dApp", - url: window.location.origin, - icons: [`${window.location.origin}/images/logo/my-dapp.png`], - }, + projectId: '8dfc5cac972ee656e6edeb8309ab30ec', + metadata: { + name: 'Sample VeChain dApp', + description: 'A sample VeChain dApp', + url: window.location.origin, + icons: [`${window.location.origin}/images/logo/my-dapp.png`] + } }; export const App = (): JSX.Element => { - return ( - <> - - - - - - - - } path="/" /> - - - - - - - ); + return ( + <> + + + + + + + + } path="/" /> + + + + + + + ); }; diff --git a/apps/sample-react-app/src/Components/AccountDetailBody.tsx b/apps/sample-react-app/src/Components/AccountDetailBody.tsx index 5a499602..b3721bda 100644 --- a/apps/sample-react-app/src/Components/AccountDetailBody.tsx +++ b/apps/sample-react-app/src/Components/AccountDetailBody.tsx @@ -1,50 +1,55 @@ -import { Button, HStack, Image, Text, VStack } from "@chakra-ui/react"; -import React, { useMemo } from "react"; -import type { WalletSource } from "react-vendor"; -import { WalletSources } from "../Constants"; -import { AddressButton } from "./AddressButton"; +import { Button, HStack, Image, Text, VStack } from '@chakra-ui/react'; +import React, { useMemo } from 'react'; +import type { WalletSource } from '@vechain/wallet-kit'; +import { WalletSources } from '../Constants'; +import { AddressButton } from './AddressButton'; interface AccountDetailBodyProps { - accountAddress: string; - source: WalletSource; - disconnectWallet: () => void; + accountAddress: string; + source: WalletSource; + disconnectWallet: () => void; } export const AccountDetailBody: React.FC = ({ - accountAddress, - source, - disconnectWallet, + accountAddress, + source, + disconnectWallet }) => { - const sourceInfo = useMemo(() => WalletSources[source], [source]); + const sourceInfo = useMemo(() => WalletSources[source], [source]); - return ( - <> - - - - Account - - - - - - Source - - - {`${sourceInfo.name}-logo`} - {sourceInfo.name} - - - - - - ); + return ( + <> + + + + Account + + + + + + Source + + + {`${sourceInfo.name}-logo`} + {sourceInfo.name} + + + + + + ); }; diff --git a/apps/sample-react-app/src/Components/AccountDetailModal.tsx b/apps/sample-react-app/src/Components/AccountDetailModal.tsx index 2e6fe428..0af149ca 100644 --- a/apps/sample-react-app/src/Components/AccountDetailModal.tsx +++ b/apps/sample-react-app/src/Components/AccountDetailModal.tsx @@ -1,57 +1,57 @@ -import { HStack, Text } from "@chakra-ui/react"; -import type { WalletSource } from "react-vendor"; -import { useWallet } from "react-vendor"; -import React, { useCallback } from "react"; -import { getPicassoImgSrc } from "../Utils/AccountUtils"; -import { Dialog } from "./shared"; -import { AccountDetailBody } from "./AccountDetailBody"; +import { HStack, Text } from '@chakra-ui/react'; +import type { WalletSource } from '@vechain/wallet-kit'; +import { useWallet } from '@vechain/react-wallet-kit'; +import React, { useCallback } from 'react'; +import { getPicassoImgSrc } from '../Utils/AccountUtils'; +import { Dialog } from './shared'; +import { AccountDetailBody } from './AccountDetailBody'; interface AccountDetailModalProps { - isOpen: boolean; - onClose: () => void; - address: string; - source: WalletSource; + isOpen: boolean; + onClose: () => void; + address: string; + source: WalletSource; } export const AccountDetailModal: React.FC = ({ - isOpen, - onClose, - address, - source, + isOpen, + onClose, + address, + source }) => { - const { disconnect } = useWallet(); + const { disconnect } = useWallet(); - const disconnectWallet = useCallback((): void => { - disconnect(); - onClose(); - }, [disconnect, onClose]); + const disconnectWallet = useCallback((): void => { + disconnect(); + onClose(); + }, [disconnect, onClose]); - const header = ( - - Connected Wallet - - ); + const header = ( + + Connected Wallet + + ); - return ( - + } + closeButtonStyle={{ color: 'white' }} + header={header} + headerStyle={{ p: 0 }} + isOpen={isOpen} + onClose={onClose} /> - } - closeButtonStyle={{ color: "white" }} - header={header} - headerStyle={{ p: 0 }} - isOpen={isOpen} - onClose={onClose} - /> - ); + ); }; diff --git a/apps/sample-react-app/src/Components/AddressButton.tsx b/apps/sample-react-app/src/Components/AddressButton.tsx index d2f9089e..f0632a50 100644 --- a/apps/sample-react-app/src/Components/AddressButton.tsx +++ b/apps/sample-react-app/src/Components/AddressButton.tsx @@ -1,60 +1,60 @@ -import type { HTMLChakraProps } from "@chakra-ui/react"; -import { Button, HStack, Icon, Text, useClipboard } from "@chakra-ui/react"; -import { CheckIcon, DocumentDuplicateIcon } from "@heroicons/react/24/solid"; -import React, { useCallback, useEffect } from "react"; -import { friendlyAddress } from "../Utils/AccountUtils"; -import { AddressIcon } from "./AddressIcon"; +import type { HTMLChakraProps } from '@chakra-ui/react'; +import { Button, HStack, Icon, Text, useClipboard } from '@chakra-ui/react'; +import { CheckIcon, DocumentDuplicateIcon } from '@heroicons/react/24/solid'; +import React, { useCallback, useEffect } from 'react'; +import { friendlyAddress } from '../Utils/AccountUtils'; +import { AddressIcon } from './AddressIcon'; -interface AddressButtonProps extends HTMLChakraProps<"button"> { - address: string; - showAddressIcon?: boolean; - showCopyIcon?: boolean; +interface AddressButtonProps extends HTMLChakraProps<'button'> { + address: string; + showAddressIcon?: boolean; + showCopyIcon?: boolean; } export const AddressButton: React.FC = ({ - address, - showAddressIcon = true, - showCopyIcon = true, - ...props + address, + showAddressIcon = true, + showCopyIcon = true, + ...props }) => { - const { onCopy, hasCopied, setValue } = useClipboard(address); + const { onCopy, hasCopied, setValue } = useClipboard(address); - const { onClick, ...otherProps } = props; + const { onClick, ...otherProps } = props; - const onClickHandler = useCallback( - (e: React.MouseEvent): void => { - // console.log(onClick) - if (onClick) onClick(e); - if (showCopyIcon) onCopy(); - }, - [onClick, showCopyIcon, onCopy] - ); + const onClickHandler = useCallback( + (e: React.MouseEvent): void => { + // console.log(onClick) + if (onClick) onClick(e); + if (showCopyIcon) onCopy(); + }, + [onClick, showCopyIcon, onCopy] + ); - useEffect(() => { - setValue(address); - }, [setValue, address]); + useEffect(() => { + setValue(address); + }, [setValue, address]); - return ( - - ); + return ( + + ); }; diff --git a/apps/sample-react-app/src/Components/AddressIcon.tsx b/apps/sample-react-app/src/Components/AddressIcon.tsx index 64d2a1a1..ff0eb489 100644 --- a/apps/sample-react-app/src/Components/AddressIcon.tsx +++ b/apps/sample-react-app/src/Components/AddressIcon.tsx @@ -1,30 +1,30 @@ -import React from "react"; -import type { HTMLChakraProps } from "@chakra-ui/react"; -import { Img } from "@chakra-ui/react"; -import { getPicassoImgSrc } from "../Utils/AccountUtils"; +import React from 'react'; +import type { HTMLChakraProps } from '@chakra-ui/react'; +import { Img } from '@chakra-ui/react'; +import { getPicassoImgSrc } from '../Utils/AccountUtils'; -interface AddressIconProps extends HTMLChakraProps<"img"> { - address: string; +interface AddressIconProps extends HTMLChakraProps<'img'> { + address: string; } export const AddressIcon: React.FC = ({ - address, - ...props + address, + ...props }) => { - return ; + return ; }; -interface PicassoProps extends HTMLChakraProps<"img"> { - address: string; +interface PicassoProps extends HTMLChakraProps<'img'> { + address: string; } const Picasso: React.FC = ({ address, ...props }) => { - return ( - - ); + return ( + + ); }; diff --git a/apps/sample-react-app/src/Components/ConnectWalletButton.tsx b/apps/sample-react-app/src/Components/ConnectWalletButton.tsx index 7ee62dc7..469bbae4 100644 --- a/apps/sample-react-app/src/Components/ConnectWalletButton.tsx +++ b/apps/sample-react-app/src/Components/ConnectWalletButton.tsx @@ -1,53 +1,53 @@ -import type { HTMLChakraProps } from "@chakra-ui/react"; -import { Button, Icon, useDisclosure } from "@chakra-ui/react"; -import { WalletIcon } from "@heroicons/react/24/solid"; -import React from "react"; -import { useWallet } from "react-vendor"; -import { AccountDetailModal } from "./AccountDetailModal"; -import { AddressButton } from "./AddressButton"; -import { ConnectWalletModal } from "./ConnectWalletModal"; +import type { HTMLChakraProps } from '@chakra-ui/react'; +import { Button, Icon, useDisclosure } from '@chakra-ui/react'; +import { WalletIcon } from '@heroicons/react/24/solid'; +import React from 'react'; +import { useWallet } from '@vechain/react-wallet-kit'; +import { AccountDetailModal } from './AccountDetailModal'; +import { AddressButton } from './AddressButton'; +import { ConnectWalletModal } from './ConnectWalletModal'; interface ConnectWalletButtonProps { - buttonProps?: HTMLChakraProps<"button">; + buttonProps?: HTMLChakraProps<'button'>; } export const ConnectWalletButton: React.FC = ({ - buttonProps, + buttonProps }): React.ReactElement => { - const { isOpen, onOpen, onClose } = useDisclosure(); + const { isOpen, onOpen, onClose } = useDisclosure(); - const { - accountState: { address, source }, - } = useWallet(); + const { + accountState: { address, source } + } = useWallet(); + + if (address && source) + return ( + <> + + + + ); - if (address && source) return ( - <> - - - + <> + + + ); - - return ( - <> - - - - ); }; diff --git a/apps/sample-react-app/src/Components/ConnectWalletModal.tsx b/apps/sample-react-app/src/Components/ConnectWalletModal.tsx index f6e80802..a9e21066 100644 --- a/apps/sample-react-app/src/Components/ConnectWalletModal.tsx +++ b/apps/sample-react-app/src/Components/ConnectWalletModal.tsx @@ -1,166 +1,169 @@ import { - Alert, - AlertIcon, - Box, - Button, - Flex, - HStack, - Icon, - Spinner, - Text, - useToast, - VStack, -} from "@chakra-ui/react"; -import { LinkIcon, WalletIcon } from "@heroicons/react/24/solid"; -import React, { useCallback, useState } from "react"; -import { Certificate } from "thor-devkit"; -import { useConnex, useWallet } from "react-vendor"; -import { Dialog } from "./shared"; -import { WalletSourceRadio } from "./WalletSourceRadio"; + Alert, + AlertIcon, + Box, + Button, + Flex, + HStack, + Icon, + Spinner, + Text, + useToast, + VStack +} from '@chakra-ui/react'; +import { LinkIcon, WalletIcon } from '@heroicons/react/24/solid'; +import React, { useCallback, useState } from 'react'; +import { Certificate } from 'thor-devkit'; +import { useConnex, useWallet } from '@vechain/react-wallet-kit'; +import { Dialog } from './shared'; +import { WalletSourceRadio } from './WalletSourceRadio'; interface ConnectedWalletDialogProps { - isOpen: boolean; - onClose: () => void; + isOpen: boolean; + onClose: () => void; } export const ConnectWalletModal: React.FC = ({ - isOpen, - onClose, + isOpen, + onClose }) => { - const header = ( - - - Connect Wallet - - ); - - return ( - } - header={header} - isOpen={isOpen} - onClose={onClose} - /> - ); + const header = ( + + + Connect Wallet + + ); + + return ( + } + header={header} + isOpen={isOpen} + onClose={onClose} + /> + ); }; interface ConnectedWalletBodyProps { - onClose: () => void; + onClose: () => void; } const ConnectedWalletBody: React.FC = ({ - onClose, + onClose }) => { - const toast = useToast(); - const { setAccount } = useWallet(); - const { vendor } = useConnex(); - - const [connectionLoading, setConnectionLoading] = useState(false); - const [connectionError, setConnectionError] = useState(""); - - const connectToWalletHandler = useCallback(async (): Promise => { - const message: Connex.Vendor.CertMessage = { - purpose: "identification", - payload: { - type: "text", - content: "Sign a certificate to prove your identity", - }, - }; - - const certResponse = await vendor.sign("cert", message).request(); - - const cert: Certificate = { - purpose: message.purpose, - payload: message.payload, - domain: certResponse.annex.domain, - timestamp: certResponse.annex.timestamp, - signer: certResponse.annex.signer, - signature: certResponse.signature, - }; - - Certificate.verify(cert); - - return cert; - }, [vendor]); - - const onSuccessfullConnection = useCallback( - (cert: Certificate): void => { - setAccount(cert.signer); - onClose(); - toast({ - title: "Wallet connected.", - description: `You've succesfully connected with wallet ${cert.signer}`, - status: "success", - position: "bottom-left", - duration: 5000, - isClosable: true, - }); - }, - [toast, setAccount, onClose] - ); - - const connectHandler = useCallback(async () => { - try { - setConnectionError(""); - setConnectionLoading(true); - - const cert = await connectToWalletHandler(); - - onSuccessfullConnection(cert); - } catch (e) { - if (e instanceof Error) { - setConnectionError(e.message); - } else { - setConnectionError("Failed to connect to wallet"); - } - } finally { - setConnectionLoading(false); - } - }, [ - onSuccessfullConnection, - setConnectionError, - setConnectionLoading, - connectToWalletHandler, - ]); - - const connect = useCallback(() => { - connectHandler().catch((e) => { - throw e; - }); - }, [connectHandler]); - - return ( - <> - - - Wallet - - - - - {connectionLoading ? ( - - - Waiting for wallet approval... - - ) : null} - {connectionError ? ( - - - {connectionError} - - ) : null} - - - - - ); + const toast = useToast(); + const { setAccount } = useWallet(); + const { vendor } = useConnex(); + + const [connectionLoading, setConnectionLoading] = useState(false); + const [connectionError, setConnectionError] = useState(''); + + const connectToWalletHandler = + useCallback(async (): Promise => { + const message: Connex.Vendor.CertMessage = { + purpose: 'identification', + payload: { + type: 'text', + content: 'Sign a certificate to prove your identity' + } + }; + + const certResponse = await vendor.sign('cert', message).request(); + + const cert: Certificate = { + purpose: message.purpose, + payload: message.payload, + domain: certResponse.annex.domain, + timestamp: certResponse.annex.timestamp, + signer: certResponse.annex.signer, + signature: certResponse.signature + }; + + Certificate.verify(cert); + + return cert; + }, [vendor]); + + const onSuccessfullConnection = useCallback( + (cert: Certificate): void => { + setAccount(cert.signer); + onClose(); + toast({ + title: 'Wallet connected.', + description: `You've succesfully connected with wallet ${cert.signer}`, + status: 'success', + position: 'bottom-left', + duration: 5000, + isClosable: true + }); + }, + [toast, setAccount, onClose] + ); + + const connectHandler = useCallback(async () => { + try { + setConnectionError(''); + setConnectionLoading(true); + + const cert = await connectToWalletHandler(); + + onSuccessfullConnection(cert); + } catch (e) { + if (e instanceof Error) { + setConnectionError(e.message); + } else { + setConnectionError('Failed to connect to wallet'); + } + } finally { + setConnectionLoading(false); + } + }, [ + onSuccessfullConnection, + setConnectionError, + setConnectionLoading, + connectToWalletHandler + ]); + + const connect = useCallback(() => { + connectHandler().catch((e) => { + throw e; + }); + }, [connectHandler]); + + return ( + <> + + + Wallet + + + + + {connectionLoading ? ( + + + Waiting for wallet approval... + + ) : null} + {connectionError ? ( + + + {connectionError} + + ) : null} + + + + + ); }; diff --git a/apps/sample-react-app/src/Components/WalletSourceRadio.tsx b/apps/sample-react-app/src/Components/WalletSourceRadio.tsx index 3091c5e6..54de45de 100644 --- a/apps/sample-react-app/src/Components/WalletSourceRadio.tsx +++ b/apps/sample-react-app/src/Components/WalletSourceRadio.tsx @@ -1,107 +1,111 @@ import { - Box, - Flex, - HStack, - Icon, - Image, - Text, - Tooltip, - VStack, -} from "@chakra-ui/react"; -import { ExclamationTriangleIcon } from "@heroicons/react/24/solid"; -import React, { useCallback } from "react"; -import type { WalletSource } from "react-vendor"; -import { useWallet } from "react-vendor"; -import { WalletSources } from "../Constants"; -import { RadioCard } from "./shared"; + Box, + Flex, + HStack, + Icon, + Image, + Text, + Tooltip, + VStack +} from '@chakra-ui/react'; +import { ExclamationTriangleIcon } from '@heroicons/react/24/solid'; +import React, { useCallback } from 'react'; +import type { WalletSource } from '@vechain/wallet-kit'; +import { useWallet } from '@vechain/react-wallet-kit'; +import { WalletSources } from '../Constants'; +import { RadioCard } from './shared'; export const WalletSourceRadio: React.FC = () => { - const { availableWallets, wallets, setSource, accountState } = useWallet(); + const { availableWallets, wallets, setSource, accountState } = useWallet(); - const handleSourceClick = useCallback( - (isDisabled: boolean, source: WalletSource) => () => { - if (!isDisabled) { - setSource(source); - } - }, - [setSource] - ); + const handleSourceClick = useCallback( + (isDisabled: boolean, source: WalletSource) => () => { + if (!isDisabled) { + setSource(source); + } + }, + [setSource] + ); - return ( - - {wallets.map((source: WalletSource) => { - const isDisabled = !availableWallets.includes(source); - const isSelected = source === accountState.source; + return ( + + {wallets.map((source: WalletSource) => { + const isDisabled = !availableWallets.includes(source); + const isSelected = source === accountState.source; - return ( - - ); - })} - - ); + return ( + + ); + })} + + ); }; interface WalletSourceButtonProps { - source: WalletSource; - isSelected: boolean; - isDisabled: boolean; - onClick: () => void; + source: WalletSource; + isSelected: boolean; + isDisabled: boolean; + onClick: () => void; } const WalletSourceButton: React.FC = ({ - source, - isSelected, - isDisabled, - onClick, + source, + isSelected, + isDisabled, + onClick }) => { - const sourceInfo = WalletSources[source]; - return ( - - - {`${sourceInfo.name}-logo`} - {sourceInfo.name} - - {isDisabled ? ( - - - - ) : null} - - ); + const sourceInfo = WalletSources[source]; + return ( + + + {`${sourceInfo.name}-logo`} + {sourceInfo.name} + + {isDisabled ? ( + + + + ) : null} + + ); }; interface SourceNotDetectedIconProps { - source: WalletSource; + source: WalletSource; } const SourceNotDetectedIcon: React.FC = ({ - source, + source }) => { - const sourceInfo = WalletSources[source]; + const sourceInfo = WalletSources[source]; - return ( - - - - - - ); + return ( + + + + + + ); }; diff --git a/apps/sample-react-app/src/Components/index.ts b/apps/sample-react-app/src/Components/index.ts index 0d3c1163..2c393926 100644 --- a/apps/sample-react-app/src/Components/index.ts +++ b/apps/sample-react-app/src/Components/index.ts @@ -1,7 +1,7 @@ -export * from "./AccountDetailBody"; -export * from "./AccountDetailModal"; -export * from "./AddressButton"; -export * from "./AddressIcon"; -export * from "./ConnectWalletButton"; -export * from "./ConnectWalletModal"; -export * from "./WalletSourceRadio"; +export * from './AccountDetailBody'; +export * from './AccountDetailModal'; +export * from './AddressButton'; +export * from './AddressIcon'; +export * from './ConnectWalletButton'; +export * from './ConnectWalletModal'; +export * from './WalletSourceRadio'; diff --git a/apps/sample-react-app/src/Components/layout/NavBar.tsx b/apps/sample-react-app/src/Components/layout/NavBar.tsx index ca1c9497..678e15cb 100644 --- a/apps/sample-react-app/src/Components/layout/NavBar.tsx +++ b/apps/sample-react-app/src/Components/layout/NavBar.tsx @@ -1,120 +1,120 @@ import { - Box, - Drawer, - DrawerBody, - DrawerContent, - DrawerOverlay, - Flex, - HStack, - Icon, - IconButton, - Text, - useColorModeValue, - useDisclosure, - useMediaQuery, - VStack, -} from "@chakra-ui/react"; -import type { JSX } from "react"; -import React from "react"; -import { Bars3Icon } from "@heroicons/react/24/solid"; -import { useWallet } from "react-vendor"; -import { VechainLogo } from "../../Logos"; -import { AccountDetailBody } from "../AccountDetailBody"; -import { ConnectWalletButton } from "../ConnectWalletButton"; + Box, + Drawer, + DrawerBody, + DrawerContent, + DrawerOverlay, + Flex, + HStack, + Icon, + IconButton, + Text, + useColorModeValue, + useDisclosure, + useMediaQuery, + VStack +} from '@chakra-ui/react'; +import type { JSX } from 'react'; +import React from 'react'; +import { Bars3Icon } from '@heroicons/react/24/solid'; +import { useWallet } from '@vechain/react-wallet-kit'; +import { VechainLogo } from '../../Logos'; +import { AccountDetailBody } from '../AccountDetailBody'; +import { ConnectWalletButton } from '../ConnectWalletButton'; export const NavBar = (): JSX.Element => { - const bg = useColorModeValue("gray.50", "gray.900"); - const [isDesktop] = useMediaQuery("(min-width: 48em)"); + const bg = useColorModeValue('gray.50', 'gray.900'); + const [isDesktop] = useMediaQuery('(min-width: 48em)'); - return ( - - {isDesktop ? : } - - ); + return ( + + {isDesktop ? : } + + ); }; const DesktopNavBar = (): JSX.Element => { - return ( - <> - - - - - - ); + return ( + <> + + + + + + ); }; const MobileNavBar = (): JSX.Element => { - const { isOpen, onClose, onOpen } = useDisclosure(); - return ( - <> - - - - - } - onClick={onOpen} - /> - - ); + const { isOpen, onClose, onOpen } = useDisclosure(); + return ( + <> + + + + + } + onClick={onOpen} + /> + + ); }; interface MobileNavBarDrawerProps { - isOpen: boolean; - onClose: () => void; + isOpen: boolean; + onClose: () => void; } const MobileNavBarDrawer = ({ - isOpen, - onClose, + isOpen, + onClose }: MobileNavBarDrawerProps): JSX.Element => { - const { accountState, disconnect } = useWallet(); + const { accountState, disconnect } = useWallet(); - return ( - - - - - - - Connected Wallet - {accountState.address && accountState.source ? ( - - ) : ( - - )} - - - - - - ); + return ( + + + + + + + Connected Wallet + {accountState.address && accountState.source ? ( + + ) : ( + + )} + + + + + + ); }; const NavBarWalletConnect = (): JSX.Element => { - return ( - - - - ); + return ( + + + + ); }; diff --git a/apps/sample-react-app/src/Components/layout/StyledContainer.tsx b/apps/sample-react-app/src/Components/layout/StyledContainer.tsx index f1f8214a..f0f98395 100644 --- a/apps/sample-react-app/src/Components/layout/StyledContainer.tsx +++ b/apps/sample-react-app/src/Components/layout/StyledContainer.tsx @@ -1,20 +1,20 @@ -import { Box, Container, useColorModeValue } from "@chakra-ui/react"; -import React from "react"; +import { Box, Container, useColorModeValue } from '@chakra-ui/react'; +import React from 'react'; interface StyledContainerProps { - children: React.ReactNode; + children: React.ReactNode; } export const StyledContainer: React.FC = ({ - children, + children }) => { - const bodyBg = useColorModeValue("gray.50", "gray.900"); + const bodyBg = useColorModeValue('gray.50', 'gray.900'); - return ( - - - {children} - - - ); + return ( + + + {children} + + + ); }; diff --git a/apps/sample-react-app/src/Components/layout/index.ts b/apps/sample-react-app/src/Components/layout/index.ts index 65a8d02b..65237cdf 100644 --- a/apps/sample-react-app/src/Components/layout/index.ts +++ b/apps/sample-react-app/src/Components/layout/index.ts @@ -1,2 +1,2 @@ -export * from "./NavBar"; -export * from "./StyledContainer"; +export * from './NavBar'; +export * from './StyledContainer'; diff --git a/apps/sample-react-app/src/Components/shared/Dialog.tsx b/apps/sample-react-app/src/Components/shared/Dialog.tsx index 7945d113..3301dcde 100644 --- a/apps/sample-react-app/src/Components/shared/Dialog.tsx +++ b/apps/sample-react-app/src/Components/shared/Dialog.tsx @@ -1,45 +1,49 @@ -import type { HTMLChakraProps } from "@chakra-ui/react"; +import type { HTMLChakraProps } from '@chakra-ui/react'; import { - Modal, - ModalBody, - ModalCloseButton, - ModalContent, - ModalFooter, - ModalHeader, - ModalOverlay, -} from "@chakra-ui/react"; -import React from "react"; + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay +} from '@chakra-ui/react'; +import React from 'react'; interface DialogProps { - isOpen: boolean; - onClose: () => void; - header?: React.ReactNode; - headerStyle?: HTMLChakraProps<"header">; - body?: React.ReactNode; - footer?: React.ReactNode; - showCloseButton?: boolean; - closeButtonStyle?: HTMLChakraProps<"button">; + isOpen: boolean; + onClose: () => void; + header?: React.ReactNode; + headerStyle?: HTMLChakraProps<'header'>; + body?: React.ReactNode; + footer?: React.ReactNode; + showCloseButton?: boolean; + closeButtonStyle?: HTMLChakraProps<'button'>; } export const Dialog: React.FC = ({ - isOpen, - onClose, - header, - headerStyle = {}, - body, - footer, - showCloseButton = true, - closeButtonStyle = {}, + isOpen, + onClose, + header, + headerStyle = {}, + body, + footer, + showCloseButton = true, + closeButtonStyle = {} }) => { - return ( - - - - {header ? {header} : null} - {showCloseButton ? : null} - {body ? {body} : null} - {footer ? {footer} : null} - - - ); + return ( + + + + {header ? ( + {header} + ) : null} + {showCloseButton ? ( + + ) : null} + {body ? {body} : null} + {footer ? {footer} : null} + + + ); }; diff --git a/apps/sample-react-app/src/Components/shared/RadioCard.tsx b/apps/sample-react-app/src/Components/shared/RadioCard.tsx index a9427998..b0012f46 100644 --- a/apps/sample-react-app/src/Components/shared/RadioCard.tsx +++ b/apps/sample-react-app/src/Components/shared/RadioCard.tsx @@ -1,61 +1,66 @@ -import type { HTMLChakraProps } from "@chakra-ui/react"; -import { Box, Button, Flex, HStack } from "@chakra-ui/react"; -import React from "react"; +import type { HTMLChakraProps } from '@chakra-ui/react'; +import { Box, Button, Flex, HStack } from '@chakra-ui/react'; +import React from 'react'; -interface RadioCardProps extends HTMLChakraProps<"button"> { - children: React.ReactNode; - selected: boolean; - onClick: () => void; +interface RadioCardProps extends HTMLChakraProps<'button'> { + children: React.ReactNode; + selected: boolean; + onClick: () => void; } export const RadioCard: React.FC = ({ - children, - selected, - onClick, - ...props + children, + selected, + onClick, + ...props }) => { - return ( - - ); + return ( + + ); }; -interface RadioCircleProps extends HTMLChakraProps<"div"> { - filled?: boolean; +interface RadioCircleProps extends HTMLChakraProps<'div'> { + filled?: boolean; } const RadioCircle: React.FC = ({ - filled = false, - ...props + filled = false, + ...props }) => { - return ( - - - - ); + return ( + + + + ); }; diff --git a/apps/sample-react-app/src/Components/shared/StyledCard.tsx b/apps/sample-react-app/src/Components/shared/StyledCard.tsx index b5b0786b..f94282ef 100644 --- a/apps/sample-react-app/src/Components/shared/StyledCard.tsx +++ b/apps/sample-react-app/src/Components/shared/StyledCard.tsx @@ -1,19 +1,19 @@ -import type { HTMLChakraProps } from "@chakra-ui/react"; -import { Card, CardBody, useColorModeValue } from "@chakra-ui/react"; -import React from "react"; +import type { HTMLChakraProps } from '@chakra-ui/react'; +import { Card, CardBody, useColorModeValue } from '@chakra-ui/react'; +import React from 'react'; -interface StyledCardProps extends HTMLChakraProps<"div"> { - children: React.ReactNode; +interface StyledCardProps extends HTMLChakraProps<'div'> { + children: React.ReactNode; } export const StyledCard: React.FC = ({ - children, - ...props + children, + ...props }) => { - const cardBg = useColorModeValue("white", "gray.700"); - return ( - - {children} - - ); + const cardBg = useColorModeValue('white', 'gray.700'); + return ( + + {children} + + ); }; diff --git a/apps/sample-react-app/src/Components/shared/index.ts b/apps/sample-react-app/src/Components/shared/index.ts index 3991c962..f623ae48 100644 --- a/apps/sample-react-app/src/Components/shared/index.ts +++ b/apps/sample-react-app/src/Components/shared/index.ts @@ -1,3 +1,3 @@ -export * from "./Dialog"; -export * from "./RadioCard"; -export * from "./StyledCard"; +export * from './Dialog'; +export * from './RadioCard'; +export * from './StyledCard'; diff --git a/apps/sample-react-app/src/Constants/Constants.ts b/apps/sample-react-app/src/Constants/Constants.ts index 0ed62d94..19cb0bed 100644 --- a/apps/sample-react-app/src/Constants/Constants.ts +++ b/apps/sample-react-app/src/Constants/Constants.ts @@ -1,30 +1,30 @@ -import { WalletSource } from "react-vendor"; +import { WalletSource } from '@vechain/wallet-kit'; -export const NODE_URL = "https://testnet.vechain.org/"; -export const NODE_NETWORK = "test"; +export const NODE_URL = 'https://testnet.vechain.org/'; +export const NODE_NETWORK = 'test'; interface SourceInfo { - name: string; - logo: string; + name: string; + logo: string; } const baseLogoUrl = `${process.env.PUBLIC_URL}/images/logo`; export const WalletSources: Record = { - [WalletSource.WalletConnect]: { - name: "Wallet Connect", - logo: `${baseLogoUrl}/wallet-connect-logo.png`, - }, - [WalletSource.VeWorldExtension]: { - name: "VeWorld Extension", - logo: `${baseLogoUrl}/veworld_black.png`, - }, - [WalletSource.Sync]: { - name: "Sync", - logo: `${baseLogoUrl}/sync.png`, - }, - [WalletSource.Sync2]: { - name: "Sync 2", - logo: `${baseLogoUrl}/sync2.png`, - }, + [WalletSource.WalletConnect]: { + name: 'Wallet Connect', + logo: `${baseLogoUrl}/wallet-connect-logo.png` + }, + [WalletSource.VeWorldExtension]: { + name: 'VeWorld Extension', + logo: `${baseLogoUrl}/veworld_black.png` + }, + [WalletSource.Sync]: { + name: 'Sync', + logo: `${baseLogoUrl}/sync.png` + }, + [WalletSource.Sync2]: { + name: 'Sync 2', + logo: `${baseLogoUrl}/sync2.png` + } }; diff --git a/apps/sample-react-app/src/Constants/index.ts b/apps/sample-react-app/src/Constants/index.ts index 86ad2cf4..a719bffa 100644 --- a/apps/sample-react-app/src/Constants/index.ts +++ b/apps/sample-react-app/src/Constants/index.ts @@ -1 +1 @@ -export * from "./Constants"; +export * from './Constants'; diff --git a/apps/sample-react-app/src/Hooks/useCounter.ts b/apps/sample-react-app/src/Hooks/useCounter.ts index 0e4d6bf5..7dacfd80 100644 --- a/apps/sample-react-app/src/Hooks/useCounter.ts +++ b/apps/sample-react-app/src/Hooks/useCounter.ts @@ -1,77 +1,77 @@ -import { useCallback, useEffect, useMemo, useState } from "react"; -import { useConnex } from "react-vendor"; -import type { abi } from "thor-devkit"; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useConnex } from '@vechain/react-wallet-kit'; +import type { abi } from 'thor-devkit'; const _counter: abi.Function.Definition = { - inputs: [], - name: "counter", - outputs: [ - { - internalType: "uint256", - name: "count", - type: "uint256", - }, - ], - stateMutability: "view", - type: "function", + inputs: [], + name: 'counter', + outputs: [ + { + internalType: 'uint256', + name: 'count', + type: 'uint256' + } + ], + stateMutability: 'view', + type: 'function' }; const _increment: abi.Function.Definition = { - inputs: [], - name: "increment", - outputs: [], - stateMutability: "nonpayable", - type: "function", + inputs: [], + name: 'increment', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' }; -type IncrementStatus = "idle" | "in-wallet" | "pending" | "error"; +type IncrementStatus = 'idle' | 'in-wallet' | 'pending' | 'error'; interface UseCounter { - count: number; - increment: () => Promise; - status: IncrementStatus; - address: string; + count: number; + increment: () => Promise; + status: IncrementStatus; + address: string; } export const useCounter = (): UseCounter => { - const { vendor, thor } = useConnex(); + const { vendor, thor } = useConnex(); - const [count, setCount] = useState(0); - const [status, setStatus] = useState("idle"); + const [count, setCount] = useState(0); + const [status, setStatus] = useState('idle'); - const contract = useMemo( - () => thor.account("0x8384738c995d49c5b692560ae688fc8b51af1059"), - [thor] - ); + const contract = useMemo( + () => thor.account('0x8384738c995d49c5b692560ae688fc8b51af1059'), + [thor] + ); - const setValue = useCallback(async () => { - const res = await contract.method(_counter).call(); + const setValue = useCallback(async () => { + const res = await contract.method(_counter).call(); - setCount(res.decoded.count as number); - }, [contract]); + setCount(res.decoded.count as number); + }, [contract]); - useEffect(() => { - setValue().catch(() => setStatus("error")); - }, [setValue]); + useEffect(() => { + setValue().catch(() => setStatus('error')); + }, [setValue]); - const increment = useCallback(async (): Promise => { - const clause = contract.method(_increment).asClause(); + const increment = useCallback(async (): Promise => { + const clause = contract.method(_increment).asClause(); - setStatus("in-wallet"); + setStatus('in-wallet'); - await vendor - .sign("tx", [clause]) - .delegate("https://sponsor-testnet.vechain.energy/by/90") - .request(); + await vendor + .sign('tx', [clause]) + .delegate('https://sponsor-testnet.vechain.energy/by/90') + .request(); - setStatus("pending"); + setStatus('pending'); - await thor.ticker().next(); + await thor.ticker().next(); - await setValue() - .then(() => setStatus("idle")) - .catch(() => setStatus("error")); - }, [thor, vendor, contract, setValue]); + await setValue() + .then(() => setStatus('idle')) + .catch(() => setStatus('error')); + }, [thor, vendor, contract, setValue]); - return { count, increment, status, address: contract.address }; + return { count, increment, status, address: contract.address }; }; diff --git a/apps/sample-react-app/src/Logos/Logo.tsx b/apps/sample-react-app/src/Logos/Logo.tsx index f821f81b..7e15008d 100644 --- a/apps/sample-react-app/src/Logos/Logo.tsx +++ b/apps/sample-react-app/src/Logos/Logo.tsx @@ -1,37 +1,37 @@ -import type { HTMLChakraProps } from "@chakra-ui/react"; -import { Image, useColorModeValue } from "@chakra-ui/react"; -import React from "react"; +import type { HTMLChakraProps } from '@chakra-ui/react'; +import { Image, useColorModeValue } from '@chakra-ui/react'; +import React from 'react'; -type IIMage = HTMLChakraProps<"img">; +type IIMage = HTMLChakraProps<'img'>; export const VechainLogo: React.FC = ({ - ...props + ...props }): React.ReactElement => { - const lightModeUrl = `${process.env.PUBLIC_URL}/images/logo/vechain.png`; - const darkModeUrl = `${process.env.PUBLIC_URL}/images/logo/vechain_white.png`; - const logoUrl = useColorModeValue(lightModeUrl, darkModeUrl); - return ( - Vechain logo - ); + const lightModeUrl = `${process.env.PUBLIC_URL}/images/logo/vechain.png`; + const darkModeUrl = `${process.env.PUBLIC_URL}/images/logo/vechain_white.png`; + const logoUrl = useColorModeValue(lightModeUrl, darkModeUrl); + return ( + Vechain logo + ); }; export const VeWorldLogo: React.FC = ({ ...props }) => { - const lightModeUrl = `${process.env.PUBLIC_URL}/images/logo/veWorld.png`; - const darkModeUrl = `${process.env.PUBLIC_URL}/images/logo/veworld_black.png`; - const logoUrl = useColorModeValue(lightModeUrl, darkModeUrl); - return ( - VeWorld logo - ); + const lightModeUrl = `${process.env.PUBLIC_URL}/images/logo/veWorld.png`; + const darkModeUrl = `${process.env.PUBLIC_URL}/images/logo/veworld_black.png`; + const logoUrl = useColorModeValue(lightModeUrl, darkModeUrl); + return ( + VeWorld logo + ); }; diff --git a/apps/sample-react-app/src/Logos/index.ts b/apps/sample-react-app/src/Logos/index.ts index bd3113fb..d97c6951 100644 --- a/apps/sample-react-app/src/Logos/index.ts +++ b/apps/sample-react-app/src/Logos/index.ts @@ -1 +1 @@ -export * from "./Logo"; +export * from './Logo'; diff --git a/apps/sample-react-app/src/Screens/Homepage.tsx b/apps/sample-react-app/src/Screens/Homepage.tsx index 23269a97..f2ad2069 100644 --- a/apps/sample-react-app/src/Screens/Homepage.tsx +++ b/apps/sample-react-app/src/Screens/Homepage.tsx @@ -1,28 +1,28 @@ -import React from "react"; -import { Box, Grid, GridItem, VStack } from "@chakra-ui/react"; -import { Counter, MeetVeWorld, Welcome } from "./components"; +import React from 'react'; +import { Box, Grid, GridItem, VStack } from '@chakra-ui/react'; +import { Counter, MeetVeWorld, Welcome } from './components'; export const Homepage: React.FC = () => { - return ( - - - - - - - - - - - - - - ); + return ( + + + + + + + + + + + + + + ); }; diff --git a/apps/sample-react-app/src/Screens/components/Counter.tsx b/apps/sample-react-app/src/Screens/components/Counter.tsx index 3d91206b..19c3d0a5 100644 --- a/apps/sample-react-app/src/Screens/components/Counter.tsx +++ b/apps/sample-react-app/src/Screens/components/Counter.tsx @@ -1,100 +1,105 @@ import { - Alert, - AlertIcon, - Button, - Heading, - HStack, - Icon, - Text, - useToast, - VStack, -} from "@chakra-ui/react"; -import type { JSX } from "react"; -import React, { useCallback, useMemo } from "react"; -import { ArrowUpIcon } from "@heroicons/react/24/solid"; -import { useWallet } from "react-vendor"; -import { StyledCard } from "../../Components/shared"; -import { useCounter } from "../../Hooks/useCounter"; -import { AddressButton } from "../../Components"; + Alert, + AlertIcon, + Button, + Heading, + HStack, + Icon, + Text, + useToast, + VStack +} from '@chakra-ui/react'; +import type { JSX } from 'react'; +import React, { useCallback, useMemo } from 'react'; +import { ArrowUpIcon } from '@heroicons/react/24/solid'; +import { useWallet } from '@vechain/react-wallet-kit'; +import { StyledCard } from '../../Components/shared'; +import { useCounter } from '../../Hooks/useCounter'; +import { AddressButton } from '../../Components'; export const Counter = (): JSX.Element => { - const { accountState } = useWallet(); - const { count, increment, status, address } = useCounter(); - const toast = useToast(); + const { accountState } = useWallet(); + const { count, increment, status, address } = useCounter(); + const toast = useToast(); - const incrementCounter = useCallback(() => { - increment() - .then(() => { - toast({ - title: "Counter incremented", - status: "success", - duration: 2000, - isClosable: true, - }); - }) - // eslint-disable-next-line no-console - .catch(console.error); - }, [increment, toast]); + const incrementCounter = useCallback(() => { + increment() + .then(() => { + toast({ + title: 'Counter incremented', + status: 'success', + duration: 2000, + isClosable: true + }); + }) + // eslint-disable-next-line no-console + .catch(console.error); + }, [increment, toast]); - const button = useMemo(() => { - return ( - - ); - }, [accountState.address, incrementCounter, status]); - - const message = useMemo(() => { - switch (status) { - case "in-wallet": - return ( - - - Waiting for wallet approval... - - ); - case "error": + const button = useMemo(() => { return ( - - - There was an unexpected error - + ); - case "pending": - return ( - - - Waiting for transaction confirmation... - - ); - case "idle": - return null; - } - }, [status]); + }, [accountState.address, incrementCounter, status]); + + const message = useMemo(() => { + switch (status) { + case 'in-wallet': + return ( + + + Waiting for wallet approval... + + ); + case 'error': + return ( + + + There was an unexpected error + + ); + case 'pending': + return ( + + + Waiting for transaction confirmation... + + ); + case 'idle': + return null; + } + }, [status]); - return ( - - - Counter + return ( + + + Counter - + - - Current count: {count} - - - {button} - {message} - - - - ); + + Current count: {count} + + + {button} + {message} + + + + ); }; diff --git a/apps/sample-react-app/src/Screens/components/MeetVeWorld.tsx b/apps/sample-react-app/src/Screens/components/MeetVeWorld.tsx index be9423a8..b753b16c 100644 --- a/apps/sample-react-app/src/Screens/components/MeetVeWorld.tsx +++ b/apps/sample-react-app/src/Screens/components/MeetVeWorld.tsx @@ -1,27 +1,27 @@ -import { Box, Button, Heading, HStack, Text, VStack } from "@chakra-ui/react"; -import type { JSX } from "react"; -import { VeWorldLogo } from "../../Logos"; -import { StyledCard } from "../../Components/shared"; +import { Box, Button, Heading, HStack, Text, VStack } from '@chakra-ui/react'; +import type { JSX } from 'react'; +import { VeWorldLogo } from '../../Logos'; +import { StyledCard } from '../../Components/shared'; export const MeetVeWorld = (): JSX.Element => { - return ( - - - - Meet VeWorld{" "} - - - - - - The new browser-based wallet coming straightly from the Vechain - Foundation. Built with state of the art technologies, security and - ease of use in mind. - - - - - ); + return ( + + + + Meet VeWorld{' '} + + + + + + The new browser-based wallet coming straightly from the + Vechain Foundation. Built with state of the art + technologies, security and ease of use in mind. + + + + + ); }; diff --git a/apps/sample-react-app/src/Screens/components/Welcome.tsx b/apps/sample-react-app/src/Screens/components/Welcome.tsx index 00cccd79..bfd1e88c 100644 --- a/apps/sample-react-app/src/Screens/components/Welcome.tsx +++ b/apps/sample-react-app/src/Screens/components/Welcome.tsx @@ -1,30 +1,35 @@ -import { Button, Heading, HStack, Link, Text, VStack } from "@chakra-ui/react"; -import type { JSX } from "react"; -import { StyledCard } from "../../Components/shared"; -import { ConnectWalletButton } from "../../Components"; +import { Button, Heading, HStack, Link, Text, VStack } from '@chakra-ui/react'; +import type { JSX } from 'react'; +import { StyledCard } from '../../Components/shared'; +import { ConnectWalletButton } from '../../Components'; export const Welcome = (): JSX.Element => { - return ( - - - Welcome to the Official VeWorld Demo Dapp - - You can use this dapp to familiarize and know more about creation on - VeChain - - - - - - - - - - ); + return ( + + + Welcome to the Official VeWorld Demo Dapp + + You can use this dapp to familiarize and know more about + creation on VeChain + + + + + + + + + + ); }; diff --git a/apps/sample-react-app/src/Screens/components/index.ts b/apps/sample-react-app/src/Screens/components/index.ts index 1a0266bb..8882b673 100644 --- a/apps/sample-react-app/src/Screens/components/index.ts +++ b/apps/sample-react-app/src/Screens/components/index.ts @@ -1,3 +1,3 @@ -export * from "./MeetVeWorld"; -export * from "./Welcome"; -export * from "./Counter"; +export * from './MeetVeWorld'; +export * from './Welcome'; +export * from './Counter'; diff --git a/apps/sample-react-app/src/Styles/Fonts.tsx b/apps/sample-react-app/src/Styles/Fonts.tsx index 64885238..7f830cf8 100644 --- a/apps/sample-react-app/src/Styles/Fonts.tsx +++ b/apps/sample-react-app/src/Styles/Fonts.tsx @@ -1,9 +1,9 @@ -import { Global } from "@emotion/react"; -import React from "react"; +import { Global } from '@emotion/react'; +import React from 'react'; export const Fonts = (): React.ReactElement => ( - ( font-style: normal; } `} - /> + /> ); diff --git a/apps/sample-react-app/src/Styles/Theme.tsx b/apps/sample-react-app/src/Styles/Theme.tsx index 3cc10ad2..caeced90 100644 --- a/apps/sample-react-app/src/Styles/Theme.tsx +++ b/apps/sample-react-app/src/Styles/Theme.tsx @@ -1,8 +1,8 @@ -import { extendTheme } from "@chakra-ui/react"; +import { extendTheme } from '@chakra-ui/react'; export const theme = extendTheme({ - fonts: { - heading: `'JetBrains Mono', sans-serif`, - body: `'Inter', sans-serif`, - }, + fonts: { + heading: `'JetBrains Mono', sans-serif`, + body: `'Inter', sans-serif` + } }); diff --git a/apps/sample-react-app/src/Styles/index.ts b/apps/sample-react-app/src/Styles/index.ts index 99c5763a..6e81efc6 100644 --- a/apps/sample-react-app/src/Styles/index.ts +++ b/apps/sample-react-app/src/Styles/index.ts @@ -1,2 +1,2 @@ -export * from "./Fonts"; -export * from "./Theme"; +export * from './Fonts'; +export * from './Theme'; diff --git a/apps/sample-react-app/src/Utils/AccountUtils.ts b/apps/sample-react-app/src/Utils/AccountUtils.ts index 76e235b5..0526249f 100644 --- a/apps/sample-react-app/src/Utils/AccountUtils.ts +++ b/apps/sample-react-app/src/Utils/AccountUtils.ts @@ -1,20 +1,20 @@ -import { picasso } from "@vechain/picasso"; +import { picasso } from '@vechain/picasso'; export const getPicassoImgSrc = (address: string, base64 = false): string => { - const image = picasso(address.toLowerCase()); - if (base64) { - const base64data = Buffer.from(image, "utf8").toString("base64"); - return `data:image/svg+xml;base64,${base64data}`; - } - return `data:image/svg+xml;utf8,${image}`; + const image = picasso(address.toLowerCase()); + if (base64) { + const base64data = Buffer.from(image, 'utf8').toString('base64'); + return `data:image/svg+xml;base64,${base64data}`; + } + return `data:image/svg+xml;utf8,${image}`; }; export const friendlyAddress = ( - address: string, - lengthBefore = 6, - lengthAfter = 4 + address: string, + lengthBefore = 6, + lengthAfter = 4 ): string => { - const before = address.substring(0, lengthBefore); - const after = address.substring(address.length - lengthAfter); - return `${before}…${after}`; + const before = address.substring(0, lengthBefore); + const after = address.substring(address.length - lengthAfter); + return `${before}…${after}`; }; diff --git a/apps/sample-react-app/src/index.tsx b/apps/sample-react-app/src/index.tsx index afcbbaa4..4c3a1658 100644 --- a/apps/sample-react-app/src/index.tsx +++ b/apps/sample-react-app/src/index.tsx @@ -1,18 +1,20 @@ -import React from "react"; -import { createRoot } from "react-dom/client"; -import { App } from "./App"; -import reportWebVitals from "./reportWebVitals"; -import "./Styles/index.css"; +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import { App } from './App'; +import reportWebVitals from './reportWebVitals'; +import './Styles/index.css'; -const rootElement = document.getElementById("root"); +document.createElement('vechain-wallet-modal'); + +const rootElement = document.getElementById('root'); if (rootElement) { - const root = createRoot(rootElement); - root.render( - - - - ); + const root = createRoot(rootElement); + root.render( + + + + ); } // If you want to start measuring performance in your app, pass a function diff --git a/apps/sample-react-app/src/reportWebVitals.ts b/apps/sample-react-app/src/reportWebVitals.ts index 9f98d7d9..fae17894 100644 --- a/apps/sample-react-app/src/reportWebVitals.ts +++ b/apps/sample-react-app/src/reportWebVitals.ts @@ -1,20 +1,20 @@ -import type { ReportHandler } from "web-vitals"; +import type { ReportHandler } from 'web-vitals'; const reportWebVitals = (onPerfEntry?: ReportHandler): void => { - if (onPerfEntry && onPerfEntry instanceof Function) { - import("web-vitals") - .then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { - getCLS(onPerfEntry); - getFID(onPerfEntry); - getFCP(onPerfEntry); - getLCP(onPerfEntry); - getTTFB(onPerfEntry); - }) - .catch((err) => { - // eslint-disable-next-line no-console - console.error('Failed to load "web-vitals"', err); - }); - } + if (onPerfEntry && onPerfEntry instanceof Function) { + import('web-vitals') + .then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + getCLS(onPerfEntry); + getFID(onPerfEntry); + getFCP(onPerfEntry); + getLCP(onPerfEntry); + getTTFB(onPerfEntry); + }) + .catch((err) => { + // eslint-disable-next-line no-console + console.error('Failed to load "web-vitals"', err); + }); + } }; export default reportWebVitals; diff --git a/apps/sample-react-app/src/setupTests.ts b/apps/sample-react-app/src/setupTests.ts index 1dd407a6..8f2609b7 100644 --- a/apps/sample-react-app/src/setupTests.ts +++ b/apps/sample-react-app/src/setupTests.ts @@ -2,4 +2,4 @@ // allows you to do things like: // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom -import "@testing-library/jest-dom"; +import '@testing-library/jest-dom'; diff --git a/package.json b/package.json index 0f1ff6fa..848a0e68 100755 --- a/package.json +++ b/package.json @@ -1,59 +1,46 @@ { - "private": true, - "workspaces": [ - "apps/*", - "packages/*" - ], - "scripts": { - "build": "turbo run build", - "build:deps": "turbo build --no-daemon --filter=react-vendor --filter=wallet-connect", - "clean": "npx turbo@latest run clean && rm -rf node_modules .turbo", - "dev": "turbo run dev --no-daemon", - "format": "prettier --write \"**/*.{ts,tsx,md}\"", - "install:all": "yarn && yarn run build:deps", - "lint": "turbo run lint", - "prepare": "husky install", - "reinstall": "yarn clean && yarn && yarn run build:deps", - "test": "turbo run test" - }, - "husky": { - "hooks": { - "pre-commit": "lint-staged" - } - }, - "lint-staged": { - "*.{js,jsx,ts,tsx,json,md}": [ - "prettier --write", - "git add" + "private": true, + "workspaces": [ + "apps/*", + "packages/*" ], - "*.{js,jsx,ts,tsx}": [ - "eslint" - ] - }, - "devDependencies": { - "@commitlint/config-conventional": "^18.0.0", - "@nomicfoundation/hardhat-chai-matchers": "^2.0.2", - "@nomicfoundation/hardhat-ethers": "^3.0.4", - "@nomicfoundation/hardhat-network-helpers": "^1.0.9", - "@nomicfoundation/hardhat-verify": "^1.1.1", - "@typechain/ethers-v6": "^0.4.3", - "@typechain/hardhat": "^8.0.3", - "@types/chai": "^4.3.9", - "@types/mocha": "^10.0.3", - "chai": "^4.3.10", - "commitlint": "^18.0.0", - "eslint": "^8.4.1", - "eslint-config-custom": "*", - "eslint-plugin-prefer-arrow": "1.2.3", - "ethers": "^6.8.0", - "hardhat-gas-reporter": "^1.0.9", - "husky": "^8.0.0", - "lint-staged": "^15.0.2", - "prettier": "^2.5.1", - "solidity-coverage": "^0.8.5", - "tsconfig": "*", - "turbo": "latest", - "typechain": "^8.3.2" - }, - "packageManager": "yarn@1.22.19" + "scripts": { + "build": "turbo run build", + "build:deps": "turbo build --no-daemon --filter='@vechain/*'", + "clean": "npx turbo@latest run clean && rm -rf node_modules .turbo", + "dev": "turbo run dev --no-daemon", + "format": "prettier --write \"**/*.{ts,tsx,md}\"", + "install:all": "yarn && yarn run build:deps", + "lint": "turbo run lint", + "prepare": "husky install", + "reinstall": "yarn clean && yarn && yarn run build:deps", + "test": "turbo run test" + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "*.{js,jsx,ts,tsx,json,md}": [ + "prettier --write", + "git add" + ], + "*.{js,jsx,ts,tsx}": [ + "eslint" + ] + }, + "devDependencies": { + "@commitlint/config-conventional": "^18.0.0", + "commitlint": "^18.0.0", + "eslint": "^8.4.1", + "eslint-config-custom": "*", + "eslint-plugin-prefer-arrow": "1.2.3", + "husky": "^8.0.0", + "lint-staged": "^15.0.2", + "prettier": "^2.5.1", + "tsconfig": "*", + "turbo": "latest" + }, + "packageManager": "yarn@1.22.19" } diff --git a/packages/eslint-config-custom/react.js b/packages/eslint-config-custom/react.js index b1fd2ee7..b8dfb5b2 100644 --- a/packages/eslint-config-custom/react.js +++ b/packages/eslint-config-custom/react.js @@ -20,6 +20,7 @@ module.exports = { parserOptions: { project, }, + parser: "@typescript-eslint/parser", globals: { JSX: true, }, diff --git a/packages/react-vendor/src/AccountReducer.ts b/packages/react-vendor/src/AccountReducer.ts deleted file mode 100644 index 4190931f..00000000 --- a/packages/react-vendor/src/AccountReducer.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { AccountState, WalletSource } from "./types"; - -const ACCOUNT_KEY = "sample-app@account"; -const WALLET_SOURCE_KEY = "sample-app@wallet-source"; - -export type AccountAction = - | { type: "set-address"; payload: { address: string; persist: boolean } } - | { - type: "set-wallet-source"; - payload: { source: WalletSource; persist: boolean }; - } - | { type: "clear" }; - -export const defaultAccountState: AccountState = { - address: localStorage.getItem(ACCOUNT_KEY), - source: localStorage.getItem(WALLET_SOURCE_KEY) as WalletSource, -}; - -export const accountReducer = ( - state: AccountState, - action: AccountAction -): AccountState => { - switch (action.type) { - case "set-address": { - const { address, persist } = action.payload; - if (persist) { - localStorage.setItem(ACCOUNT_KEY, address); - } - return { ...state, address }; - } - case "set-wallet-source": { - const { source, persist } = action.payload; - if (persist) { - localStorage.setItem(WALLET_SOURCE_KEY, source); - } - return { ...state, source }; - } - case "clear": - localStorage.removeItem(ACCOUNT_KEY); - localStorage.removeItem(WALLET_SOURCE_KEY); - return { address: null, source: null }; - } -}; diff --git a/packages/react-vendor/src/ConnexProvider.tsx b/packages/react-vendor/src/ConnexProvider.tsx deleted file mode 100644 index 087fe0f2..00000000 --- a/packages/react-vendor/src/ConnexProvider.tsx +++ /dev/null @@ -1,244 +0,0 @@ -import React, { - createContext, - useCallback, - useContext, - useEffect, - useMemo, - useReducer, - useRef, -} from "react"; -import { Connex } from "@vechain/connex"; -import { newVendor } from "@vechain/connex-framework"; -import type { WalletConnectOptions, WCSigner } from "wallet-connect"; -import { newWcClient, newWcSigner, newWeb3Modal } from "wallet-connect"; -import { accountReducer, defaultAccountState } from "./AccountReducer"; -import type { - ConnexContext, - ConnexProviderOptions, - SetAccount, - SetSource, -} from "./types"; -import { WalletSource } from "./types"; - -/** - * Context - */ -const Context = createContext({} as ConnexContext); - -export const ConnexProvider: React.FC = ({ - children, - nodeOptions, - walletConnectOptions, - persistState = false, -}): React.ReactElement => { - const [accountState, dispatch] = useReducer( - accountReducer, - defaultAccountState - ); - - const wcSignerRef = useRef(); - - const disconnectMobile = useCallback(() => { - const client = wcSignerRef.current; - - if (client) { - client.disconnect().catch((err) => { - throw err; - }); - wcSignerRef.current = undefined; - } - }, []); - - const availableWallets = useMemo(() => { - const wallets: WalletSource[] = [WalletSource.Sync2]; - - if (window.vechain) { - wallets.push(WalletSource.VeWorldExtension); - } - - if (window.connex) { - wallets.push(WalletSource.Sync); - } - - if (walletConnectOptions) { - wallets.push(WalletSource.WalletConnect); - } - - return wallets; - }, [walletConnectOptions]); - - const onDisconnected = useCallback((): void => { - if (accountState.source === WalletSource.WalletConnect) { - disconnectMobile(); - } - - dispatch({ type: "clear" }); - }, [disconnectMobile, accountState]); - - useEffect(() => { - if ( - accountState.source === WalletSource.WalletConnect && - !walletConnectOptions - ) { - onDisconnected(); - } - - if ( - accountState.source === WalletSource.VeWorldExtension && - !window.vechain - ) { - onDisconnected(); - } - }, [accountState, walletConnectOptions, onDisconnected]); - - const updateSource: SetSource = useCallback( - (wallet: WalletSource): void => { - // We can't set VeWorld Mobile if there is no Wallet Connect config - if (wallet === WalletSource.WalletConnect && !walletConnectOptions) { - throw new Error("Wallet Connect config not found"); - } - - // We can't set VeWorld Extension if there is no VeChain extension - if (wallet === WalletSource.VeWorldExtension && !window.vechain) { - throw new Error("VeWorld extension not found"); - } - - dispatch({ - type: "set-wallet-source", - payload: { source: wallet, persist: persistState }, - }); - }, - [walletConnectOptions, persistState] - ); - - const thor: Connex.Thor = useMemo( - () => new Connex.Thor(nodeOptions), - [nodeOptions] - ); - - const createWalletConnectVendor = useCallback( - (options: WalletConnectOptions) => { - const { projectId, metadata } = options; - - const wcClient = newWcClient({ - projectId, - metadata, - }); - - const web3Modal = newWeb3Modal(projectId); - - const wcSigner: WCSigner = newWcSigner({ - genesisId: thor.genesis.id, - wcClient, - web3Modal, - onDisconnected, - }); - - wcSignerRef.current = wcSigner; - - return newVendor(wcSigner); - }, - [onDisconnected, thor.genesis.id] - ); - - /** - * Create the vendor - * Create a vendor for the provided options. If that vendor is not available, the priority is: - * 1. Wallet Connect - If the options are provided - * 2. VeWorld Extension - If the extension is available - * 3. Sync2 - As a fallback, as it is always available - */ - const vendor: Connex.Vendor = useMemo(() => { - const { source } = accountState; - - if (source === WalletSource.WalletConnect && walletConnectOptions) { - return createWalletConnectVendor(walletConnectOptions); - } - - if (source === WalletSource.Sync2 || source === WalletSource.Sync) { - return new Connex.Vendor(thor.genesis.id, source); - } - - if (source === WalletSource.VeWorldExtension && window.vechain) { - const extensionSigner: Connex.Signer = window.vechain.newConnexSigner( - thor.genesis.id - ); - - return newVendor(extensionSigner); - } - - // We've exhausted all options, so default to Wallet Connect if the options are provided - if (walletConnectOptions) { - return createWalletConnectVendor(walletConnectOptions); - } - - // No wallet connect options, so use the extension if it's available - if (window.vechain) { - const extensionSigner: Connex.Signer = window.vechain.newConnexSigner( - thor.genesis.id - ); - - return newVendor(extensionSigner); - } - - // Default to Sync2 - return new Connex.Vendor(thor.genesis.id, WalletSource.Sync2); - }, [ - createWalletConnectVendor, - thor.genesis.id, - walletConnectOptions, - accountState, - ]); - - const updateAccount: SetAccount = useCallback( - (address: string) => { - dispatch({ - type: "set-address", - payload: { address, persist: persistState }, - }); - }, - [persistState] - ); - - const wallets: WalletSource[] = useMemo(() => { - return Object.values(WalletSource); - }, []); - - const context: ConnexContext = useMemo(() => { - return { - connex: { - thor, - vendor, - }, - wallet: { - setSource: updateSource, - setAccount: updateAccount, - availableWallets, - wallets, - accountState, - disconnect: onDisconnected, - }, - }; - }, [ - onDisconnected, - updateAccount, - accountState, - thor, - vendor, - updateSource, - wallets, - availableWallets, - ]); - - return {children}; -}; - -export const useConnex = (): Connex => { - const context = useContext(Context); - return context.connex; -}; - -export const useWallet = (): ConnexContext["wallet"] => { - const context = useContext(Context); - return context.wallet; -}; diff --git a/packages/react-vendor/src/index.tsx b/packages/react-vendor/src/index.tsx deleted file mode 100644 index b418b710..00000000 --- a/packages/react-vendor/src/index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import * as React from "react"; - -export * from "./ConnexProvider"; -export * from "./types"; diff --git a/packages/react-vendor/.eslintrc.js b/packages/react-wallet-kit/.eslintrc.js similarity index 100% rename from packages/react-vendor/.eslintrc.js rename to packages/react-wallet-kit/.eslintrc.js diff --git a/packages/react-vendor/README.md b/packages/react-wallet-kit/README.md similarity index 100% rename from packages/react-vendor/README.md rename to packages/react-wallet-kit/README.md diff --git a/packages/react-vendor/package.json b/packages/react-wallet-kit/package.json similarity index 84% rename from packages/react-vendor/package.json rename to packages/react-wallet-kit/package.json index 2912e8fb..db775251 100644 --- a/packages/react-vendor/package.json +++ b/packages/react-wallet-kit/package.json @@ -1,5 +1,5 @@ { - "name": "react-vendor", + "name": "@vechain/react-wallet-kit", "version": "0.0.0", "private": true, "license": "MIT", @@ -15,8 +15,9 @@ "dependencies": { "@vechain/connex": "2.1.0", "@vechain/connex-framework": "2.1.0", - "thor-devkit": "^2.0.9", - "wallet-connect": "*" + "@vechain/wallet-connect": "*", + "@vechain/wallet-kit": "*", + "thor-devkit": "^2.0.9" }, "devDependencies": { "@types/react": "^18.2.28", diff --git a/packages/react-wallet-kit/src/AccountReducer.ts b/packages/react-wallet-kit/src/AccountReducer.ts new file mode 100644 index 00000000..54f75dd5 --- /dev/null +++ b/packages/react-wallet-kit/src/AccountReducer.ts @@ -0,0 +1,44 @@ +import type { WalletSource } from '@vechain/wallet-kit'; +import type { AccountState } from './types'; + +const ACCOUNT_KEY = 'sample-app@account'; +const WALLET_SOURCE_KEY = 'sample-app@wallet-source'; + +export type AccountAction = + | { type: 'set-address'; payload: { address: string; persist: boolean } } + | { + type: 'set-wallet-source'; + payload: { source: WalletSource; persist: boolean }; + } + | { type: 'clear' }; + +export const defaultAccountState: AccountState = { + address: localStorage.getItem(ACCOUNT_KEY), + source: localStorage.getItem(WALLET_SOURCE_KEY) as WalletSource +}; + +export const accountReducer = ( + state: AccountState, + action: AccountAction +): AccountState => { + switch (action.type) { + case 'set-address': { + const { address, persist } = action.payload; + if (persist) { + localStorage.setItem(ACCOUNT_KEY, address); + } + return { ...state, address }; + } + case 'set-wallet-source': { + const { source, persist } = action.payload; + if (persist) { + localStorage.setItem(WALLET_SOURCE_KEY, source); + } + return { ...state, source }; + } + case 'clear': + localStorage.removeItem(ACCOUNT_KEY); + localStorage.removeItem(WALLET_SOURCE_KEY); + return { address: null, source: null }; + } +}; diff --git a/packages/react-wallet-kit/src/ConnexProvider.tsx b/packages/react-wallet-kit/src/ConnexProvider.tsx new file mode 100644 index 00000000..aa2e563e --- /dev/null +++ b/packages/react-wallet-kit/src/ConnexProvider.tsx @@ -0,0 +1,250 @@ +import React, { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useReducer, + useRef +} from 'react'; +import { Connex } from '@vechain/connex'; +import { newVendor } from '@vechain/connex-framework'; +import type { WalletConnectOptions, WCSigner } from '@vechain/wallet-connect'; +import { + newWcClient, + newWcSigner, + newWeb3Modal +} from '@vechain/wallet-connect'; +import { WalletSource } from '@vechain/wallet-kit'; +import { accountReducer, defaultAccountState } from './AccountReducer'; +import type { + ConnexContext, + ConnexProviderOptions, + SetAccount, + SetSource +} from './types'; + +/** + * Context + */ +const Context = createContext({} as ConnexContext); + +export const ConnexProvider: React.FC = ({ + children, + nodeOptions, + walletConnectOptions, + persistState = false +}): React.ReactElement => { + const [accountState, dispatch] = useReducer( + accountReducer, + defaultAccountState + ); + + const wcSignerRef = useRef(); + + const disconnectMobile = useCallback(() => { + const client = wcSignerRef.current; + + if (client) { + client.disconnect().catch((err) => { + throw err; + }); + wcSignerRef.current = undefined; + } + }, []); + + const availableWallets = useMemo(() => { + const wallets: WalletSource[] = [WalletSource.Sync2]; + + if (window.vechain) { + wallets.push(WalletSource.VeWorldExtension); + } + + if (window.connex) { + wallets.push(WalletSource.Sync); + } + + if (walletConnectOptions) { + wallets.push(WalletSource.WalletConnect); + } + + return wallets; + }, [walletConnectOptions]); + + const onDisconnected = useCallback((): void => { + if (accountState.source === WalletSource.WalletConnect) { + disconnectMobile(); + } + + dispatch({ type: 'clear' }); + }, [disconnectMobile, accountState]); + + useEffect(() => { + if ( + accountState.source === WalletSource.WalletConnect && + !walletConnectOptions + ) { + onDisconnected(); + } + + if ( + accountState.source === WalletSource.VeWorldExtension && + !window.vechain + ) { + onDisconnected(); + } + }, [accountState, walletConnectOptions, onDisconnected]); + + const updateSource: SetSource = useCallback( + (wallet: WalletSource): void => { + // We can't set VeWorld Mobile if there is no Wallet Connect config + if ( + wallet === WalletSource.WalletConnect && + !walletConnectOptions + ) { + throw new Error('Wallet Connect config not found'); + } + + // We can't set VeWorld Extension if there is no VeChain extension + if (wallet === WalletSource.VeWorldExtension && !window.vechain) { + throw new Error('VeWorld extension not found'); + } + + dispatch({ + type: 'set-wallet-source', + payload: { source: wallet, persist: persistState } + }); + }, + [walletConnectOptions, persistState] + ); + + const thor: Connex.Thor = useMemo( + () => new Connex.Thor(nodeOptions), + [nodeOptions] + ); + + const createWalletConnectVendor = useCallback( + (options: WalletConnectOptions) => { + const { projectId, metadata } = options; + + const wcClient = newWcClient({ + projectId, + metadata + }); + + const web3Modal = newWeb3Modal(projectId); + + const wcSigner: WCSigner = newWcSigner({ + genesisId: thor.genesis.id, + wcClient, + web3Modal, + onDisconnected + }); + + wcSignerRef.current = wcSigner; + + return newVendor(wcSigner); + }, + [onDisconnected, thor.genesis.id] + ); + + /** + * Create the vendor + * Create a vendor for the provided options. If that vendor is not available, the priority is: + * 1. Wallet Connect - If the options are provided + * 2. VeWorld Extension - If the extension is available + * 3. Sync2 - As a fallback, as it is always available + */ + const vendor: Connex.Vendor = useMemo(() => { + const { source } = accountState; + + if (source === WalletSource.WalletConnect && walletConnectOptions) { + return createWalletConnectVendor(walletConnectOptions); + } + + if (source === WalletSource.Sync2 || source === WalletSource.Sync) { + return new Connex.Vendor(thor.genesis.id, source); + } + + if (source === WalletSource.VeWorldExtension && window.vechain) { + const extensionSigner = window.vechain.newConnexSigner( + thor.genesis.id + ); + + return newVendor(extensionSigner); + } + + // We've exhausted all options, so default to Wallet Connect if the options are provided + if (walletConnectOptions) { + return createWalletConnectVendor(walletConnectOptions); + } + + // No wallet connect options, so use the extension if it's available + if (window.vechain) { + const extensionSigner: Connex.Signer = + window.vechain.newConnexSigner(thor.genesis.id); + + return newVendor(extensionSigner); + } + + // Default to Sync2 + return new Connex.Vendor(thor.genesis.id, WalletSource.Sync2); + }, [ + createWalletConnectVendor, + thor.genesis.id, + walletConnectOptions, + accountState + ]); + + const updateAccount: SetAccount = useCallback( + (address: string) => { + dispatch({ + type: 'set-address', + payload: { address, persist: persistState } + }); + }, + [persistState] + ); + + const wallets: WalletSource[] = useMemo(() => { + return Object.values(WalletSource); + }, []); + + const context: ConnexContext = useMemo(() => { + return { + connex: { + thor, + vendor + }, + wallet: { + setSource: updateSource, + setAccount: updateAccount, + availableWallets, + wallets, + accountState, + disconnect: onDisconnected + } + }; + }, [ + onDisconnected, + updateAccount, + accountState, + thor, + vendor, + updateSource, + wallets, + availableWallets + ]); + + return {children}; +}; + +export const useConnex = (): Connex => { + const context = useContext(Context); + return context.connex; +}; + +export const useWallet = (): ConnexContext['wallet'] => { + const context = useContext(Context); + return context.wallet; +}; diff --git a/packages/react-wallet-kit/src/index.tsx b/packages/react-wallet-kit/src/index.tsx new file mode 100644 index 00000000..a9e4b854 --- /dev/null +++ b/packages/react-wallet-kit/src/index.tsx @@ -0,0 +1,5 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import * as React from 'react'; + +export * from './ConnexProvider'; +export * from './types'; diff --git a/packages/react-vendor/src/types.ts b/packages/react-wallet-kit/src/types.ts similarity index 56% rename from packages/react-vendor/src/types.ts rename to packages/react-wallet-kit/src/types.ts index c44c852e..dfa00253 100644 --- a/packages/react-vendor/src/types.ts +++ b/packages/react-wallet-kit/src/types.ts @@ -1,28 +1,11 @@ -import type { Options } from "@vechain/connex"; -import type React from "react"; -import type { WalletConnectOptions } from "wallet-connect"; +import type { Options } from '@vechain/connex'; +import type React from 'react'; +import type { WalletConnectOptions } from '@vechain/wallet-connect'; +import type { WalletSource } from '@vechain/wallet-kit'; export interface AccountState { - address: string | null; - source: WalletSource | null; -} - -declare global { - interface Window { - vechain?: { - newConnexSigner: (genesisId: string) => Connex.Signer; - }; - } -} - -/** - * The wallet source - */ -export enum WalletSource { - WalletConnect = "wallet-connect", - VeWorldExtension = "veworld-extension", - Sync2 = "sync2", - Sync = "sync", + address: string | null; + source: WalletSource | null; } /** @@ -45,10 +28,10 @@ export type SetSource = (wallet: WalletSource) => void; * @param persistState - An option to persist state. Defaults to false */ export interface ConnexProviderOptions { - children: React.ReactNode; - nodeOptions: Omit; - walletConnectOptions?: WalletConnectOptions; - persistState?: boolean; + children: React.ReactNode; + nodeOptions: Omit; + walletConnectOptions?: WalletConnectOptions; + persistState?: boolean; } /** @@ -65,13 +48,13 @@ export interface ConnexProviderOptions { */ export interface ConnexContext { - connex: Connex; - wallet: { - setSource: SetSource; - setAccount: SetAccount; - availableWallets: WalletSource[]; - wallets: WalletSource[]; - accountState: AccountState; - disconnect: () => void; - }; + connex: Connex; + wallet: { + setSource: SetSource; + setAccount: SetAccount; + availableWallets: WalletSource[]; + wallets: WalletSource[]; + accountState: AccountState; + disconnect: () => void; + }; } diff --git a/packages/react-vendor/tsconfig.json b/packages/react-wallet-kit/tsconfig.json similarity index 100% rename from packages/react-vendor/tsconfig.json rename to packages/react-wallet-kit/tsconfig.json diff --git a/packages/wallet-connect/package.json b/packages/wallet-connect/package.json index 218bda03..ef6c3864 100644 --- a/packages/wallet-connect/package.json +++ b/packages/wallet-connect/package.json @@ -1,5 +1,5 @@ { - "name": "wallet-connect", + "name": "@vechain/wallet-connect", "version": "0.0.0", "private": true, "license": "MIT", diff --git a/packages/wallet-connect/src/client.ts b/packages/wallet-connect/src/client.ts index bcf313b1..16b053b8 100644 --- a/packages/wallet-connect/src/client.ts +++ b/packages/wallet-connect/src/client.ts @@ -1,38 +1,40 @@ -import { SignClient } from "@walletconnect/sign-client"; +import { SignClient } from '@walletconnect/sign-client'; import type { - ResolvedSignClient, - WalletConnectOptions, - WCClient, -} from "./types"; + ResolvedSignClient, + WalletConnectOptions, + WCClient +} from './types'; const _cachedClients: Record = {}; export const newWcClient = ({ - projectId, - metadata, + projectId, + metadata }: WalletConnectOptions): WCClient => { - const cachedClient = _cachedClients[projectId]; + const cachedClient = _cachedClients[projectId]; - if (cachedClient) { - return cachedClient; - } + if (cachedClient) { + return cachedClient; + } - const _signClient = SignClient.init({ - projectId, - metadata, - }); + const _signClient = SignClient.init({ + projectId, + metadata + }); - const client: WCClient = { - get: async (): Promise => { - try { - return await _signClient; - } catch (e) { - throw new Error(`Failed to initialise the wallet connect sign client`); - } - }, - }; + const client: WCClient = { + get: async (): Promise => { + try { + return await _signClient; + } catch (e) { + throw new Error( + `Failed to initialise the wallet connect sign client` + ); + } + } + }; - _cachedClients[projectId] = client; + _cachedClients[projectId] = client; - return client; + return client; }; diff --git a/packages/wallet-connect/src/constants.ts b/packages/wallet-connect/src/constants.ts index a0c7589f..fc70546d 100644 --- a/packages/wallet-connect/src/constants.ts +++ b/packages/wallet-connect/src/constants.ts @@ -1,6 +1,6 @@ export enum DefaultMethods { - RequestTransaction = "thor_sendTransaction", - SignCertificate = "thor_signCertificate", + RequestTransaction = 'thor_sendTransaction', + SignCertificate = 'thor_signCertificate' } export enum DefaultEvents {} diff --git a/packages/wallet-connect/src/index.ts b/packages/wallet-connect/src/index.ts index 49e0ec14..ae707d36 100644 --- a/packages/wallet-connect/src/index.ts +++ b/packages/wallet-connect/src/index.ts @@ -1,13 +1,13 @@ -import { newWcSigner } from "./signer"; -import { newWeb3Modal } from "./web3-modal"; -import { newWcClient } from "./client"; +import { newWcSigner } from './signer'; +import { newWeb3Modal } from './web3-modal'; +import { newWcClient } from './client'; export type { - WCSigner, - WCSignerOptions, - WalletConnectOptions, - ResolvedSignClient, - WCClient, -} from "./types"; + WCSigner, + WCSignerOptions, + WalletConnectOptions, + ResolvedSignClient, + WCClient +} from './types'; export { newWcSigner, newWeb3Modal, newWcClient }; diff --git a/packages/wallet-connect/src/signer.ts b/packages/wallet-connect/src/signer.ts index 687aab0c..c24c39ee 100644 --- a/packages/wallet-connect/src/signer.ts +++ b/packages/wallet-connect/src/signer.ts @@ -1,214 +1,216 @@ /// -import type { SessionTypes } from "@walletconnect/types"; -import type { ProposalTypes } from "@walletconnect/types/dist/types/sign-client/proposal"; -import type { EngineTypes } from "@walletconnect/types/dist/types/sign-client/engine"; -import { getSdkError } from "@walletconnect/utils"; -import type { SignClient } from "@walletconnect/sign-client/dist/types/client"; -import type { WCSigner, WCSignerOptions } from "./types"; -import { DefaultEvents, DefaultMethods } from "./constants"; +import type { SessionTypes } from '@walletconnect/types'; +import type { ProposalTypes } from '@walletconnect/types/dist/types/sign-client/proposal'; +import type { EngineTypes } from '@walletconnect/types/dist/types/sign-client/engine'; +import { getSdkError } from '@walletconnect/utils'; +import type { SignClient } from '@walletconnect/sign-client/dist/types/client'; +import type { WCSigner, WCSignerOptions } from './types'; +import { DefaultEvents, DefaultMethods } from './constants'; interface SessionAccount { - networkIdentifier: string; - address: string; - topic: string; + networkIdentifier: string; + address: string; + topic: string; } export const newWcSigner = ({ - genesisId, - wcClient, - web3Modal, - onDisconnected, + genesisId, + wcClient, + web3Modal, + onDisconnected }: WCSignerOptions): WCSigner => { - const chainId = `vechain:${genesisId.slice(-32)}`; - let session: SessionTypes.Struct | undefined; - - wcClient - .get() - .then((clientInstance) => { - listenToEvents(clientInstance); - restoreSession(clientInstance); - }) - .catch(() => { - throw new Error(`Failed to initialise the wallet connect sign client`); - }); - - const makeRequest = async ( - params: EngineTypes.RequestParams["request"], - signer?: string - ): Promise => { - const sessionTopic = await getSessionTopic(signer); - - const signClient = await wcClient.get(); - - return signClient.request({ - topic: sessionTopic, - chainId, - request: params, - }); - }; - - const signTx = async ( - message: Connex.Vendor.TxMessage, - options: Connex.Signer.TxOptions - ): Promise => { - return makeRequest({ - method: DefaultMethods.RequestTransaction, - params: [{ message, options }], - }); - }; - - const signCert = async ( - message: Connex.Vendor.CertMessage, - options: Connex.Signer.CertOptions - ): Promise => { - return makeRequest({ - method: DefaultMethods.SignCertificate, - params: [{ message, options }], - }); - }; - - const disconnect = async (): Promise => { - if (!session) return; - - const topic = session.topic; - session = undefined; - - const signClient = await wcClient.get(); - - try { - await signClient.disconnect({ - topic, - reason: getSdkError("USER_DISCONNECTED"), - }); - } catch (e) { - throw new Error(`SignClient.disconnect failed`); - } - }; - - /** - * Validates the requested account and network against a request - * @param requestedAddress - The optional requested account address - */ - const validateSession = ( - requestedAddress?: string - ): SessionAccount | undefined => { - if (!session) return; - - const firstAccount = session.namespaces.vechain.accounts[0]; - - const address = firstAccount.split(":")[2]; - const networkIdentifier = firstAccount.split(":")[1]; - - // Return undefined if the network identifier doesn't match - if (networkIdentifier !== genesisId.slice(-32)) return; - - // Return undefined if the address doesn't match - if ( - requestedAddress && - requestedAddress.toLowerCase() !== address.toLowerCase() - ) - return; + const chainId = `vechain:${genesisId.slice(-32)}`; + let session: SessionTypes.Struct | undefined; + + wcClient + .get() + .then((clientInstance) => { + listenToEvents(clientInstance); + restoreSession(clientInstance); + }) + .catch(() => { + throw new Error( + `Failed to initialise the wallet connect sign client` + ); + }); - return { - address, - networkIdentifier, - topic: session.topic, + const makeRequest = async ( + params: EngineTypes.RequestParams['request'], + signer?: string + ): Promise => { + const sessionTopic = await getSessionTopic(signer); + + const signClient = await wcClient.get(); + + return signClient.request({ + topic: sessionTopic, + chainId, + request: params + }); + }; + + const signTx = async ( + message: Connex.Vendor.TxMessage, + options: Connex.Signer.TxOptions + ): Promise => { + return makeRequest({ + method: DefaultMethods.RequestTransaction, + params: [{ message, options }] + }); + }; + + const signCert = async ( + message: Connex.Vendor.CertMessage, + options: Connex.Signer.CertOptions + ): Promise => { + return makeRequest({ + method: DefaultMethods.SignCertificate, + params: [{ message, options }] + }); + }; + + const disconnect = async (): Promise => { + if (!session) return; + + const topic = session.topic; + session = undefined; + + const signClient = await wcClient.get(); + + try { + await signClient.disconnect({ + topic, + reason: getSdkError('USER_DISCONNECTED') + }); + } catch (e) { + throw new Error(`SignClient.disconnect failed`); + } }; - }; - const getSessionTopic = async ( - requestedAccount?: string - ): Promise => { - const validation = validateSession(requestedAccount); + /** + * Validates the requested account and network against a request + * @param requestedAddress - The optional requested account address + */ + const validateSession = ( + requestedAddress?: string + ): SessionAccount | undefined => { + if (!session) return; + + const firstAccount = session.namespaces.vechain.accounts[0]; + + const address = firstAccount.split(':')[2]; + const networkIdentifier = firstAccount.split(':')[1]; + + // Return undefined if the network identifier doesn't match + if (networkIdentifier !== genesisId.slice(-32)) return; + + // Return undefined if the address doesn't match + if ( + requestedAddress && + requestedAddress.toLowerCase() !== address.toLowerCase() + ) + return; + + return { + address, + networkIdentifier, + topic: session.topic + }; + }; - if (validation) return validation.topic; + const getSessionTopic = async ( + requestedAccount?: string + ): Promise => { + const validation = validateSession(requestedAccount); - const newSession = await connect(); + if (validation) return validation.topic; - return newSession.topic; - }; + const newSession = await connect(); - const connect = async (): Promise => { - const signClient = await wcClient.get(); + return newSession.topic; + }; - const namespace: ProposalTypes.RequiredNamespace = { - methods: Object.values(DefaultMethods), - chains: [chainId], - events: Object.values(DefaultEvents), + const connect = async (): Promise => { + const signClient = await wcClient.get(); + + const namespace: ProposalTypes.RequiredNamespace = { + methods: Object.values(DefaultMethods), + chains: [chainId], + events: Object.values(DefaultEvents) + }; + + try { + const requiredNamespaces: Record< + string, + ProposalTypes.RequiredNamespace + > = { + vechain: namespace + }; + + const { uri, approval } = await signClient.connect({ + requiredNamespaces + }); + + if (uri) { + await web3Modal.openModal({ uri, chains: namespace.chains }); + } + + return await new Promise((resolve, reject) => { + web3Modal.subscribeModal((ev: { open: boolean }) => { + if (!ev.open) { + reject(new Error('User closed modal')); + } + }); + + approval() + .then((newSession) => { + session = newSession; + resolve(newSession); + }) + .catch(reject); + }); + } finally { + web3Modal.closeModal(); + } }; - try { - const requiredNamespaces: Record< - string, - ProposalTypes.RequiredNamespace - > = { - vechain: namespace, - }; - - const { uri, approval } = await signClient.connect({ - requiredNamespaces, - }); - - if (uri) { - await web3Modal.openModal({ uri, chains: namespace.chains }); - } - - return await new Promise((resolve, reject) => { - web3Modal.subscribeModal((ev: { open: boolean }) => { - if (!ev.open) { - reject(new Error("User closed modal")); - } + const listenToEvents = (_client: SignClient): void => { + _client.on('session_update', ({ topic, params }) => { + const { namespaces } = params; + const _session = _client.session.get(topic); + session = { ..._session, namespaces }; + }); + + _client.on('session_delete', () => { + if (onDisconnected) onDisconnected(); + disconnect().catch(() => { + throw new Error('Failed to disconnect'); + }); }); + }; - approval() - .then((newSession) => { - session = newSession; - resolve(newSession); - }) - .catch(reject); - }); - } finally { - web3Modal.closeModal(); - } - }; - - const listenToEvents = (_client: SignClient): void => { - _client.on("session_update", ({ topic, params }) => { - const { namespaces } = params; - const _session = _client.session.get(topic); - session = { ..._session, namespaces }; - }); - - _client.on("session_delete", () => { - if (onDisconnected) onDisconnected(); - disconnect().catch(() => { - throw new Error("Failed to disconnect"); - }); - }); - }; - - const restoreSession = (_client: SignClient): void => { - if (typeof session !== "undefined") return; - - const sessionKeys = _client.session.keys; - - for (const key of sessionKeys) { - const _session = _client.session.get(key); - const accounts = _session.namespaces.vechain.accounts; - - for (const acc of accounts) { - if (acc.split(":")[1] === genesisId.slice(-32)) { - session = _session; - return; + const restoreSession = (_client: SignClient): void => { + if (typeof session !== 'undefined') return; + + const sessionKeys = _client.session.keys; + + for (const key of sessionKeys) { + const _session = _client.session.get(key); + const accounts = _session.namespaces.vechain.accounts; + + for (const acc of accounts) { + if (acc.split(':')[1] === genesisId.slice(-32)) { + session = _session; + return; + } + } } - } - } - }; - - return { - signTx, - signCert, - disconnect, - genesisId, - }; + }; + + return { + signTx, + signCert, + disconnect, + genesisId + }; }; diff --git a/packages/wallet-connect/src/types.d.ts b/packages/wallet-connect/src/types.d.ts index 090ab4be..c1e4deea 100644 --- a/packages/wallet-connect/src/types.d.ts +++ b/packages/wallet-connect/src/types.d.ts @@ -1,6 +1,6 @@ -import type { WalletConnectModal } from "@walletconnect/modal"; -import type { SignClientTypes } from "@walletconnect/types"; -import type { SignClient } from "@walletconnect/sign-client"; +import type { WalletConnectModal } from '@walletconnect/modal'; +import type { SignClientTypes } from '@walletconnect/types'; +import type { SignClient } from '@walletconnect/sign-client'; export type ResolvedSignClient = Awaited>; @@ -9,22 +9,22 @@ export type ResolvedSignClient = Awaited>; * */ export type WCSigner = Connex.Signer & { - /** - * Disconnects and cleans up the WalletConnect session - */ - disconnect: () => Promise; + /** + * Disconnects and cleans up the WalletConnect session + */ + disconnect: () => Promise; - /** - * The genesis ID of the current signer - */ - genesisId: string; + /** + * The genesis ID of the current signer + */ + genesisId: string; }; export interface WCClient { - /** - * Gets the initialized WalletConnect SignClient - */ - get: () => Promise; + /** + * Gets the initialized WalletConnect SignClient + */ + get: () => Promise; } /** @@ -35,8 +35,8 @@ export interface WCClient { * @param logger - The logger or log level to use */ export interface WalletConnectOptions { - projectId: string; - metadata: SignClientTypes.Options["metadata"]; + projectId: string; + metadata: SignClientTypes.Options['metadata']; } /** @@ -47,8 +47,8 @@ export interface WalletConnectOptions { * @param genesisId - The genesis ID of the VeChain network you want to connect to */ export interface WCSignerOptions { - wcClient: WCClient; - web3Modal: WalletConnectModal; - onDisconnected?: () => void; - genesisId: string; + wcClient: WCClient; + web3Modal: WalletConnectModal; + onDisconnected?: () => void; + genesisId: string; } diff --git a/packages/wallet-connect/src/web3-modal.ts b/packages/wallet-connect/src/web3-modal.ts index f0453585..1f5f571d 100644 --- a/packages/wallet-connect/src/web3-modal.ts +++ b/packages/wallet-connect/src/web3-modal.ts @@ -1,4 +1,4 @@ -import { WalletConnectModal } from "@walletconnect/modal"; +import { WalletConnectModal } from '@walletconnect/modal'; const _cachedModals: Record = {}; @@ -7,34 +7,34 @@ const _cachedModals: Record = {}; * @param projectId - Your WalletConnect project ID */ export const newWeb3Modal = (projectId: string): WalletConnectModal => { - const cached = _cachedModals[projectId]; + const cached = _cachedModals[projectId]; - if (cached) { - return cached; - } + if (cached) { + return cached; + } - const modal = new WalletConnectModal({ - projectId, - explorerRecommendedWalletIds: "NONE", - mobileWallets: [ - { - name: "VeWorld", - id: "veworld-mobile", - links: { - native: "veworld://org.vechain.veworld.app/", - universal: "https://veworld.com", + const modal = new WalletConnectModal({ + projectId, + explorerRecommendedWalletIds: 'NONE', + mobileWallets: [ + { + name: 'VeWorld', + id: 'veworld-mobile', + links: { + native: 'veworld://org.vechain.veworld.app/', + universal: 'https://veworld.com' + } + } + ], + themeVariables: { + '--wcm-z-index': '99999999' }, - }, - ], - themeVariables: { - "--wcm-z-index": "99999999", - }, - walletImages: { - "veworld-mobile": "https://www.veworld.net/assets/logo/logo.svg", - }, - }); + walletImages: { + 'veworld-mobile': 'https://www.veworld.net/assets/logo/logo.svg' + } + }); - _cachedModals[projectId] = modal; + _cachedModals[projectId] = modal; - return modal; + return modal; }; diff --git a/packages/wallet-connect/tsconfig.json b/packages/wallet-connect/tsconfig.json index ad010d9a..127dc568 100644 --- a/packages/wallet-connect/tsconfig.json +++ b/packages/wallet-connect/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "tsconfig/react-library.json", + "extends": "tsconfig/base.json", "include": ["."], "exclude": ["dist", "node_modules"] } diff --git a/packages/wallet-kit/.eslintrc.js b/packages/wallet-kit/.eslintrc.js new file mode 100644 index 00000000..f8329c28 --- /dev/null +++ b/packages/wallet-kit/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ["custom/library"], +}; diff --git a/packages/wallet-kit/README.md b/packages/wallet-kit/README.md new file mode 100644 index 00000000..687a3faf --- /dev/null +++ b/packages/wallet-kit/README.md @@ -0,0 +1 @@ +# `wallet-helpers` diff --git a/packages/wallet-kit/package.json b/packages/wallet-kit/package.json new file mode 100644 index 00000000..ff071113 --- /dev/null +++ b/packages/wallet-kit/package.json @@ -0,0 +1,26 @@ +{ + "name": "@vechain/wallet-kit", + "version": "0.0.0", + "private": true, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "scripts": { + "build": "tsup src/index.ts --format cjs --dts", + "clean": "rm -rf dist node_modules .turbo", + "dev": "tsup src/index.ts --format cjs --watch --dts", + "lint": "eslint src --ext .js,.jsx,.ts,.tsx" + }, + "dependencies": { + "@vechain/connex": "2.1.0", + "@vechain/connex-driver": "2.1.0", + "@vechain/connex-framework": "2.1.0", + "@vechain/wallet-connect": "*" + }, + "devDependencies": { + "eslint": "^8.4.1", + "eslint-config-custom": "*", + "tsconfig": "*", + "tsup": "^5.10.1", + "typescript": "4.9.5" + } +} diff --git a/packages/wallet-kit/src/connex.ts b/packages/wallet-kit/src/connex.ts new file mode 100644 index 00000000..13717cf2 --- /dev/null +++ b/packages/wallet-kit/src/connex.ts @@ -0,0 +1,67 @@ +import { Framework } from '@vechain/connex-framework'; +import type { WalletConnectOptions } from '@vechain/wallet-connect'; +import type { Genesis } from './types'; +import { WalletSource } from './wallet'; +import { createSigner } from './signer'; +import { normalizeGenesisBlock } from './genesis'; +import { createVendorDriver } from './vendor-driver'; +import { createDriverNoVendor } from './thor-driver'; + +interface BaseConnexOptions { + nodeUrl: string; + genesis: Genesis; +} + +interface NoWalletConfigOptions extends BaseConnexOptions { + source: + | WalletSource.VeWorldExtension + | WalletSource.Sync + | WalletSource.Sync2; +} + +interface WalletConnectConfigOptions extends BaseConnexOptions { + source: WalletSource.WalletConnect; + options: WalletConnectOptions; + onDisconnected: () => void; +} + +type ConnexOptions = NoWalletConfigOptions | WalletConnectConfigOptions; + +const createConnexInstance = (options: ConnexOptions): Connex => { + const { source, genesis } = options; + + const genesisBlock = normalizeGenesisBlock(genesis); + + let signer: Promise; + + if (source === WalletSource.WalletConnect) { + signer = createSigner({ + source: options.source, + genesis: options.genesis, + options: options.options, + onDisconnected: options.onDisconnected + }); + } else { + signer = createSigner({ + source: options.source, + genesis: options.genesis + }); + } + + // This is cached, so the `DriverNoVendor` is shared between wallet sources + const driverNoVendor = createDriverNoVendor(options.nodeUrl, genesisBlock); + + // This is also cached based on the source + const vendorDriver = createVendorDriver(signer, source); + + vendorDriver.setNoVendor(driverNoVendor); + + const framework = new Framework(vendorDriver); + + return { + thor: framework.thor, + vendor: framework.vendor + }; +}; + +export { createConnexInstance }; diff --git a/packages/wallet-kit/src/genesis.ts b/packages/wallet-kit/src/genesis.ts new file mode 100644 index 00000000..e50705a1 --- /dev/null +++ b/packages/wallet-kit/src/genesis.ts @@ -0,0 +1,17 @@ +import { genesisBlocks } from '@vechain/connex/esm/config'; +import type { Genesis } from './types'; + +const normalizeGenesisId = (genesis: Genesis): string => { + if (genesis === 'main' || genesis === 'test') + return genesisBlocks[genesis].id; + + return genesis.id; +}; + +const normalizeGenesisBlock = (genesis: Genesis): Connex.Thor.Block => { + if (genesis === 'main' || genesis === 'test') return genesisBlocks[genesis]; + + return genesis; +}; + +export { normalizeGenesisId, normalizeGenesisBlock }; diff --git a/packages/wallet-kit/src/index.ts b/packages/wallet-kit/src/index.ts new file mode 100644 index 00000000..4684031f --- /dev/null +++ b/packages/wallet-kit/src/index.ts @@ -0,0 +1,2 @@ +export * from './connex'; +export * from './wallet'; diff --git a/packages/wallet-kit/src/signer.ts b/packages/wallet-kit/src/signer.ts new file mode 100644 index 00000000..42a19854 --- /dev/null +++ b/packages/wallet-kit/src/signer.ts @@ -0,0 +1,79 @@ +import type { + WalletConnectOptions, + WCSigner +} from '@vechain/wallet-connect/dist'; +import { + newWcClient, + newWcSigner, + newWeb3Modal +} from '@vechain/wallet-connect/dist'; +import type { Connex } from '@vechain/connex'; +import { createSync, createSync2 } from '@vechain/connex/esm/signer'; +import { WalletSource } from './wallet'; +import type { Genesis } from './types'; +import { normalizeGenesisId } from './genesis'; + +interface WCOptions { + source: WalletSource.WalletConnect; + genesis: Genesis; + options: WalletConnectOptions; + onDisconnected: () => void; +} + +interface DefaultOptions { + source: + | WalletSource.Sync + | WalletSource.Sync2 + | WalletSource.VeWorldExtension; + genesis: Genesis; +} + +type ICreateVendor = DefaultOptions | WCOptions; + +export const createSigner = (params: ICreateVendor): Promise => { + const { source, genesis } = params; + + const genesisId = normalizeGenesisId(genesis); + + if (source === WalletSource.VeWorldExtension) { + if (!window.vechain) { + throw new Error('VeWorld Extension is not installed'); + } + + const signer = window.vechain.newConnexSigner(genesisId); + + return Promise.resolve(signer); + } + + if (source === WalletSource.WalletConnect) { + const { onDisconnected, options } = params; + + const { projectId, metadata } = options; + + const wcClient = newWcClient({ + projectId, + metadata + }); + + const web3Modal = newWeb3Modal(projectId); + + const wcSigner: WCSigner = newWcSigner({ + genesisId, + wcClient, + web3Modal, + onDisconnected + }); + + return Promise.resolve(wcSigner); + } + + if (source === WalletSource.Sync) { + if (!window.connex) { + throw new Error('User is not in a Sync wallet'); + } + + return createSync(genesisId); + } + + return createSync2(genesisId); +}; diff --git a/packages/wallet-kit/src/thor-driver.ts b/packages/wallet-kit/src/thor-driver.ts new file mode 100644 index 00000000..258f48af --- /dev/null +++ b/packages/wallet-kit/src/thor-driver.ts @@ -0,0 +1,27 @@ +import { DriverNoVendor } from '@vechain/connex-driver/dist/driver-no-vendor'; +import { SimpleNet } from '@vechain/connex-driver'; +import type { Genesis } from './types'; +import { normalizeGenesisBlock } from './genesis'; + +let previousDriver: DriverNoVendor | undefined; + +const createDriverNoVendor = ( + nodeUrl: string, + genesis: Genesis +): DriverNoVendor => { + const genesisBlock = normalizeGenesisBlock(genesis); + + if (previousDriver && previousDriver.genesis.id === genesisBlock.id) { + return previousDriver; + } + + const net = new SimpleNet(nodeUrl); + + const driver = new DriverNoVendor(net, genesisBlock); + + previousDriver = driver; + + return driver; +}; + +export { createDriverNoVendor }; diff --git a/packages/wallet-kit/src/types.d.ts b/packages/wallet-kit/src/types.d.ts new file mode 100644 index 00000000..0f6e4507 --- /dev/null +++ b/packages/wallet-kit/src/types.d.ts @@ -0,0 +1,12 @@ +import type { Connex1 } from '@vechain/connex/esm/signer'; + +declare global { + interface Window { + vechain?: { + newConnexSigner: (genesisId: string) => Connex.Signer; + }; + connex?: Connex1; + } +} + +export type Genesis = 'main' | 'test' | Connex.Thor.Block; diff --git a/packages/wallet-kit/src/vendor-driver.ts b/packages/wallet-kit/src/vendor-driver.ts new file mode 100644 index 00000000..c02fb198 --- /dev/null +++ b/packages/wallet-kit/src/vendor-driver.ts @@ -0,0 +1,22 @@ +import { LazyDriver } from '@vechain/connex/esm/driver'; +import type { WalletSource } from './wallet'; + +const _cachedDrivers: Record = {}; + +const createVendorDriver = ( + signer: Promise, + source: WalletSource +): LazyDriver => { + const cachedDriver = _cachedDrivers[source]; + + if (cachedDriver) { + return cachedDriver; + } + + const driver = new LazyDriver(signer); + _cachedDrivers[source] = driver; + + return driver; +}; + +export { createVendorDriver }; diff --git a/packages/wallet-kit/src/wallet.ts b/packages/wallet-kit/src/wallet.ts new file mode 100644 index 00000000..05c1405d --- /dev/null +++ b/packages/wallet-kit/src/wallet.ts @@ -0,0 +1,31 @@ +/** + * Wallet types + */ + +enum WalletSource { + WalletConnect = 'wallet-connect', + VeWorldExtension = 'veworld-extension', + Sync2 = 'sync2', + Sync = 'sync' +} + +interface WalletConfig { + requiresCertificate: boolean; +} + +const DEFAULT_CONFIG: WalletConfig = { + requiresCertificate: true +}; + +const WalletMapping: Record = { + [WalletSource.WalletConnect]: { + requiresCertificate: false + }, + [WalletSource.VeWorldExtension]: DEFAULT_CONFIG, + [WalletSource.Sync2]: DEFAULT_CONFIG, + [WalletSource.Sync]: DEFAULT_CONFIG +}; + +export { WalletSource, WalletMapping }; + +export type { WalletConfig }; diff --git a/packages/wallet-kit/tsconfig.json b/packages/wallet-kit/tsconfig.json new file mode 100644 index 00000000..127dc568 --- /dev/null +++ b/packages/wallet-kit/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "tsconfig/base.json", + "include": ["."], + "exclude": ["dist", "node_modules"] +} diff --git a/yarn.lock b/yarn.lock index ba28a4c6..cdafe691 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3427,9 +3427,9 @@ mcl-wasm "^0.7.1" rustbn.js "~0.2.0" -"@nomicfoundation/hardhat-chai-matchers@^2.0.2": +"@nomicfoundation/hardhat-chai-matchers@^2.0.0": version "2.0.2" - resolved "https://registry.npmjs.org/@nomicfoundation/hardhat-chai-matchers/-/hardhat-chai-matchers-2.0.2.tgz" + resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-chai-matchers/-/hardhat-chai-matchers-2.0.2.tgz#a0e5dbca43ba9560c096da162c0e3245303479d1" integrity sha512-9Wu9mRtkj0U9ohgXYFbB/RQDa+PcEdyBm2suyEtsJf3PqzZEEjLUZgWnMjlFhATMk/fp3BjmnYVPrwl+gr8oEw== dependencies: "@types/chai-as-promised" "^7.1.3" @@ -3437,17 +3437,17 @@ deep-eql "^4.0.1" ordinal "^1.0.3" -"@nomicfoundation/hardhat-ethers@^3.0.4": +"@nomicfoundation/hardhat-ethers@^3.0.0": version "3.0.4" - resolved "https://registry.npmjs.org/@nomicfoundation/hardhat-ethers/-/hardhat-ethers-3.0.4.tgz" + resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-ethers/-/hardhat-ethers-3.0.4.tgz#6f0df2424e687e26d6574610de7a36bd69485cc1" integrity sha512-k9qbLoY7qn6C6Y1LI0gk2kyHXil2Tauj4kGzQ8pgxYXIGw8lWn8tuuL72E11CrlKaXRUvOgF0EXrv/msPI2SbA== dependencies: debug "^4.1.1" lodash.isequal "^4.5.0" -"@nomicfoundation/hardhat-network-helpers@^1.0.9": +"@nomicfoundation/hardhat-network-helpers@^1.0.0": version "1.0.9" - resolved "https://registry.npmjs.org/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.0.9.tgz" + resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.0.9.tgz#767449e8a2acda79306ac84626117583d95d25aa" integrity sha512-OXWCv0cHpwLUO2u7bFxBna6dQtCC2Gg/aN/KtJLO7gmuuA28vgmVKYFRCDUqrbjujzgfwQ2aKyZ9Y3vSmDqS7Q== dependencies: ethereumjs-util "^7.1.4" @@ -3457,9 +3457,9 @@ resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-toolbox/-/hardhat-toolbox-3.0.0.tgz#83e2c28a745aa4eb1236072166367b0de68b4c76" integrity sha512-MsteDXd0UagMksqm9KvcFG6gNKYNa3GGNCy73iQ6bEasEgg2v8Qjl6XA5hjs8o5UD5A3153B6W2BIVJ8SxYUtA== -"@nomicfoundation/hardhat-verify@^1.1.1": +"@nomicfoundation/hardhat-verify@^1.0.0": version "1.1.1" - resolved "https://registry.npmjs.org/@nomicfoundation/hardhat-verify/-/hardhat-verify-1.1.1.tgz" + resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-verify/-/hardhat-verify-1.1.1.tgz#6a433d777ce0172d1f0edf7f2d3e1df14b3ecfc1" integrity sha512-9QsTYD7pcZaQFEA3tBb/D/oCStYDiEVDN7Dxeo/4SCyHRSm86APypxxdOMEPlGmXsAvd+p1j/dTODcpxb8aztA== dependencies: "@ethersproject/abi" "^5.1.2" @@ -4084,17 +4084,17 @@ resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz" integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== -"@typechain/ethers-v6@^0.4.3": +"@typechain/ethers-v6@^0.4.0": version "0.4.3" - resolved "https://registry.npmjs.org/@typechain/ethers-v6/-/ethers-v6-0.4.3.tgz" + resolved "https://registry.yarnpkg.com/@typechain/ethers-v6/-/ethers-v6-0.4.3.tgz#badd99f88d5a1f1a2f42590f298e20cc62618e59" integrity sha512-TrxBsyb4ryhaY9keP6RzhFCviWYApcLCIRMPyWaKp2cZZrfaM3QBoxXTnw/eO4+DAY3l+8O0brNW0WgeQeOiDA== dependencies: lodash "^4.17.15" ts-essentials "^7.0.1" -"@typechain/hardhat@^8.0.3": +"@typechain/hardhat@^8.0.0": version "8.0.3" - resolved "https://registry.npmjs.org/@typechain/hardhat/-/hardhat-8.0.3.tgz" + resolved "https://registry.yarnpkg.com/@typechain/hardhat/-/hardhat-8.0.3.tgz#a114825f130405bbb8e535314003733b7ce3f91c" integrity sha512-MytSmJJn+gs7Mqrpt/gWkTCOpOQ6ZDfRrRT2gtZL0rfGe4QrU4x9ZdW15fFbVM/XTa+5EsKiOMYXhRABibNeng== dependencies: fs-extra "^9.1.0" @@ -4180,7 +4180,7 @@ dependencies: "@types/chai" "*" -"@types/chai@*", "@types/chai@^4.3.9": +"@types/chai@*", "@types/chai@^4.2.0": version "4.3.9" resolved "https://registry.npmjs.org/@types/chai/-/chai-4.3.9.tgz" integrity sha512-69TtiDzu0bcmKQv3yg1Zx409/Kd7r0b5F1PfpYJfSHzLGtB53547V4u+9iqKYsTu/O2ai6KTb0TInNpvuQ3qmg== @@ -4374,9 +4374,9 @@ resolved "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.4.tgz" integrity sha512-Kfe/D3hxHTusnPNRbycJE1N77WHDsdS4AjUYIzlDzhDrS47NrwuL3YW4VITxwR7KCVpzwgy4Rbj829KSSQmwXQ== -"@types/mocha@^10.0.3": +"@types/mocha@>=9.1.0": version "10.0.3" - resolved "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.3.tgz" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.3.tgz#4804fe9cd39da26eb62fa65c15ea77615a187812" integrity sha512-RsOPImTriV/OE4A9qKjMtk2MnXiuLLbcO3nCXK+kvq4nr0iMfFgpjaX3MPLb6f7+EL1FGSelYvuJMV6REH+ZPQ== "@types/node@*": @@ -4694,7 +4694,7 @@ resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== -"@vechain/connex-driver@^2.0.12", "@vechain/connex-driver@^2.1.0": +"@vechain/connex-driver@2.1.0", "@vechain/connex-driver@^2.0.12", "@vechain/connex-driver@^2.1.0": version "2.1.0" resolved "https://registry.npmjs.org/@vechain/connex-driver/-/connex-driver-2.1.0.tgz" integrity sha512-wFTakrFwsjJPq/9e+USGl5QKnoy6+FeZG4h+/wEITw5IaIAzujtTR8m7wqJFdto/gLZbP8j3puR4ZU9LrdfpkA== @@ -6326,9 +6326,9 @@ chai-as-promised@^7.1.1: dependencies: check-error "^1.0.2" -chai@^4.3.10: +chai@^4.2.0: version "4.3.10" - resolved "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.10.tgz#d784cec635e3b7e2ffb66446a63b4e33bd390384" integrity sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g== dependencies: assertion-error "^1.1.0" @@ -8574,7 +8574,7 @@ ethers@^5.7.1, ethers@^5.7.2: "@ethersproject/web" "5.7.1" "@ethersproject/wordlists" "5.7.0" -ethers@^6.7.1, ethers@^6.8.0: +ethers@^6.4.0, ethers@^6.7.1: version "6.8.0" resolved "https://registry.npmjs.org/ethers/-/ethers-6.8.0.tgz" integrity sha512-zrFbmQRlraM+cU5mE4CZTLBurZTs2gdp2ld0nG/f3ecBK+x6lZ69KSxBqZ4NjclxwfTxl5LeNufcBbMsTdY53Q== @@ -9472,9 +9472,9 @@ hard-rejection@^2.1.0: resolved "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz" integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== -hardhat-gas-reporter@^1.0.9: +hardhat-gas-reporter@^1.0.8: version "1.0.9" - resolved "https://registry.npmjs.org/hardhat-gas-reporter/-/hardhat-gas-reporter-1.0.9.tgz" + resolved "https://registry.yarnpkg.com/hardhat-gas-reporter/-/hardhat-gas-reporter-1.0.9.tgz#9a2afb354bc3b6346aab55b1c02ca556d0e16450" integrity sha512-INN26G3EW43adGKBNzYWOlI3+rlLnasXTwW79YNnUhXPDa+yHESgt639dJEs37gCjhkbNKcRRJnomXEuMFBXJg== dependencies: array-uniq "1.0.3" @@ -13305,6 +13305,11 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== + punycode@^2.1.0, punycode@^2.1.1: version "2.3.0" resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz" @@ -13332,6 +13337,13 @@ qs@6.11.0, qs@^6.4.0: dependencies: side-channel "^1.0.4" +qs@^6.11.2: + version "6.11.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" + integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== + dependencies: + side-channel "^1.0.4" + query-string@7.1.3: version "7.1.3" resolved "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz" @@ -14459,9 +14471,9 @@ solc@0.7.3: semver "^5.5.0" tmp "0.0.33" -solidity-coverage@^0.8.5: +solidity-coverage@^0.8.1: version "0.8.5" - resolved "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.5.tgz" + resolved "https://registry.yarnpkg.com/solidity-coverage/-/solidity-coverage-0.8.5.tgz#64071c3a0c06a0cecf9a7776c35f49edc961e875" integrity sha512-6C6N6OV2O8FQA0FWA95FdzVH+L16HU94iFgg5wAFZ29UpLFkgNI/DRR2HotG1bC0F4gAc/OMs2BJI44Q/DYlKQ== dependencies: "@ethersproject/abi" "^5.0.9" @@ -15585,9 +15597,9 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -typechain@^8.3.2: +typechain@^8.2.0: version "8.3.2" - resolved "https://registry.npmjs.org/typechain/-/typechain-8.3.2.tgz" + resolved "https://registry.yarnpkg.com/typechain/-/typechain-8.3.2.tgz#1090dd8d9c57b6ef2aed3640a516bdbf01b00d73" integrity sha512-x/sQYr5w9K7yv3es7jo4KTX05CLxOf7TRWwoHlrjRh8H82G64g+k7VuWPJlgMo6qrjfCulOdfBjiaDtmhFYD/Q== dependencies: "@types/prettier" "^2.1.1" @@ -15799,6 +15811,14 @@ url-parse@^1.5.3: querystringify "^2.1.1" requires-port "^1.0.0" +url@^0.11.3: + version "0.11.3" + resolved "https://registry.yarnpkg.com/url/-/url-0.11.3.tgz#6f495f4b935de40ce4a0a52faee8954244f3d3ad" + integrity sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw== + dependencies: + punycode "^1.4.1" + qs "^6.11.2" + use-callback-ref@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz"