diff --git a/resources/js/components/SignTransaction.vue b/resources/js/components/SignTransaction.vue index a8a3a52..6b7732b 100644 --- a/resources/js/components/SignTransaction.vue +++ b/resources/js/components/SignTransaction.vue @@ -21,7 +21,7 @@
-
+
No accounts found. Please connect your wallet.
@@ -51,13 +51,13 @@ import { DialogTitle } from '@headlessui/vue'; import Btn from './Btn.vue'; import Modal from './Modal.vue'; import { addressShortHex } from '~/util/address'; -import { useAppStore } from '~/store'; import { useTransactionStore } from '~/store/transaction'; import { ref } from 'vue'; import LoadingCircle from './LoadingCircle.vue'; import snackbar from '~/util/snackbar'; import Identicon from './Identicon.vue'; import { formatPriceFromENJ } from '~/util'; +import { useConnectionStore } from '~/store/connection'; const props = defineProps<{ transaction: any; @@ -71,19 +71,19 @@ const feeCost = ref(); const loadingApi = ref(false); const signing = ref(false); -const appStore = useAppStore(); const transactionStore = useTransactionStore(); +const connectionStore = useConnectionStore(); const signTransaction = async () => { try { - if (!appStore.provider) { + if (!connectionStore.provider) { snackbar.error({ title: 'Please connect your wallet to sign' }); return; } showAccountsModal.value = true; loadingApi.value = true; - appStore.getAccounts(); + connectionStore.getAccounts(); await transactionStore.init(); feeCost.value = formatPriceFromENJ(props.transaction.fee)?.toFixed(5); loadingApi.value = false; @@ -100,7 +100,7 @@ const closeModal = () => { const selectAccount = async (account) => { try { isLoading.value = true; - await appStore.setAccount(account); + await connectionStore.setAccount(account); signing.value = true; const res = await transactionStore.signTransaction(props.transaction); if (res) { diff --git a/resources/js/components/WalletConnectButton.vue b/resources/js/components/WalletConnectButton.vue index 9c5c86d..52d7279 100644 --- a/resources/js/components/WalletConnectButton.vue +++ b/resources/js/components/WalletConnectButton.vue @@ -57,25 +57,25 @@ diff --git a/resources/js/store/connection.ts b/resources/js/store/connection.ts new file mode 100644 index 0000000..05ad305 --- /dev/null +++ b/resources/js/store/connection.ts @@ -0,0 +1,171 @@ +import { defineStore } from 'pinia'; +import { ConnectionState } from '~/types/types.interface'; +import { wcOptions } from '~/util'; +import { wcRequiredNamespaces } from '~/util'; +import { getAppMetadata, getSdkError } from '@walletconnect/utils'; +import { PolkadotjsWallet, Wallet } from '@talismn/connect-wallets'; +import SignClient from '@walletconnect/sign-client'; +import { HexString } from '@polkadot/util/types'; +import { wcNamespaces, wcProjectId } from '~/util/constants'; +import { useAppStore } from '.'; +import { WalletConnectModalSign } from '@walletconnect/modal-sign-html'; + +export const useConnectionStore = defineStore('connection', { + state: (): ConnectionState => ({ + provider: '', + wallet: false, + walletClient: null, + walletSession: null, + account: null, + accounts: null, + }), + persist: { + paths: ['provider'], + }, + actions: { + getWeb3Modal() { + return new WalletConnectModalSign(wcOptions); + }, + async connectWallet(provider) { + if (provider === 'wc') { + await this.connectWC(); + } + + if (provider === 'polkadot.js') { + await this.connectPolkadotJS(); + } + }, + async initWalletClient() { + this.walletClient = await SignClient.init({ + projectId: wcProjectId, + metadata: getAppMetadata(), + }); + + if (this.walletClient) { + this.walletClient.on('session_delete', () => { + this.disconnectWallet(); + }); + this.walletClient.on('session_expire', () => { + this.disconnectWallet(); + }); + } + + this.walletClient.session.getAll({}); + if (this.walletClient.session.length) { + const lastKeyIndex = this.walletClient.session.keys.length - 1; + this.walletSession = this.walletClient.session.get(this.walletClient.session.keys[lastKeyIndex]); + } + }, + async connectWC() { + const walletConnect = this.getWeb3Modal(); + await this.initWalletClient(); + + this.walletSession = await walletConnect.connect({ + requiredNamespaces: wcRequiredNamespaces(useAppStore().config.network), + }); + + if (this.walletSession && this.walletSession.acknowledged) { + this.provider = 'wc'; + this.wallet = true; + await this.initWalletClient(); + + return; + } + + await walletConnect!.disconnect({ + topic: this.walletSession.topic, + reason: getSdkError('USER_REJECTED'), + }); + + this.account = null; + }, + async connectPolkadotJS() { + const pkjs = new PolkadotjsWallet(); + if (pkjs.installed) { + await pkjs.enable('Platform'); + this.wallet = true; + this.provider = 'polkadot.js'; + this.walletSession = pkjs; + } + }, + + async getSession(): Promise { + if (this.provider === 'wc') { + await this.initWalletClient(); + + if (this.walletSession?.acknowledged) { + this.wallet = true; + } + } else if (this.provider === 'polkadot.js') { + const pkjs = new PolkadotjsWallet(); + this.walletSession = pkjs; + + if (this.walletSession.installed) { + await this.walletSession.enable('Platform'); + this.wallet = true; + } + } + return this.walletSession; + }, + async disconnectWallet() { + try { + if (this.provider === 'wc') { + if (this.walletClient) { + this.walletClient.disconnect({ + topic: this.walletSession.topic, + reason: getSdkError('USER_DISCONNECTED'), + }); + } + } + } finally { + this.account = null; + this.wallet = false; + this.provider = ''; + this.walletClient = null; + this.walletSession = null; + } + }, + async setAccount(account: Wallet) { + if (this.provider === 'wc') { + account.signer = { + signPayload: async (payload: any) => { + const result = await (this.walletClient! as SignClient).request<{ signature: HexString }>({ + chainId: wcNamespaces[useAppStore().config.network], + topic: this.walletSession?.topic, + request: { + method: 'polkadot_signTransaction', + params: { + address: payload.address, + transactionPayload: payload, + }, + }, + }); + + return result; + }, + }; + } + this.account = account; + }, + async getAccounts() { + if (this.provider === 'wc') { + if (!this.walletSession) { + return; + } + + const accounts = Object.values(this.walletSession.namespaces) + .map((namespace: any) => namespace.accounts) + .flat() + .map((account) => { + return { + address: account.split(':')[2], + }; + }); + this.accounts = accounts; + } else if (this.provider === 'polkadot.js') { + const accounts = await this.walletSession.getAccounts(); + this.accounts = accounts; + } + }, + }, +}); diff --git a/resources/js/store/index.ts b/resources/js/store/index.ts index 87bb0e7..0e3c222 100644 --- a/resources/js/store/index.ts +++ b/resources/js/store/index.ts @@ -5,14 +5,7 @@ import { ApiService } from '~/api'; import snackbar from '~/util/snackbar'; import { AuthApi } from '~/api/auth'; import { CollectionApi } from '~/api/collection'; -import { WalletConnectModalSign } from '@walletconnect/modal-sign-html'; -import { wcOptions } from '~/util'; -import { wcRequiredNamespaces } from '~/util'; -import { getAppMetadata, getSdkError } from '@walletconnect/utils'; -import { PolkadotjsWallet, Wallet } from '@talismn/connect-wallets'; -import SignClient from '@walletconnect/sign-client'; -import { HexString } from '@polkadot/util/types'; -import { wcNamespaces, wcProjectId } from '~/util/constants'; +import { useConnectionStore } from './connection'; const parseConfigURL = (url: string): URL => { return new URL(url); @@ -44,12 +37,6 @@ export const useAppStore = defineStore('app', { loggedIn: false, newCollection: false, user: null, - provider: '', - wallet: false, - walletClient: null, - walletSession: null, - account: null, - accounts: null, }), persist: { paths: ['url', 'authorization_token', 'loggedIn', 'advanced', 'provider'], @@ -83,7 +70,7 @@ export const useAppStore = defineStore('app', { if (this.hasMarketplacePackage) this.addMarketplaceNavigation(); if (this.loggedIn) await this.getUser(); - await this.getSession(); + await useConnectionStore().getSession(); return await this.fetchCollectionIds(); } catch (error: any) { @@ -240,145 +227,6 @@ export const useAppStore = defineStore('app', { setCollections(collections: string[]) { this.collections = collections; }, - getWeb3Modal() { - return new WalletConnectModalSign(wcOptions); - }, - async getSession(): Promise { - if (this.provider === 'wc') { - const walletConnect = this.getWeb3Modal(); - this.walletSession = await walletConnect.getSession(); - if (this.walletSession?.acknowledged) { - this.wallet = true; - } - } else if (this.provider === 'polkadot.js') { - const pkjs = new PolkadotjsWallet(); - this.walletSession = pkjs; - - if (this.walletSession.installed) { - await this.walletSession.enable('Platform'); - this.wallet = true; - } - } - return this.walletSession; - }, - async connectWallet(provider) { - if (provider === 'wc') { - await this.connectWC(); - } - - if (provider === 'polkadot.js') { - await this.connectPolkadotJS(); - } - }, - async connectWC() { - const walletConnect = this.getWeb3Modal(); - await this.initWalletClient(); - - this.walletSession = await walletConnect.connect({ - requiredNamespaces: wcRequiredNamespaces(this.config.network), - }); - - if (this.walletSession && this.walletSession.acknowledged) { - this.provider = 'wc'; - this.wallet = true; - await this.initWalletClient(); - - return; - } - - await walletConnect.disconnect({ - topic: this.walletSession.topic, - reason: getSdkError('USER_REJECTED'), - }); - - this.account = null; - }, - async connectPolkadotJS() { - const pkjs = new PolkadotjsWallet(); - if (pkjs.installed) { - await pkjs.enable('Platform'); - this.wallet = true; - this.provider = 'polkadot.js'; - this.walletSession = pkjs; - } - }, - async initWalletClient() { - this.walletClient = await SignClient.init({ - projectId: wcProjectId, - metadata: getAppMetadata(), - }); - - if (this.walletClient) { - this.walletClient.on('session_delete', () => { - this.disconnectWallet(); - }); - this.walletClient.on('session_expire', () => { - this.disconnectWallet(); - }); - } - }, - async disconnectWallet() { - try { - if (this.provider === 'wc') { - if (this.walletClient) { - this.walletClient.disconnect({ - topic: this.walletSession.topic, - reason: getSdkError('USER_DISCONNECTED'), - }); - } - } - } catch { - // do nothing - } finally { - this.account = null; - this.wallet = false; - this.provider = ''; - this.walletClient = null; - this.walletSession = null; - } - }, - async setAccount(account: Wallet) { - if (this.provider === 'wc') { - account.signer = { - signPayload: async (payload: any) => { - const result = await (this.walletClient! as SignClient).request<{ signature: HexString }>({ - chainId: wcNamespaces[this.config.network], - topic: this.walletSession?.topic, - request: { - method: 'polkadot_signTransaction', - params: { - address: payload.address, - transactionPayload: payload, - }, - }, - }); - - return result; - }, - }; - } - this.account = account; - }, - async getAccounts() { - if (this.provider === 'wc') { - if (!this.walletSession) { - return; - } - - const accounts = Object.values(this.walletSession.namespaces) - .map((namespace: any) => namespace.accounts) - .flat() - .map((account) => { - return { - address: account.split(':')[2], - }; - }); - this.accounts = accounts; - } else if (this.provider === 'polkadot.js') { - const accounts = await this.walletSession.getAccounts(); - this.accounts = accounts; - } - }, }, getters: { hasValidConfig(state: AppState) { diff --git a/resources/js/store/transaction.ts b/resources/js/store/transaction.ts index 8a443eb..dd39849 100644 --- a/resources/js/store/transaction.ts +++ b/resources/js/store/transaction.ts @@ -9,6 +9,7 @@ import { SignerPayloadJSON } from '@polkadot/types/types'; import { markRaw } from 'vue'; import { AccountInfoWithTripleRefCount } from '@polkadot/types/interfaces'; import { publicKeyToAddress } from '~/util/address'; +import { useConnectionStore } from './connection'; const RPC_URLS = { canary: 'wss://rpc.matrix.canary.enjin.io', @@ -71,11 +72,15 @@ export const useTransactionStore = defineStore('transaction', { }; }, async getTransactionCost(transaction: any) { - const { extrinsic } = await this.getExtrinsicData(transaction, useAppStore().accounts[0].address, this.api); + const { extrinsic } = await this.getExtrinsicData( + transaction, + useConnectionStore().accounts[0].address, + this.api + ); const paymentInfo = await this.api.tx[extrinsic.method.section] [extrinsic.method.method](...extrinsic.method.args) - .paymentInfo(useAppStore().accounts[0].address); + .paymentInfo(useConnectionStore().accounts[0].address); return paymentInfo.partialFee.toHuman(); }, @@ -83,7 +88,7 @@ export const useTransactionStore = defineStore('transaction', { const appStore = useAppStore(); if (appStore.user?.account || appStore.config.daemon) { if ( - publicKeyToAddress(appStore.account.address) !== + publicKeyToAddress(useConnectionStore().account.address) !== publicKeyToAddress(appStore.user?.account ?? appStore.config.daemon) ) { snackbar.error({ @@ -97,20 +102,20 @@ export const useTransactionStore = defineStore('transaction', { const { extrinsic, payloadToSign, currentBlock } = await this.getExtrinsicData( transaction, - appStore.account.address, + useConnectionStore().account.address, this.api ); - const { signature } = await appStore.account.signer.signPayload(payloadToSign); + const { signature } = await useConnectionStore().account.signer.signPayload(payloadToSign); - extrinsic.addSignature(appStore.account.address, signature, payloadToSign); + extrinsic.addSignature(useConnectionStore().account.address, signature, payloadToSign); const transactionHash = await this.api.rpc.author.submitExtrinsic(extrinsic.toHex()); return await this.updateTransaction({ id: transaction.id, transactionHash: transactionHash.toHex(), - signingAccount: appStore.account.address, + signingAccount: useConnectionStore().account.address, signedAtBlock: currentBlock.block.header.number.toNumber(), }); }, diff --git a/resources/js/types/types.interface.ts b/resources/js/types/types.interface.ts index 434e540..f196b21 100644 --- a/resources/js/types/types.interface.ts +++ b/resources/js/types/types.interface.ts @@ -1,4 +1,5 @@ import { BeamType, NotificationType, TokenCapType, TokenIdSelectType } from './types.enums'; +import type SignClient from '@walletconnect/sign-client'; export interface AppState { url?: URL; @@ -20,9 +21,12 @@ export interface AppState { loggedIn: boolean; newCollection: boolean; user: any; +} + +export interface ConnectionState { provider: string; wallet: boolean; - walletClient?: any; + walletClient?: SignClient | null; walletSession: any; account: any; accounts: any; diff --git a/resources/js/util/index.ts b/resources/js/util/index.ts index 7ce7d27..5d467b0 100644 --- a/resources/js/util/index.ts +++ b/resources/js/util/index.ts @@ -2,8 +2,8 @@ import { TokenIdSelectType } from '~/types/types.enums'; import { TokenIdType } from '~/types/types.interface'; import snackbar from '~/util/snackbar'; import { getAppMetadata } from '@walletconnect/utils'; -import { WalletConnectModalSignOptions } from '@walletconnect/modal-sign-html'; import { wcNamespaces, wcProjectId } from './constants'; +import { WalletConnectModalSignOptions } from '@walletconnect/modal-sign-html'; export const formatData = (entries: any, type = 'object') => { const data: { [key: string]: any } = type === 'object' ? {} : []; @@ -150,6 +150,7 @@ export const wcRequiredNamespaces = (network: string) => { }; }; + export const wcOptions: WalletConnectModalSignOptions = { projectId: wcProjectId, metadata: getAppMetadata(), @@ -157,7 +158,10 @@ export const wcOptions: WalletConnectModalSignOptions = { themeMode: 'light', explorerRecommendedWalletIds: ['210cd0946d2b026755635b95761c0570e817ade3f3052a0c6d273dba7ba47aff'], enableExplorer: true, - walletImages: {}, + walletImages: { + enjin: 'https://nft.io/images/enjin-wallet-long.svg', + }, + themeVariables: { '--wcm-background-color': '#7567CE', '--wcm-accent-color': '#7567CE',