From b457675b3f29570f875b46fe24c5e2c97055c139 Mon Sep 17 00:00:00 2001 From: Davide Carpini Date: Fri, 10 Nov 2023 12:28:03 +0100 Subject: [PATCH] feat: create a custom modal to handle wallet connect (#67) * feat: create a custom modal to handle wallet connect * fix: types * chore: remove hardhat --------- Co-authored-by: Darren Kelly --- apps/sample-react-app/contracts/Counter.sol | 22 -- apps/sample-react-app/hardhat.config.ts | 11 - apps/sample-react-app/package.json | 14 +- .../src/Components/AddressButton.tsx | 1 - package.json | 2 +- packages/vanilla-wallet-kit/index.html | 6 +- packages/vanilla-wallet-kit/index.js | 21 ++ packages/vanilla-wallet-kit/package.json | 5 +- .../src/assets/icons/chevron-left.ts | 17 ++ .../src/assets/icons/copy.ts | 17 ++ .../src/assets/icons/index.ts | 2 + packages/vanilla-wallet-kit/src/client.ts | 50 +++- .../components/base/vwk-base-modal/index.ts | 2 +- .../src/components/index.ts | 1 + .../components/vwk-connect-button/index.ts | 4 +- .../src/components/vwk-connect-modal/index.ts | 88 ++++-- .../src/components/vwk-source-card/index.ts | 2 +- .../vwk-wallet-connect-qrcode/index.ts | 124 ++++++++ .../src/constants/enums/colors.ts | 4 +- packages/vanilla-wallet-kit/src/index.ts | 1 + .../vanilla-wallet-kit/src/utils/index.ts | 1 + .../vanilla-wallet-kit/src/utils/qr-code.ts | 218 ++++++++++++++ packages/wallet-kit/src/create-wallet.ts | 4 +- yarn.lock | 280 ++---------------- 24 files changed, 556 insertions(+), 341 deletions(-) delete mode 100644 apps/sample-react-app/contracts/Counter.sol delete mode 100644 apps/sample-react-app/hardhat.config.ts create mode 100644 packages/vanilla-wallet-kit/index.js create mode 100644 packages/vanilla-wallet-kit/src/assets/icons/chevron-left.ts create mode 100644 packages/vanilla-wallet-kit/src/assets/icons/copy.ts create mode 100644 packages/vanilla-wallet-kit/src/components/vwk-wallet-connect-qrcode/index.ts create mode 100644 packages/vanilla-wallet-kit/src/utils/index.ts create mode 100644 packages/vanilla-wallet-kit/src/utils/qr-code.ts diff --git a/apps/sample-react-app/contracts/Counter.sol b/apps/sample-react-app/contracts/Counter.sol deleted file mode 100644 index 58bc5d9f..00000000 --- a/apps/sample-react-app/contracts/Counter.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -contract Counter { - uint256 public count; - - event CounterUpdated(uint256 newValue); - - constructor() { - count = 0; - } - - function increment() public { - count += 1; - emit CounterUpdated(count); - } - - function incrementBy(uint256 value) public { - count += value; - emit CounterUpdated(count); - } -} diff --git a/apps/sample-react-app/hardhat.config.ts b/apps/sample-react-app/hardhat.config.ts deleted file mode 100644 index 9eac3ffc..00000000 --- a/apps/sample-react-app/hardhat.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { HardhatUserConfig } from 'hardhat/config'; -import '@nomicfoundation/hardhat-toolbox'; - -const config: HardhatUserConfig = { - 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 7615fe61..004fbfa1 100644 --- a/apps/sample-react-app/package.json +++ b/apps/sample-react-app/package.json @@ -5,12 +5,10 @@ "homepage": ".", "main": "src/index.js", "scripts": { - "build": "yarn compile && react-app-rewired build", - "clean": "rm -rf build .turbo cache artifacts src/hardhat", - "compile": "yarn hardhat compile", + "build": "react-app-rewired build", + "clean": "rm -rf build .turbo cache", "dev": "react-app-rewired start", "eject": "react-app-rewired eject", - "postinstall": "yarn compile", "lint": "eslint src --ext .js,.jsx,.ts,.tsx", "purge": "yarn clean && rm -rf node_modules", "start": "HTTPS=true react-app-rewired start", @@ -40,8 +38,6 @@ "@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", "@vechainfoundation/react-wallet-kit": "*", @@ -65,16 +61,10 @@ "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", diff --git a/apps/sample-react-app/src/Components/AddressButton.tsx b/apps/sample-react-app/src/Components/AddressButton.tsx index 1bb4bdee..1fbfed73 100644 --- a/apps/sample-react-app/src/Components/AddressButton.tsx +++ b/apps/sample-react-app/src/Components/AddressButton.tsx @@ -23,7 +23,6 @@ export const AddressButton: React.FC = ({ const onClickHandler = useCallback( (e: React.MouseEvent): void => { - // console.log(onClick) if (onClick) onClick(e); if (showCopyIcon) onCopy(); }, diff --git a/package.json b/package.json index 4831797f..921232d1 100755 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "lint": "turbo run lint", "prepare": "husky install", "purge": "npx turbo@latest run purge && rm -rf node_modules", - "reinstall": "yarn clean && yarn && yarn run build:deps", + "reinstall": "yarn clean && yarn purge && yarn && yarn run build:deps", "test": "turbo run test" }, "husky": { diff --git a/packages/vanilla-wallet-kit/index.html b/packages/vanilla-wallet-kit/index.html index 61f4d410..312fce50 100644 --- a/packages/vanilla-wallet-kit/index.html +++ b/packages/vanilla-wallet-kit/index.html @@ -2,13 +2,13 @@ - Connect + Sample Vanilla App - + diff --git a/packages/vanilla-wallet-kit/index.js b/packages/vanilla-wallet-kit/index.js new file mode 100644 index 00000000..4e51aff2 --- /dev/null +++ b/packages/vanilla-wallet-kit/index.js @@ -0,0 +1,21 @@ +// eslint-disable-next-line eslint-comments/disable-enable-pair +/* eslint-disable no-undef */ +import { configureThorModal } from './dist'; + +const walletConnectOptions = { + projectId: 'a0b855ceaf109dbc8426479a4c3d38d8', + metadata: { + name: 'Sample VeChain dApp', + description: 'A sample VeChain dApp', + url: window.location.origin, + icons: [`${window.location.origin}/images/logo/my-dapp.png`], + }, +}; + +const vechainWalletKitOptions = { + node: 'https://testnet.vechain.org/', + network: 'test', + walletConnectOptions, +}; + +configureThorModal(vechainWalletKitOptions); diff --git a/packages/vanilla-wallet-kit/package.json b/packages/vanilla-wallet-kit/package.json index 6d872dd6..ac01a9f6 100644 --- a/packages/vanilla-wallet-kit/package.json +++ b/packages/vanilla-wallet-kit/package.json @@ -17,21 +17,24 @@ "scripts": { "build": "tsup", "clean": "rm -rf dist .turbo", - "dev": "parcel index.html --open --port 1234", + "dev": "parcel index.html", "format": "prettier \"**/*.{cjs,html,js,json,md,ts}\" --ignore-path ./.eslintignore --write", "lint": "eslint 'src/**/*.ts'", "purge": "yarn clean && rm -rf node_modules", "watch": "tsup --watch" }, "dependencies": { + "@vechainfoundation/wallet-connect": "*", "@vechainfoundation/wallet-kit": "*", "@wagmi/core": "^1.4.5", "@web3modal/ethereum": "^2.7.1", "@web3modal/html": "^2.7.1", "lit": "^3.0.0", + "qrcode": "1.5.3", "viem": "^1.18.4" }, "devDependencies": { + "@types/qrcode": "^1.5.5", "@typescript-eslint/eslint-plugin": "^5.25.0", "@typescript-eslint/parser": "^5.25.0", "@vechain/repo-config": "https://github.com/vechainfoundation/repo-config#v0.0.1", diff --git a/packages/vanilla-wallet-kit/src/assets/icons/chevron-left.ts b/packages/vanilla-wallet-kit/src/assets/icons/chevron-left.ts new file mode 100644 index 00000000..98a9a28d --- /dev/null +++ b/packages/vanilla-wallet-kit/src/assets/icons/chevron-left.ts @@ -0,0 +1,17 @@ +import { html, svg } from 'lit'; + +export const ChevronLeftSvg = svg` + + `; + +export const LightChevronLeftSvg = html` + + ${ChevronLeftSvg} + +`; +export const DarkChevronLeftSvg = html` + + ${ChevronLeftSvg} + +`; diff --git a/packages/vanilla-wallet-kit/src/assets/icons/copy.ts b/packages/vanilla-wallet-kit/src/assets/icons/copy.ts new file mode 100644 index 00000000..7e60a386 --- /dev/null +++ b/packages/vanilla-wallet-kit/src/assets/icons/copy.ts @@ -0,0 +1,17 @@ +import { html, svg } from 'lit'; + +export const CopySvg = svg` + + `; + +export const LightCopySvg = html` + + ${CopySvg} + +`; +export const DarkCopySvg = html` + + ${CopySvg} + +`; diff --git a/packages/vanilla-wallet-kit/src/assets/icons/index.ts b/packages/vanilla-wallet-kit/src/assets/icons/index.ts index 38cf8d25..59b757cc 100644 --- a/packages/vanilla-wallet-kit/src/assets/icons/index.ts +++ b/packages/vanilla-wallet-kit/src/assets/icons/index.ts @@ -1 +1,3 @@ export * from './close'; +export * from './chevron-left'; +export * from './copy'; diff --git a/packages/vanilla-wallet-kit/src/client.ts b/packages/vanilla-wallet-kit/src/client.ts index 1dd14090..7f3ceb01 100644 --- a/packages/vanilla-wallet-kit/src/client.ts +++ b/packages/vanilla-wallet-kit/src/client.ts @@ -1,11 +1,30 @@ import type { WalletSource } from '@vechainfoundation/wallet-kit'; import { MultiWalletConnex } from '@vechainfoundation/wallet-kit'; import type { ConnexOptions } from '@vechainfoundation/wallet-kit/src'; -import type { SourceInfo } from './constants'; +import type { WCModal } from '@vechainfoundation/wallet-connect'; +import type { OpenOptions } from '@vechainfoundation/wallet-connect/src/types'; import './components'; +import type { SourceInfo } from './constants'; export type VechainWalletKitOptions = MultiWalletConnex | ConnexOptions; +class CustomWalletConnectModal implements WCModal { + openModal(options: OpenOptions): Promise { + const { uri } = options; + dispatchEvent(new CustomEvent('vwk-open-wc-modal', { detail: uri })); + return Promise.resolve(); + } + closeModal(): void { + // NOT USED because it is controlled from inside the component + // dispatchEvent(new CustomEvent('vwk-close-wc-modal')); + } + subscribeModal() { + // NOT USED because it is controlled from inside the component + // eslint-disable-next-line @typescript-eslint/no-empty-function + return (): void => {}; + } +} + class VechainWalletKit { connex: MultiWalletConnex; account: string | null = null; @@ -14,10 +33,22 @@ class VechainWalletKit { if ('thor' in options) { this.connex = options; } else { - this.connex = new MultiWalletConnex(options); + this.connex = new MultiWalletConnex( + this.addCustomWalletConnectModalIfNotPresent(options), + ); } } + addCustomWalletConnectModalIfNotPresent( + options: ConnexOptions, + ): ConnexOptions { + return { + ...options, + customWcModal: + options.customWcModal || new CustomWalletConnectModal(), + }; + } + setSource = (wallet: WalletSource): void => { this.connex.wallet.setSource(wallet); }; @@ -33,14 +64,10 @@ class VechainWalletKitModal { initModalListeners(): void { addEventListener('vwk-source-card-clicked', (event) => { const source = (event as CustomEvent).detail as SourceInfo; - // eslint-disable-next-line no-console - console.log('vwk-source-card-clicked', source); this.walletKit.setSource(source.id); this.walletKit.connex.wallet .connect() .then(({ account }) => { - // eslint-disable-next-line no-console - console.log('account connected', account); this.walletKit.account = account; }) .catch((error) => { @@ -48,13 +75,20 @@ class VechainWalletKitModal { console.error(error); }); }); + const disconnect = (): void => { + this.walletKit.connex.wallet.disconnect().catch((error) => { + // eslint-disable-next-line no-console + console.error(error); + }); + }; + addEventListener('vwk-close-wc-modal', disconnect); } } export const configureThorModal = ( - walletKit: VechainWalletKitOptions, + walletKitOptions: VechainWalletKitOptions, ): void => { - const vechainWalletKit = new VechainWalletKit(walletKit); + const vechainWalletKit = new VechainWalletKit(walletKitOptions); const vechainWalletKitModal = new VechainWalletKitModal(vechainWalletKit); vechainWalletKitModal.initModalListeners(); }; diff --git a/packages/vanilla-wallet-kit/src/components/base/vwk-base-modal/index.ts b/packages/vanilla-wallet-kit/src/components/base/vwk-base-modal/index.ts index f5916c95..16ad20e2 100644 --- a/packages/vanilla-wallet-kit/src/components/base/vwk-base-modal/index.ts +++ b/packages/vanilla-wallet-kit/src/components/base/vwk-base-modal/index.ts @@ -32,7 +32,7 @@ export class Modal extends LitElement { .modal.DARK { background-color: ${Colors.Dark}; - color: ${Colors.LightGray}; + color: ${Colors.LightGrey}; } @media (max-width: 600px) { diff --git a/packages/vanilla-wallet-kit/src/components/index.ts b/packages/vanilla-wallet-kit/src/components/index.ts index 8a5e5a9d..b986c786 100644 --- a/packages/vanilla-wallet-kit/src/components/index.ts +++ b/packages/vanilla-wallet-kit/src/components/index.ts @@ -11,3 +11,4 @@ export * from './vwk-connect-button'; export * from './vwk-connect-button-with-modal'; export * from './vwk-source-card'; export * from './vwk-fonts'; +export * from './vwk-wallet-connect-qrcode'; diff --git a/packages/vanilla-wallet-kit/src/components/vwk-connect-button/index.ts b/packages/vanilla-wallet-kit/src/components/vwk-connect-button/index.ts index 4173035e..cf9cc217 100644 --- a/packages/vanilla-wallet-kit/src/components/vwk-connect-button/index.ts +++ b/packages/vanilla-wallet-kit/src/components/vwk-connect-button/index.ts @@ -21,13 +21,13 @@ export class ConnectButton extends LitElement { } button.LIGHT { - background-color: ${Colors.LightGray}; + background-color: ${Colors.LightGrey}; color: ${Colors.Dark}; } button.DARK { background-color: ${Colors.Dark}; - color: ${Colors.LightGray}; + color: ${Colors.LightGrey}; } `; diff --git a/packages/vanilla-wallet-kit/src/components/vwk-connect-modal/index.ts b/packages/vanilla-wallet-kit/src/components/vwk-connect-modal/index.ts index c68ba001..ca980247 100644 --- a/packages/vanilla-wallet-kit/src/components/vwk-connect-modal/index.ts +++ b/packages/vanilla-wallet-kit/src/components/vwk-connect-modal/index.ts @@ -2,15 +2,31 @@ import type { TemplateResult } from 'lit'; import { css, html, LitElement, nothing } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { Theme, ThemeMode } from '@vechainfoundation/wallet-kit'; -import { DarkCloseSvg, LightCloseSvg } from '../../assets'; +import { + DarkCloseSvg, + LightCloseSvg, + LightChevronLeftSvg, + DarkChevronLeftSvg, +} from '../../assets'; import type { SourceInfo } from '../../constants'; -import { WalletSources } from '../../constants'; +import { Colors, WalletSources } from '../../constants'; @customElement('vwk-connect-modal') export class ConnectModal extends LitElement { + constructor() { + super(); + addEventListener('vwk-open-wc-modal', (event) => { + const uri = (event as CustomEvent).detail as string; + this.walletConnectQRcode = uri; + }); + addEventListener('vwk-close-wc-modal', () => { + this.walletConnectQRcode = undefined; + }); + } static override styles = css` .modal-container { padding: 20px; + transition: width 5s, height 4s; } .modal-header { @@ -23,12 +39,22 @@ export class ConnectModal extends LitElement { .modal-body { flex-direction: column; + transition: width 2s, height 4s; } - .close-icon { + .icon { cursor: pointer; - width: 20px; - height: 20px; + width: 25px; + height: 25px; + padding: 5px; + border-radius: 50%; + } + + .icon.LIGHT:hover { + background-color: ${Colors.LightGrey}; + } + .icon.DARK:hover { + background-color: ${Colors.DarkGrey}; } `; @@ -40,38 +66,66 @@ export class ConnectModal extends LitElement { mode = ThemeMode.Light; @property() theme = Theme.Default; - + @property() + walletConnectQRcode?: string = undefined; @property({ type: Function }) onClose: () => void = () => nothing; + private onBack = (): void => { + dispatchEvent(new CustomEvent('vwk-close-wc-modal')); + }; + private handleClose = (): void => { + this.onBack(); + this.onClose(); + }; + override render(): TemplateResult { return html`