diff --git a/package.json b/package.json index 716c629..9f464c3 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@types/qrcode": "^1.5.5", "isomorphic-fetch": "3.0.0", "lodash": "4.17.21", + "lit": "3.2.1", "protobufjs": "7.3.0", "qrcode": "^1.5.4", "socket.io-client": "4.7.5", diff --git a/src/core/providers/ProviderFactory.ts b/src/core/providers/ProviderFactory.ts index 87872b2..d35d892 100644 --- a/src/core/providers/ProviderFactory.ts +++ b/src/core/providers/ProviderFactory.ts @@ -8,6 +8,8 @@ import { createCrossWindowProvider } from './helpers/crossWindow/createCrossWind import { createExtensionProvider } from './helpers/extension/createExtensionProvider'; import { createMetamaskProvider } from './helpers/iframe/createMetamaskProvider'; import { createWalletconnectProvider } from './helpers/walletconnect/createWalletconnectProvider'; +import { networkSelector } from 'store/selectors'; +import { getState } from 'store/store'; export class ProviderFactory { public async create({ @@ -16,6 +18,7 @@ export class ProviderFactory { customProvider }: IProviderFactory): Promise { let createdProvider: IProvider | undefined; + const network = networkSelector(getState()); switch (type) { case ProviderTypeEnum.extension: { @@ -30,10 +33,8 @@ export class ProviderFactory { } case ProviderTypeEnum.crossWindow: { - const { walletAddress } = config.network; - const provider = await createCrossWindowProvider({ - walletAddress, + walletAddress: config.network.walletAddress ?? network.walletAddress, address: config.account?.address }); createdProvider = provider as unknown as IProvider; @@ -76,7 +77,12 @@ export class ProviderFactory { } case ProviderTypeEnum.walletconnect: { - createdProvider = createWalletconnectProvider(); + createdProvider = createWalletconnectProvider({ + network: { + ...network, + ...config.network + } + }); break; } diff --git a/src/core/providers/helpers/walletconnect/components/WalletconnectModalComponent.ts b/src/core/providers/helpers/walletconnect/components/WalletconnectModalComponent.ts new file mode 100644 index 0000000..4255dd7 --- /dev/null +++ b/src/core/providers/helpers/walletconnect/components/WalletconnectModalComponent.ts @@ -0,0 +1,87 @@ +import { LitElement, html, css } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import QRCode from 'qrcode'; + +@customElement('wallet-connect-modal') +export class WalletconnectModalComponent extends LitElement { + @property({ type: Boolean }) isOpen = false; + @property({ type: String }) qrCodeData = ''; + + static styles = css` + .modal { + display: none; + position: fixed; + z-index: 1000; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.4); + } + .modal-content { + background-color: #fefefe; + margin: 15% auto; + padding: 20px; + border: 1px solid #888; + width: 80%; + max-width: 500px; + } + .close { + color: #aaa; + float: right; + font-size: 28px; + font-weight: bold; + cursor: pointer; + } + .close:hover, + .close:focus { + color: black; + text-decoration: none; + cursor: pointer; + } + `; + + render() { + return html` + + `; + } + + async open(connectorUri: string) { + this.isOpen = true; + this.qrCodeData = await QRCode.toString(connectorUri, { type: 'svg' }); + this.requestUpdate(); + await this.updateComplete; + const qrContainer = this.shadowRoot!.getElementById('qrContainer'); + if (qrContainer) { + qrContainer.innerHTML = this.qrCodeData; + } + } + + close() { + this.isOpen = false; + } +} + +export function createModalFunctions() { + const modalElement = document.createElement( + 'wallet-connect-modal' + ) as WalletconnectModalComponent; + document.body.appendChild(modalElement); + + return { + openModal: async (connectorUri: string) => { + await modalElement.open(connectorUri); + }, + closeModal: () => { + modalElement.close(); + } + }; +} diff --git a/src/core/providers/helpers/walletconnect/createWalletconnectProvider.ts b/src/core/providers/helpers/walletconnect/createWalletconnectProvider.ts index ef760d8..a4b0988 100644 --- a/src/core/providers/helpers/walletconnect/createWalletconnectProvider.ts +++ b/src/core/providers/helpers/walletconnect/createWalletconnectProvider.ts @@ -1,18 +1,33 @@ import { WalletConnectV2Provider } from '@multiversx/sdk-wallet-connect-provider'; import { IProvider } from 'core/providers/types/providerFactory.types'; -import QRCode from 'qrcode'; -import { networkSelector } from 'store/selectors'; -import { getState } from 'store/store'; +import { createModalFunctions } from './components/WalletconnectModalComponent'; +import { CurrentNetworkType } from 'types'; + +interface IWalletconnectProvider { + openModal?: (connectorUri: string) => Promise; + closeModal?: () => void; + onClientLogin?: () => Promise; + onClientLogout?: () => void; + onClientEvent?: (event: any) => void; + network: CurrentNetworkType; +} + +export function createWalletconnectProvider( + props: IWalletconnectProvider +): IProvider { + const modalFunctions = createModalFunctions(); + const openModal = props.openModal ?? modalFunctions.openModal; + const closeModal = props.closeModal ?? modalFunctions.closeModal; -export function createWalletconnectProvider() { - const network = networkSelector(getState()); const provider = new WalletConnectV2Provider( prepareCallbacks(), - network.chainId, - network.walletConnectV2RelayAddress, - String(network.walletConnectV2ProjectId) + props.network.chainId, + props.network.walletConnectV2RelayAddress, + String(props.network.walletConnectV2ProjectId) ); + const walletconnectLogin = provider.login; + const createdProvider = provider as unknown as IProvider; function prepareCallbacks() { @@ -42,10 +57,10 @@ export function createWalletconnectProvider() { throw 'URI not found'; } - await openModal(uri); + await openModal?.(uri); try { - const account = await provider.login({ + const account = await walletconnectLogin({ approval, token: options?.token }); @@ -54,7 +69,7 @@ export function createWalletconnectProvider() { const signature = account?.signature; if (!account) { - throw new Error('Connection Proposal Refused'); + throw new Error(`Connection Proposal Refused ${account}`); } return { @@ -62,89 +77,9 @@ export function createWalletconnectProvider() { signature: signature || '' }; } catch (err) { - throw new Error('Connection Proposal Refused'); + throw new Error(`Connection Proposal Refused, ${err}`); } }; return createdProvider; } - -async function openModal(connectorUri: string) { - const svg = await QRCode.toString(connectorUri, { type: 'svg' }); - - // Check if the modal already exists - let modal = document.getElementById('MyWalletConnectV2Modal'); - - if (!modal) { - // Create the modal HTML - modal = document.createElement('div'); - modal.id = 'MyWalletConnectV2Modal'; - modal.className = 'modal'; - - const modalDialog = document.createElement('div'); - modalDialog.className = 'modal-dialog'; - modal.appendChild(modalDialog); - - const modalContent = document.createElement('div'); - modalContent.className = 'modal-content'; - modalDialog.appendChild(modalContent); - - const modalHeader = document.createElement('div'); - modalHeader.className = 'modal-header'; - modalContent.appendChild(modalHeader); - - const modalTitle = document.createElement('h4'); - modalTitle.className = 'modal-title'; - modalTitle.textContent = 'Connect using xPortal on your phone'; - modalHeader.appendChild(modalTitle); - - const closeButton = document.createElement('button'); - closeButton.type = 'button'; - closeButton.className = 'close'; - closeButton.textContent = '×'; - closeButton.onclick = closeModal; - modalHeader.appendChild(closeButton); - - const modalBody = document.createElement('div'); - modalBody.className = 'modal-body'; - modalContent.appendChild(modalBody); - - const qrContainer = document.createElement('div'); - qrContainer.id = 'MyWalletConnectV2QRContainer'; - modalBody.appendChild(qrContainer); - - const modalFooter = document.createElement('div'); - modalFooter.className = 'modal-footer'; - modalContent.appendChild(modalFooter); - - const closeButtonFooter = document.createElement('button'); - closeButtonFooter.type = 'button'; - closeButtonFooter.className = 'btn btn-danger'; - closeButtonFooter.textContent = 'Close'; - closeButtonFooter.onclick = closeModal; - modalFooter.appendChild(closeButtonFooter); - - // Append the modal to the body - document.body.appendChild(modal); - } - - // Get the QR container - const qrContainer = document.getElementById('MyWalletConnectV2QRContainer'); - - if (qrContainer) { - qrContainer.innerHTML = svg; - } - - // Show the modal - modal.classList.add('show'); - modal.style.display = 'block'; -} - -function closeModal() { - const modal = document.getElementById('MyWalletConnectV2Modal'); - - if (modal) { - modal.classList.remove('show'); - modal.style.display = 'none'; - } -} diff --git a/src/core/providers/types/providerFactory.types.ts b/src/core/providers/types/providerFactory.types.ts index 8ba8075..874945f 100644 --- a/src/core/providers/types/providerFactory.types.ts +++ b/src/core/providers/types/providerFactory.types.ts @@ -1,6 +1,6 @@ import type { IDAppProviderBase } from '@multiversx/sdk-dapp-utils'; +import { CustomNetworkType } from 'types/network.types'; -// @ts-ignore export interface IProvider extends IDAppProviderBase { init: () => Promise; login: (options?: { callbackUrl?: string; token?: string }) => Promise<{ @@ -20,9 +20,7 @@ export interface IProvider extends IDAppProviderBase { } export interface IProviderConfig { - network: { - walletAddress: string; - }; + network: CustomNetworkType; account?: { address: string; }; diff --git a/yarn.lock b/yarn.lock index b48ea36..5920a73 100644 --- a/yarn.lock +++ b/yarn.lock @@ -845,6 +845,18 @@ dependencies: "@types/node-fetch" "^2.5.10" +"@lit-labs/ssr-dom-shim@^1.2.0": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.1.tgz#2f3a8f1d688935c704dbc89132394a41029acbb8" + integrity sha512-wx4aBmgeGvFmOKucFKY+8VFJSYZxs9poN3SDNQFF6lT6NrQUnHiPB2PWz2sc4ieEcAaYYzN+1uWahEeTq2aRIQ== + +"@lit/reactive-element@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-2.0.4.tgz#8f2ed950a848016383894a26180ff06c56ae001b" + integrity sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ== + dependencies: + "@lit-labs/ssr-dom-shim" "^1.2.0" + "@metamask/json-rpc-engine@^7.3.2": version "7.3.3" resolved "https://registry.yarnpkg.com/@metamask/json-rpc-engine/-/json-rpc-engine-7.3.3.tgz#f2b30a2164558014bfcca45db10f5af291d989af" @@ -1734,6 +1746,11 @@ resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304" integrity sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA== +"@types/trusted-types@^2.0.2": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11" + integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw== + "@types/yargs-parser@*": version "21.0.3" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" @@ -5107,6 +5124,31 @@ listhen@^1.7.2: untun "^0.1.3" uqr "^0.1.2" +lit-element@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-4.1.1.tgz#07905992815076e388cf6f1faffc7d6866c82007" + integrity sha512-HO9Tkkh34QkTeUmEdNYhMT8hzLid7YlMlATSi1q4q17HE5d9mrrEHJ/o8O2D0cMi182zK1F3v7x0PWFjrhXFew== + dependencies: + "@lit-labs/ssr-dom-shim" "^1.2.0" + "@lit/reactive-element" "^2.0.4" + lit-html "^3.2.0" + +lit-html@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-3.2.1.tgz#8fc49e3531ee5947e4d93e8a5aa642ab1649833b" + integrity sha512-qI/3lziaPMSKsrwlxH/xMgikhQ0EGOX2ICU73Bi/YHFvz2j/yMCIrw4+puF2IpQ4+upd3EWbvnHM9+PnJn48YA== + dependencies: + "@types/trusted-types" "^2.0.2" + +lit@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/lit/-/lit-3.2.1.tgz#d6dd15eac20db3a098e81e2c85f70a751ff55592" + integrity sha512-1BBa1E/z0O9ye5fZprPtdqnc0BFzxIxTTOO/tQFmyC/hj1O3jL4TfmLBw0WEwjAokdLwpclkvGgDJwTIh0/22w== + dependencies: + "@lit/reactive-element" "^2.0.4" + lit-element "^4.1.0" + lit-html "^3.2.0" + locate-path@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"